-
-
Notifications
You must be signed in to change notification settings - Fork 398
Description
attrs
appears to treat an initialized final class variable as an instance variable. I'd expect that this would follow PEP 591, specifically this sentence in the "Semantics and Examples" section:
Type checkers should infer a final attribute that is initialized in a class body as being a class variable. Variables should not be annotated with both ClassVar and Final.
However, that doesn't appear to be the case. Suppose we have the following code, where x
is intended to be an "immutable" class variable and y
is an instance variable:
@attr.s(auto_attribs=True)
class A:
x: Final[int] = 123
y: int
The code crashes with an error:
ValueError: No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, order=True, hash=None, init=True, metadata=mappingproxy({}), type=<class 'int'>, converter=None, kw_only=False, inherited=False, on_setattr=None)
This indicates that x
isn't being properly treated as a class variable and skipped over. Indeed, if we reverse the order of the declarations, it "works" in the sense that it doesn't crash, but it generates a class with incorrect behavior.
>>> @attr.s(auto_attribs=True)
... class A:
... y: int
... x: Final[int] = 123
>>> A(y=1, x=2)
A(y=1, x=2)
As you can see, x
is unexpectedly an instance variable. If we're conforming to PEP 591, the correct behavior here would be to build a class equivalent to the following:
@attr.s(auto_attribs=True)
class A:
x: ClassVar[int] = 123
y: int
Environment:
- CPython 3.8.8
attrs==20.3.0
- OS: MacOS 11.2.3