Repository navigation
Mypy disallows overriding an attribute with a property #4125
Description
Activity
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.
Reacted by Szymon Zmilczak, Serhii Tereshchenko, Blake Williams, Nate McMaster, Garrett Reynolds, Redoubts, Michael Niklas , Brett Jackson and Maxim Egorushkin- addedbugmypy got something wrongmypy got something wrong
on Oct 16, 2017 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
@x.deleter.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.
Reacted by Roy Williams, Lennart Regebro, Fabio Zadrozny, Blake Williams, Max Kühn, Fran Hrženjak, Alex Waygood, Michael Scott Asato Cuthbert, Higor Carmanini and Thibaut DecombeTrue, 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
property()as a function: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)
foo.py:9: error: Incompatible types in assignment (expression has type "property", base class "Base" defined the type as "bool")Reacted by Nate McMaster and Jason R. CoombsIn
mypy==0.720, if I slightly modify the example in #4125 (comment) (above) based on the documentation's guidance that:Explicitly including a protocol as a base class is also a way of documenting that your class implements a particular protocol, and it forces mypy to verify that your class implementation is actually compatible with the protocol.
...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.
Reacted by ihor-nahuliak, lovetoburnswhen and RedoubtsSetting priority to high, see duplicate issue #6759 (comment) for motivation.
Reacted by Erick, Germano Gabbianelli, Brenton Partridge and Blake Williams- addedfalse-positivemypy gave an error on correct codemypy gave an error on correct coderefactoringChanging mypy's internalsChanging mypy's internalsand removed
on Aug 16, 2019 1 remaining item
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
AbstractClass.foois defined with property as a decorator, it works fine.- added a commit that references this issue
on Nov 1, 2020 True, but overriding an attribute with a property is also not so common, so a reminder that it's not complete might be useful.
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:
The crucial importance of properties is that their existence makes it perfectly safe (and indeed advisable) for you to expose public data attributes as part of your class’s public interface. Should it ever become necessary, in future versions of your class or other classes that need to be polymorphic to it, to have some code executed when the attribute is referenced, rebound, or unbound, you know you will be able to change the plain attribute into a property and get the desired effect without any impact on any other code that uses your class (AKA “client code”). This lets you avoid goofy idioms, such as accessor and mutator methods, required by OO languages that lack properties or equivalent machinery.
some_instance.widget_count += 1rather than being forced into contorted nests of accessors and mutators such as:
some_instance.set_widget_count(some_instance.get_widget_count() + 1)Reacted by Jelle Zijlstra, Hanusz Leszek, Daniel Miranda, Michael Scott Asato Cuthbert, Constantine Peresypkin, Maxim Egorushkin and hydrargyrumFor 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
delbut the property would not. From a type safety perspective, it is not safe to substitute one for the other.Reacted by PyprohlyThanks 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.
Reacted by Nate McMaster, Doğukan Çağatay, Robert Grant, Dan Strokirk, Oleg Navolotsky, Constantine Peresypkin, Maxim Egorushkin and hydrargyrum- 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> .Reacted by Luciano Ramalho, Juan, Hanusz Leszek, Sam Mason, Soren Bjornstad, Alex Waygood, Anton Agestam, Juan Rocamonde, Marius Gedminas, Oleg Navolotsky and 3 more
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.
Reacted by Michael Scott Asato Cuthbert, Dan Strokirk, Kiran Jonnalagadda, Robert Smallshire, Oleg Navolotsky, Constantine Peresypkin, Maxim Egorushkin and hydrargyrum- added a commit that references this issue
on Oct 17, 2021 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
Reacted by Adam Johnson and Greg Werbin- addedtopic-descriptorsProperties, class vs. instance attributesProperties, class vs. instance attributes
on Mar 27, 2022 - added a commit that references this issue
on Aug 22, 2022 Which is why a derivative with a different name communicates better. TypeScript, not JavaScript with types bolted on.
There's one small problem with that. MSFT falsely claims that TS is a "superset" of JS.
Which is a blatant lie, as things that can be done in JS cannot be done in TS.
Same thing here:mypysupports a very limited subset of python.Reacted by hydrargyrum
For example:
This should be allowed as it doesn't violate LSP as long as
ximplements both agetterand asetterof the correct type.