When a class is decorated with @define or @frozen, and an attribute references a type declared later in the file, the generated __init__ method contains a type annotation that is different from the one on a hand-written __init__ method.
In Python 3.14 without from __future__ import annotations:
import inspect
from attrs import define
class Works:
def __init__(self, foo: Foo) -> None:
self._foo = foo
@define
class DoesNotWork:
_foo: Foo
class Foo:
pass
# prints `(foo: __main__.Foo) -> None`
print(inspect.signature(Works))
# prints `(foo: Foo) -> None` (a ForwardRef)
print(inspect.signature(DoesNotWork))
With from __future__ import annotations (tested on both 3.14 and 3.13 - same result):
from __future__ import annotations
import inspect
from attrs import define
class Works:
def __init__(self, foo: Foo) -> None:
self._foo = foo
@define
class DoesNotWork:
_foo: Foo
class Foo:
pass
# prints `(foo: __main__.Foo) -> None`
print(inspect.signature(Works, eval_str=True))
# crashes with `NameError: name 'Foo' is not defined`
print(inspect.signature(DoesNotWork, eval_str=True))
When a class is decorated with
@defineor@frozen, and an attribute references a type declared later in the file, the generated__init__method contains a type annotation that is different from the one on a hand-written__init__method.In Python 3.14 without
from __future__ import annotations:With
from __future__ import annotations(tested on both 3.14 and 3.13 - same result):