-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Enums with annotations and no values are fine to be subclassed #11579
Conversation
def method(self) -> int: pass | ||
|
||
class B(A): | ||
x = 1 # E: Cannot override writable attribute "x" with a final one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be fixed. I am working on it right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But, on the other hand. Looks like it is the same for regular classes:
from typing import Final
class A:
x: int
y: int
class B(A):
x: Final = 1
y: Final = 2
# out/ex.py:8: error: Cannot override writable attribute "x" with a final one
# out/ex.py:9: error: Cannot override writable attribute "y" with a final one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look like PEP-591 prohibits Final
on a subclass, so it looks like it's a logic error that Mypy does not allow subclassing like you did ^^^.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though this is pretty clear why this is the case (and makes sense)
Lines 2505 to 2525 in d7c4e69
def check_if_final_var_override_writable(self, | |
name: str, | |
base_node: | |
Optional[Node], | |
ctx: Context) -> None: | |
"""Check that a final variable doesn't override writeable attribute. | |
This is done to prevent situations like this: | |
class C: | |
attr = 1 | |
class D(C): | |
attr: Final = 2 | |
x: C = D() | |
x.attr = 3 # Oops! | |
""" | |
writable = True | |
if base_node: | |
writable = self.is_writable_attribute(base_node) | |
if writable: | |
self.msg.final_cant_override_writable(name, ctx) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, let's make this a subject for a next PR. This would require some extra work. And might have unwanted consequences.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. This is intended. I don't remember why we didn't mention it in the PEP (it may be we indeed just wanted to be terse).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should still be allowed if the variable is not initialized yet, right? Based on the PEP it looks like it would be expected to be initialized in the __init__
, but it sounds like there should be a special case where the class is basically completely abstract.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it shouldn't. Consider this
class A:
x: int
class B(A):
x: Final = 1
# somewhere in modules that depend on the above, and therefore are being processed later
def set_x(a: A) -> None:
a.x = 42
set_x(B()) # ???
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense in general but I wonder if it makes sense to special case enums here. We may need more details from the use case in #11578.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The example given here #11578 (comment) is basically how the code is used. The only thing that the base class does is define a __new__
that stores the actual values that all the subclasses provide (additional constants like defined in this example https://docs.python.org/3/library/enum.html#planet).
The interpreter doesn't complain about subclassing an enum, and as I realize now that's because there are no values defined on the base class. I do agree enums probably need a special case because the class that does define enum values is the concrete class that is also final whereas the example above the interpreter does not effectively define final like it would for enums.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me, but this still doesn't fix the second two errors from #11578 (comment), right?
mypy/semanal.py
Outdated
for node in base.type.defn.defs.body: | ||
if isinstance(node, (FuncBase, Decorator)): | ||
continue # A method | ||
if isinstance(node, AssignmentStmt) and isinstance(node.rvalue, TempNode): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this also check node.rvalue.no_rhs
like
Line 2412 in 7f0ad94
if (isinstance(s.rvalue, TempNode) and s.rvalue.no_rhs and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but this still doesn't fix the second two errors from #11578 (comment), right?
Yes, see this comment: #11579 (comment)
should this also check node.rvalue.no_rhs like
Done! Thanks.
I probably also have to check that values are only required for Because we can use code like class Encoding(Enum):
PEM: str
DER: str
OpenSSH: str
Raw: str
X962: str
SMIME: str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to merge this now so that we can include it in the 0.930 release. It would be great to have an additional test cases in a follow-up PR that mirrors the case in #11702 (assignment to an attribute via self
without a type annotation in class body, and __new__
).
Thanks! I will add new test cases shortly 👍 |
I am using AST here, because
Var
does not seem to have information about having an explicit value.I left a TODO item for it. Should we add this to
Var
?Closes #11578