Skip to content
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

Strange error when declaring @final property #15325

Open
tmke8 opened this issue May 30, 2023 · 3 comments
Open

Strange error when declaring @final property #15325

tmke8 opened this issue May 30, 2023 · 3 comments
Labels
bug mypy got something wrong

Comments

@tmke8
Copy link
Contributor

tmke8 commented May 30, 2023

Bug Report

Consider this code:

from typing import final

class A:
    def __init__(self):
        self._x = 4

    @property
    @final  # E: @final should be applied only to overload implementation
    def x(self) -> int:
        return self._x

    @x.setter
    def x(self, value) -> None:
        self._x = value
        
        
class B(A):

    @property  # E: Cannot override final attribute "x" (previously declared in base class "A")
    def x(self) -> int:
        return self._x

    @x.setter
    def x(self, value) -> None:
        self._x = value

https://mypy-play.net/?mypy=latest&python=3.11&gist=a4a1981a3db4ac546b9947bb344a5fd5

Mypy complains that @final is not applied to the "overload implementation", but the property is still successfully declared final.

I tried moving the @final decorator to the setter – in that case, mypy's first error goes away, but then also the second error vanishes, meaning that x wasn't actually marked as final.

(I also tried moving @final above @property but that produces the same result.)

Environment:
See mypy-play link above.

@tmke8 tmke8 added the bug mypy got something wrong label May 30, 2023
@sobolevn
Copy link
Member

sobolevn commented Jun 4, 2023

So, you want to be able to have separate final properties and their setters? Am I correct?

@tmke8
Copy link
Contributor Author

tmke8 commented Jun 4, 2023

Hmm... I wanted to declare the implementation of the settable property final – meaning that subclasses shouldn't be allowed to override the getter or the setter. Do you think this doesn't make sense because Final attributes aren't settable?

EDIT: so, to clarify, I wanted to make getter and setter both final, such that subclassses can override neither, but just making the getter final would also help, I guess

@Prometheus3375
Copy link

Prometheus3375 commented Aug 17, 2023

It will be interesting to make getters final, but allow overwrite setters/getters. Or more generally, to make only a subset of (getter, setter, deleter) final.

Usually, overwriting a subset of property accessors in a subclass is done via referencing to superclass property. But this is not currently allowed by mypy:

class A:
    @property
    def a(self) -> int: return 1
    

class B(A):
    @A.a.setter # error: "Callable[[A], int]" has no attribute "setter"  [attr-defined]
    def a(self, value): pass
class A:
    @property
    def a(self) -> int: return 1
    
    @a.setter
    def a(self, value): pass

    @a.deleter
    def a(self): pass
    

class B(A):
    @A.a.setter # error: overloaded function has no attribute "setter"  [attr-defined]
    def a(self, value): pass

Returning to completely final properties. When a function name is repeated, mypy considers that it is being overloaded. In the example of @tmke8 mypy emits error at final, because x is redefined later.

I tried to bypass this error via different names for setter and deleter, but ended up with another error:

from typing import final


class A:
    @property
    @final
    def a(self) -> int: return 1
    
    @a.setter  # error: "Callable[[A], int]" has no attribute "setter"  [attr-defined]
    def a_setter(self, value): pass

    @a_setter.deleter
    def a_deleter(self): pass

    a = a_deleter
    del a_deleter, a_setter
    

class B(A):
    @property  # error: Cannot override final attribute "a" (previously declared in base class "A")  [misc]
    def a(self) -> int: return 2
    
    @a.setter
    def a(self, value): pass

Then I tried to use Final, but this made mypy to stop considering a as property.

from typing import final, Final, reveal_type


class A:
    def a_get(self) -> int: return 1
    
    def a_set(self, value): pass

    def a_del(self): pass

    a: Final[property] = property(a_get, a_set, a_del)
    del a_get, a_set, a_del
    
    
obj = A()
reveal_type(obj.a)  # note: Revealed type is "Any"
a.a = 1  # error: Name "a" is not defined  [name-defined]
    

class B(A):
    @property  # error: Cannot override final attribute "a" (previously declared in base class "A")  [misc]
    # error: Signature of "a" incompatible with supertype "A"  [override]
    def a(self) -> int: return 2
    
    @a.setter
    def a(self, value): pass

All in all, final poorly works with properties:

  1. Adding final after property to settable/deletable property emits error that final is used on "overloaded" version of function.
  2. Adding final after setter or deleter does not produce errors if property got overwritten in a subclass.

Also my examples shows that properties themselves are poorly supported:

  1. Taking property from superclass to overwrite/add only a part of accessors emits an error.
  2. Using another name for setter/deleter (this also defines another property) emits an error.
  3. Properties set via assignment are not considered as properties. Making properties via assignment is useful when you want to make only settable properties. I assume to be able to consider them correctly, properties should be generic [GetReturnType, SetValueType].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

3 participants