-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mypy disallows overriding an attribute with a property #4125
Comments
Hm, interesting, this is already allowed for structural subtypes: class Foo(Protocol):
x: int
class Bar:
def __init__(self) -> None:
self.y = 9000
@property
def x(self) -> int:
print('got x!!!')
return self.y
@x.setter
def x(self, value: int) -> None:
print('set x!!!')
self.y = value
x: Foo = Bar() # OK I think this is a bug, it should be allowed for nominal classes as well. |
Yes, this is a bug. I was under the impression that this was already working. |
Technically it does violate LSP; there should be a warning about the missing |
Yes, but if you add a deleter it still complains. |
I don't think mypy should actually care about whether a deleter is present though, since deleting attributes is rare and if you do it your code is unlikely to be type-safe anyway. |
True, but overriding an attribute with a property is also not so common, so a reminder that it's not complete might be useful. |
There is a different error message when using class Base:
prop: bool
class Sub(Base):
def _get_prop(self) -> bool:
return False
def _set_prop(self, value: bool) -> None:
pass
prop = property(_get_prop, _set_prop)
|
In
...I get the following behavior: class Foo(Protocol):
x: int
class Bar(Foo):
def __init__(self) -> None:
self.y = 9000
@property # Signature of "x" incompatible with supertype "Foo"
def x(self) -> int: # Signature of "x" incompatible with supertype "Foo"
print('got x!!!')
return self.y
@x.setter
def x(self, value: int) -> None:
print('set x!!!')
self.y = value
x: Foo = Bar() I would expect this to be OK for the same reasons that the original protocol example (no inheritance) is OK. |
Setting priority to high, see duplicate issue #6759 (comment) for motivation. |
I ran into a similar issue. Mypy seems to disallow overriding even a property, if not defined using a decorator: # Annotations in here doesn't seem to matter.
def _not_implemented(*args, **kwargs):
raise NotImplementedError
class AbstractClass:
foo = property(_not_implemented, _not_implemented)
class MyClass(AbstractClass):
# Here the mypy reports:
# error: Signature of "foo" incompatible with supertype "AbstractClass" [override]
@property
def foo(self) -> str:
return 'Foo'
@foo.setter
def foo(self, value: str) -> None:
pass If |
I disagree. Overriding an attribute with a property is a recommended best practice. You start your class with public attributes, and if a subclass needs some getter or setter logic, then you make it into a property. I agree that, pragmatically the lack of a deleter is not relevant in practice, and should be ignored. The following advice appears in Alex Martelli's classic and highly influential Python in a Nutshell since the first edition:
|
For what it's worth, pyright does not allow attributes to be overridden with properties or vice versa. Our reasoning is that the semantics for an attribute are not the same as a property. In the sample at the top of this issue, for example, the attribute would work with |
Thanks for your insight, @erictraut. That's yet another situation where "type safety" trumps established practices in Python. It makes me think everyone would be happier if there was a derivative language with static types—which was @JukkaL's original idea for Mypy. Yet another lesson to learn from TypeScript. |
Isn’t the idea of deleting attributes antithetical to static typing? If
I’m using mypy to check my code, deleting attributes is not something I
want to accommodate, especially at the detriment of a useful *typing*
feature like overriding an attribute with a property. In fact, it’s not
hard to imagine that deleting an attribute should be considered a type
error.
…On Mon, Jun 28, 2021 at 7:33 PM Luciano Ramalho ***@***.***> wrote:
Thanks for your insight, @erictraut <https://github.com/erictraut>.
That's yet another situation where "type safety" trumps established
practices in Python. It makes me think everyone would be happier if there
was a derivative language with static types—which was @JukkaL
<https://github.com/JukkaL>'s original idea for Mypy. Yet another lesson
to learn from TypeScript.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#4125 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAPOE4FJXR4ZQFCKQLMKX3TVEIENANCNFSM4D7NBVTQ>
.
|
Same with adding attributes at runtime, which is a feature, not a bug of Python and dynamic languages in general. But with time, the widespread, uncritical use of type hints will make those language features become taboo, and then you have—in effect—a different language defined by different usage reinforced by tooling. Which is why a derivative with a different name communicates better. TypeScript, not JavaScript with types bolted on. |
There's a workaround that works for both mypy and pyright if you're willing to use descriptors. Method follows typing advice from https://twitter.com/AdamChainz/status/1450044899093618688 from __future__ import annotations
from typing import cast, overload
class YIntProp(int):
@overload
def __get__(self, obj: None, objtype: None) -> YIntProp:
...
@overload
def __get__(self, obj: object, objtype: type[object]) -> int:
...
def __get__(
self, obj: object | None, objtype: type[object] | None = None
) -> YIntProp | int:
if obj is None:
return self
return cast(int, obj.__dict__["y"])
def __set__(self, obj: object, value: int) -> None:
obj.__dict__["y"] = value
class Foo:
def __init__(self) -> None:
self.x = 42
class Bar(Foo):
x = YIntProp()
def __init__(self) -> None:
self.y = 9000
a = Bar()
print(a.x)
reveal_type(a.x) # revealed as int |
mypy 0.730 is not compatible with Python 3.9, resulting in the following bogus error message: python/mypy#4125
Fixes #4125 Previously the code compared the original signatures for properties. Now we compare just the return types, similar to how we do it in `checkmember.py`. Note that we still only allow invariant overrides, which is stricter that for regular variables that where we allow (unsafe) covariance.
For example:
This should be allowed as it doesn't violate LSP as long as
x
implements both agetter
and asetter
of the correct type.The text was updated successfully, but these errors were encountered: