Skip to content

Type annotations for attrs.field(converter=...) incorrectly reject tuples #1460

@finite-state-machine

Description

@finite-state-machine

Converters can currently be specified as _ConverterType, list[_ConverterType], or tuple[_ConverterType]. The last of these does not mean "tuple of _ConverterType instances" as was likely intended; it specifies a tuple with exactly one _ConverterType element. The correct form for a tuple with any number of instances is tuple[_ConverterType, ...].

A pull request will be created shortly.


I was tempted to change the parameter's type by replacing both the list and tuple expressions with a unified Sequence[_ConverterType], but that currently doesn't work at runtime (attrs seems to treat sequences other than list and tuple as if they should be callable converters, rather than an ordered collection of converters):

from __future__ import annotations
from collections.abc import (
        Sequence,
        )
from typing import (
        Self,
        TypeVar,
        overload,
        )

import attrs

_T = TypeVar('_T')

class ThirdSequenceType(Sequence[_T]):

    _payload: tuple[_T, ...]

    def __init__(self, value: Sequence[_T]) -> None:
        self._payload = tuple(value)

    def __len__(self) -> int:
        return len(self._payload)

    @overload
    def __getitem__(self, index: int) -> _T: ...
    @overload
    def __getitem__(self, index: slice) -> Self: ...

    def __getitem__(self, index: int | slice) -> _T | Self:
     
        if isinstance(index, slice):
            return type(self)(self._payload[index])
        return self._payload[index]


def negate(value: int) -> int:
    return -value

def square(value: int) -> int:
    return value * value


@attrs.define
class SomeClass:

    value: int = attrs.field(converter=ThirdSequenceType([square, negate]))


inst = SomeClass(3)
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "..../bug.py", line 56, in <module>
    inst = SomeClass(3)
           ^^^^^^^^^^^^
  File "<attrs generated methods __main__.SomeClass>", line 24, in __init__
TypeError: 'ThirdSequenceType' object is not callable

If we wrap the argument to converter= in tuple(), i.e., change the last few lines to:

@attrs.define
class SomeClass:

    value: int = attrs.field(converter=tuple(ThirdSequenceType([square, negate])))

inst = SomeClass(3)
print(inst.value)  # prints: -9

... then everything works as expected. My interpretation is that ThirdSequenceType is a perfectly well-behaved Sequence, but not one that attrs.field(converter=...) is willing to accept.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions