I am trying to make mypy happy with my type annotations. Here is minimal example:
class FooInterface:
x: int
class FooWithAttribute(FooInterface):
x: int = 0
class FooWithProperty(FooInterface):
@property
def x(self) -> int:
return 0
To my human understanding everything is fine: both FooWithAttribute().x
and FooWithProperty().x
will return 0
which is int
, no type errors. However mypy complains:
error: Signature of "x" incompatible with supertype "FooInterface"
Is there a way to tell mypy that everything is OK? Right now the only way I found is annotating x: typing.Any
in FooInterface
which wastes the information that x is int.
The @Property annotation can be used on fields, on setter methods or on a constructor method parameter. However, the @Property annotation MUST NOT be used on a class field that is declared as final. Properties can also be injected via setter methods even when the @Property annotation is not present.
The @property is a built-in decorator for the property() function in Python. It is used to give "special" functionality to certain methods to make them act as getters, setters, or deleters when we define properties in a class.
Mypy is actually pointing out a legitimate bug in your program. To demonstrate, suppose you have a program that looks like this:
def mutate(f: FooInterface) -> None:
f.x = 100
Seems fine, right? But what happens if we do mutate(FooWithProperty())
? Python will actually crash with an AttributeError
!
Traceback (most recent call last):
File "test.py", line 19, in <module>
mutate(FooWithProperty())
File "test.py", line 16, in mutate
f.x = 100
AttributeError: can't set attribute
To make mypy happy, you basically have two options:
FooInterface.x
also be a read-only propertyFooWithProperty.x
to make it writableI'm guessing that in your case, you probably want to take approach 1. If you do so, mypy will correctly point out that the line f.x = 100
is not permitted:
from abc import abstractmethod
class FooInterface:
# Marking this property as abstract is *optional*. If you do it,
# mypy will complain if you forget to define x in a subclass.
@property
@abstractmethod
def x(self) -> int: ...
class FooWithAttribute(FooInterface):
# No complaints from mypy here: having this attribute be writable
# won't violate the Liskov substitution principle -- it's safe to
# use FooWithAttribute in any location that expects a FooInterface.
x: int = 0
class FooWithProperty(FooInterface):
@property
def x(self) -> int:
return 0
def mutate(f: FooInterface) -> None:
# error: Property "x" defined in "FooInterface" is read-only
f.x = 100
mutate(FooWithProperty())
Approach 2 unfortunately doesn't quite work yet due to a bug in mypy -- mypy doesn't correctly understand how to handle overriding an attribute with a property. The workaround in this case is to make FooInterface.x
a property with a setter.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With