Skip to content

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

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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