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

Overloading an abstract attribute with a Final one #14863

Open
ydirson opened this issue Mar 9, 2023 · 6 comments
Open

Overloading an abstract attribute with a Final one #14863

ydirson opened this issue Mar 9, 2023 · 6 comments
Labels
bug mypy got something wrong topic-final PEP 591 topic-inheritance Inheritance and incompatible overrides

Comments

@ydirson
Copy link

ydirson commented Mar 9, 2023

In the following code it would be useful to mark toto as final to make sure no code modifies it by error. However this triggers Cannot override writable attribute "toto" with a final one

from typing import Final

class Base:
    toto: str = NotImplemented
    def access_toto(self) -> str:
        return self.toto

class CmdA(Base):
    toto: Final[str] = "toto"

In fact I tend to agree with that error, in that what I'd really like to do is to annotate toto as "abstract final", which is not possible either as the use of toto: Final[str] triggers Final name must be initialized with a value, and toto: Final[str] = NotImplemented still gets the
Cannot override final attribute "toto" (previously declared in base class "Base") the previous attempt also showed.

  • Mypy version used: 1.1.1, 0.812
  • Python version used: 3.9.2
@ydirson ydirson added the bug mypy got something wrong label Mar 9, 2023
@ikonst
Copy link
Contributor

ikonst commented Mar 9, 2023

If it's final, it means it cannot be changed

class Base:
    toto: str = NotImplemented

    def change_toto(self) -> None:
        self.toto = "spam"

class CmdA(Base):
    toto: Final[str] = "toto"
 
 
 c = CmdA()
 c.change_toto()  # changes `c.toto` even though it's final :(

What are you trying to achieve with Final?

@ydirson
Copy link
Author

ydirson commented Mar 9, 2023

@ikonst you're right, base.toto should not be mutable, this was not the best of those attempts :)
What I'd like is to ensure all concrete subclasses of Base have final strings for their .toto.

@A5rocks
Copy link
Contributor

A5rocks commented Mar 10, 2023

This doesn't require all subclasses to have final strings (I'm not sure how you could ensure that) but you can make your original example work with a readonly property!

from typing import Final
from abc import ABC, abstractmethod

class Base(ABC):
    @property
    @abstractmethod
    def toto(self) -> str:
        ...

    def access_toto(self) -> str:
        return self.toto

class CmdA(Base):
    toto: Final[str] = "toto"

@AlexWaygood AlexWaygood added topic-inheritance Inheritance and incompatible overrides topic-final PEP 591 labels Mar 10, 2023
@ikonst
Copy link
Contributor

ikonst commented Mar 10, 2023

Should we close this, or do we consider this a usability problem with the current error "Cannot override writable attribute "{name}" with a final one'"?

For example, do we need treatment similar to this?

mypy/mypy/messages.py

Lines 1194 to 1205 in 267d376

self.note(
"This violates the Liskov substitution principle",
context,
code=codes.OVERRIDE,
secondary_context=secondary_context,
)
self.note(
"See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides",
context,
code=codes.OVERRIDE,
secondary_context=secondary_context,
)

@ydirson
Copy link
Author

ydirson commented Mar 11, 2023

My original point was more about:

  1. specifying an attribute in parent class without implying it would be writable, so that a derived class could refine it as being final
  2. going one step further and specifying that this attribute should not be writable

@A5rocks yes this answers point 1, though I prefer avoiding ABC when stuff like ABC.register() is not needed (it's a PITA when you start to have other metaclasses), so I prefer this slightly modified version:

from typing import Final

class Base:
    @property
    def toto(self) -> str:
        raise NotImplementedError()

    def access_toto(self) -> str:
        return self.toto

class CmdA(Base):
    toto: Final[str] = "toto"

For point 2 I still don't have any idea. I'd think the final annotation in superclass without an assignment could have a use here, it would just declare an "abstract attribute" -- assigning NotImplemented could likely not be interpreted in another way, but going that way creates a special case which the first option avoids. Would there be any problem going that way ?

@ikonst: yes I'd tend to think similar treatment would help, but it's not the whole of this ticket ;)

@A5rocks
Copy link
Contributor

A5rocks commented Jun 21, 2023

Hm I just re-read this and I think typing-wise a property setter that has a NoReturn return type might work for point 2, but I'm not sure if mypy supports that and I'm on mobile so can't test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-final PEP 591 topic-inheritance Inheritance and incompatible overrides
Projects
None yet
Development

No branches or pull requests

4 participants