-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
bpo-44975: [typing] Support issubclass for ClassVar data members #27883
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
Changes from 1 commit
a613af6
f045d2e
58c69e1
2ed2ee5
c842370
f44d3b6
9278a34
ba57bb1
43ec2ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1631,8 +1631,13 @@ class C: pass | |
|
||
class D: | ||
x = 1 | ||
|
||
class E: | ||
x = 2 | ||
|
||
self.assertNotIsSubclass(C, P) | ||
self.assertIsSubclass(D, P) | ||
Fidget-Spinner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.assertIsSubclass(E, P) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's also test these classes: class G:
x: ClassVar[int] = 1 class H:
x: 'ClassVar[int]' = 1 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These aren't testing more code paths, the code doesn't look at the annotations of the first class argument in |
||
# String annotations (forward references). | ||
@runtime_checkable | ||
|
@@ -1645,6 +1650,7 @@ class D: | |
x = 1 | ||
y = 2 | ||
z = 3 | ||
|
||
self.assertNotIsSubclass(C, P) | ||
self.assertIsSubclass(D, P) | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -1404,21 +1404,26 @@ def _get_protocol_attrs(cls): | |||||
attrs.add(attr) | ||||||
return attrs | ||||||
|
||||||
_classvar_prefixes = ("typing.ClassVar[", "t.ClassVar[", "ClassVar[") | ||||||
_CLASSVAR_PREFIXES = ("typing.ClassVar", "t.ClassVar", "ClassVar") | ||||||
|
||||||
def _is_callable_or_classvar_members_only(cls): | ||||||
def _is_callable_or_classvar_members_only(cls, instance=None, verify_classvar_values=False): | ||||||
attr_names = _get_protocol_attrs(cls) | ||||||
annotations = getattr(cls, '__annotations__', {}) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about using It will allow having @runtime_checkable
class P(Protocol):
x: 'ClassVar[int]' = 1
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question! I'd considered that too but decided against it for two main reasons:
Consider the following: # foo.py
from __future__ import annotations
from typing import *
@runtime_checkable
class X(Protocol):
x: ClassVar[int] = 1
y: SomeUndeclaredType = None # bar.py
from .foo import X
class Y: ...
# Error! get_type_hints cannot resolve 'ClassVar' and 'SomeUndeclaredType'
issubclass(X, Y) Nonetheless, your suggestion has reminded me of a basic workaround for string annotations. Thanks. |
||||||
# PEP 544 prohibits using issubclass() with protocols that have non-method members. | ||||||
# bpo-44975: Relaxing that restriction to allow for runtime-checkable | ||||||
# protocols with class variables since those should be available at class | ||||||
# definition time. | ||||||
for attr_name in attr_names: | ||||||
attr = getattr(cls, attr_name, None) | ||||||
# Method-like. | ||||||
if callable(attr): | ||||||
continue | ||||||
annotation = annotations.get(attr_name) | ||||||
# ClassVar member | ||||||
if getattr(annotation, '__origin__', None) is ClassVar: | ||||||
continue | ||||||
# String annotations (forward references). | ||||||
if isinstance(annotation, str) and annotation.startswith(_classvar_prefixes): | ||||||
# ClassVar string annotations (forward references). | ||||||
if isinstance(annotation, str) and annotation.startswith(_CLASSVAR_PREFIXES): | ||||||
continue | ||||||
return False | ||||||
return True | ||||||
|
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.
Let's also add a case with the same class prop, but different value. Example:
Because right now all names / values always match. It is not clear whether value is a part of the protocol or not.
Uh oh!
There was an error while loading. Please reload this page.
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.
Agreed.
It shouldn't be. IMO, we should only care about the "shape". Trying to runtime-check the value is difficult and should be left to third-party libs to do.Nevermind, I changed my mind. Let's do it!