-
-
Notifications
You must be signed in to change notification settings - Fork 32.5k
gh-92810: Reduce memory usage by ABCMeta.__subclasscheck__ #131914
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
base: main
Are you sure you want to change the base?
Changes from 12 commits
675bec5
701ecc9
041f109
9bc4385
3d80b1e
b7603e0
dd0d18c
8d695fd
a2650b6
7afa5ea
57980d3
bbaf38a
6fc994d
b3b5895
69c5038
dc1b6d5
cd097ab
f3a21a7
e24e815
b723912
0295846
16f39bd
2dc6453
968766d
a6e4461
80d3281
ff38b9e
23df287
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -270,29 +270,100 @@ def x(self): | |
class C(metaclass=meta): | ||
pass | ||
|
||
def test_isinstance_direct_inheritance(self): | ||
class A(metaclass=abc_ABCMeta): | ||
pass | ||
class B(A): | ||
pass | ||
class C(A): | ||
pass | ||
a = A() | ||
dolfinus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
b = B() | ||
c = C() | ||
# trigger caching | ||
for _ in range(2): | ||
self.assertIsInstance(a, A) | ||
dolfinus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self.assertIsInstance(a, (A,)) | ||
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. AFAICT, tests are of the form:
How about having a small helper method: def check_isinstance(value, *classes):
for cls in classes:
self.assertIsInstance(value, cls)
self.assertIsInstance(value, (cls,)) It would make the code way less vertical. 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. Done. But I've added this helper only to new tests, existing ones are still using old form |
||
self.assertNotIsInstance(a, B) | ||
self.assertNotIsInstance(a, (B,)) | ||
self.assertNotIsInstance(a, C) | ||
self.assertNotIsInstance(a, (C,)) | ||
|
||
self.assertIsInstance(b, B) | ||
self.assertIsInstance(b, (B,)) | ||
self.assertIsInstance(b, A) | ||
self.assertIsInstance(b, (A,)) | ||
self.assertNotIsInstance(b, C) | ||
self.assertNotIsInstance(b, (C,)) | ||
|
||
self.assertIsInstance(c, C) | ||
self.assertIsInstance(c, (C,)) | ||
self.assertIsInstance(c, A) | ||
self.assertIsInstance(c, (A,)) | ||
self.assertNotIsInstance(c, B) | ||
self.assertNotIsInstance(c, (B,)) | ||
|
||
self.assertIsSubclass(B, A) | ||
self.assertIsSubclass(B, (A,)) | ||
self.assertIsSubclass(C, A) | ||
self.assertIsSubclass(C, (A,)) | ||
self.assertNotIsSubclass(B, C) | ||
self.assertNotIsSubclass(B, (C,)) | ||
self.assertNotIsSubclass(C, B) | ||
self.assertNotIsSubclass(C, (B,)) | ||
self.assertNotIsSubclass(A, B) | ||
self.assertNotIsSubclass(A, (B,)) | ||
self.assertNotIsSubclass(A, C) | ||
self.assertNotIsSubclass(A, (C,)) | ||
|
||
def test_registration_basics(self): | ||
class A(metaclass=abc_ABCMeta): | ||
pass | ||
class B(object): | ||
pass | ||
a = A() | ||
b = B() | ||
self.assertNotIsSubclass(B, A) | ||
self.assertNotIsSubclass(B, (A,)) | ||
self.assertNotIsInstance(b, A) | ||
self.assertNotIsInstance(b, (A,)) | ||
|
||
# trigger caching | ||
for _ in range(2): | ||
self.assertNotIsSubclass(B, A) | ||
self.assertNotIsSubclass(B, (A,)) | ||
self.assertNotIsInstance(b, A) | ||
self.assertNotIsInstance(b, (A,)) | ||
|
||
self.assertNotIsSubclass(A, B) | ||
self.assertNotIsSubclass(A, (B,)) | ||
self.assertNotIsInstance(a, B) | ||
self.assertNotIsInstance(a, (B,)) | ||
|
||
B1 = A.register(B) | ||
self.assertIsSubclass(B, A) | ||
self.assertIsSubclass(B, (A,)) | ||
self.assertIsInstance(b, A) | ||
self.assertIsInstance(b, (A,)) | ||
self.assertIs(B1, B) | ||
# trigger caching | ||
for _ in range(2): | ||
self.assertIsSubclass(B, A) | ||
self.assertIsSubclass(B, (A,)) | ||
self.assertIsInstance(b, A) | ||
self.assertIsInstance(b, (A,)) | ||
self.assertIs(B1, B) | ||
|
||
self.assertNotIsSubclass(A, B) | ||
self.assertNotIsSubclass(A, (B,)) | ||
self.assertNotIsInstance(a, B) | ||
self.assertNotIsInstance(a, (B,)) | ||
|
||
class C(B): | ||
pass | ||
c = C() | ||
self.assertIsSubclass(C, A) | ||
self.assertIsSubclass(C, (A,)) | ||
self.assertIsInstance(c, A) | ||
self.assertIsInstance(c, (A,)) | ||
# trigger caching | ||
for _ in range(2): | ||
self.assertIsSubclass(C, A) | ||
self.assertIsSubclass(C, (A,)) | ||
self.assertIsInstance(c, A) | ||
self.assertIsInstance(c, (A,)) | ||
|
||
self.assertNotIsSubclass(A, C) | ||
self.assertNotIsSubclass(A, (C,)) | ||
self.assertNotIsInstance(a, C) | ||
self.assertNotIsInstance(a, (C,)) | ||
|
||
def test_register_as_class_deco(self): | ||
class A(metaclass=abc_ABCMeta): | ||
|
@@ -377,39 +448,73 @@ class A(metaclass=abc_ABCMeta): | |
pass | ||
self.assertIsSubclass(A, A) | ||
self.assertIsSubclass(A, (A,)) | ||
|
||
class B(metaclass=abc_ABCMeta): | ||
pass | ||
self.assertNotIsSubclass(A, B) | ||
self.assertNotIsSubclass(A, (B,)) | ||
self.assertNotIsSubclass(B, A) | ||
self.assertNotIsSubclass(B, (A,)) | ||
|
||
class C(metaclass=abc_ABCMeta): | ||
pass | ||
A.register(B) | ||
class B1(B): | ||
pass | ||
self.assertIsSubclass(B1, A) | ||
self.assertIsSubclass(B1, (A,)) | ||
# trigger caching | ||
for _ in range(2): | ||
self.assertIsSubclass(B1, A) | ||
self.assertIsSubclass(B1, (A,)) | ||
|
||
class C1(C): | ||
pass | ||
B1.register(C1) | ||
self.assertNotIsSubclass(C, B) | ||
self.assertNotIsSubclass(C, (B,)) | ||
self.assertNotIsSubclass(C, B1) | ||
self.assertNotIsSubclass(C, (B1,)) | ||
self.assertIsSubclass(C1, A) | ||
self.assertIsSubclass(C1, (A,)) | ||
self.assertIsSubclass(C1, B) | ||
self.assertIsSubclass(C1, (B,)) | ||
self.assertIsSubclass(C1, B1) | ||
self.assertIsSubclass(C1, (B1,)) | ||
# trigger caching | ||
for _ in range(2): | ||
self.assertNotIsSubclass(C, B) | ||
self.assertNotIsSubclass(C, (B,)) | ||
self.assertNotIsSubclass(C, B1) | ||
self.assertNotIsSubclass(C, (B1,)) | ||
self.assertIsSubclass(C1, A) | ||
self.assertIsSubclass(C1, (A,)) | ||
self.assertIsSubclass(C1, B) | ||
self.assertIsSubclass(C1, (B,)) | ||
self.assertIsSubclass(C1, B1) | ||
self.assertIsSubclass(C1, (B1,)) | ||
|
||
C1.register(int) | ||
class MyInt(int): | ||
pass | ||
self.assertIsSubclass(MyInt, A) | ||
self.assertIsSubclass(MyInt, (A,)) | ||
self.assertIsInstance(42, A) | ||
self.assertIsInstance(42, (A,)) | ||
# trigger caching | ||
for _ in range(2): | ||
self.assertIsSubclass(MyInt, A) | ||
self.assertIsSubclass(MyInt, (A,)) | ||
self.assertIsInstance(42, A) | ||
self.assertIsInstance(42, (A,)) | ||
|
||
def test_custom_subclasses(self): | ||
class A: pass | ||
class B: pass | ||
|
||
class Parent1(metaclass=abc_ABCMeta): | ||
@classmethod | ||
def __subclasses__(cls): | ||
return [A] | ||
|
||
class Parent2(metaclass=abc_ABCMeta): | ||
__subclasses__ = lambda: [A] | ||
|
||
# trigger caching | ||
for _ in range(2): | ||
self.assertIsInstance(A(), Parent1) | ||
self.assertIsSubclass(A, Parent1) | ||
self.assertNotIsInstance(B(), Parent1) | ||
self.assertNotIsSubclass(B, Parent1) | ||
|
||
self.assertIsInstance(A(), Parent2) | ||
self.assertIsSubclass(A, Parent2) | ||
self.assertNotIsInstance(B(), Parent2) | ||
self.assertNotIsSubclass(B, Parent2) | ||
|
||
def test_issubclass_bad_arguments(self): | ||
class A(metaclass=abc_ABCMeta): | ||
|
@@ -522,7 +627,6 @@ def foo(self): | |
self.assertEqual(A.__abstractmethods__, set()) | ||
A() | ||
|
||
|
||
def test_update_new_abstractmethods(self): | ||
class A(metaclass=abc_ABCMeta): | ||
@abc.abstractmethod | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Reduce memory usage by :meth:`~type.__subclasscheck__` | ||
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. I think this can be also added in What's New with concrete numbers (I think we have an optimizations section). The NEWS entry should be in 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.
Is there an example how this entry should be added? I'm new to CPython sources, and there can be some rules for this I'm not familiar with. 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. Yes: open Doc/whatsnew/3.15.rst, and check if we have an optimization section in the document. Then just write something like: Optimizations
=============
abc
---
* Reduce memory usage of `issubclass` checks for classes inheriting abstract classes.
(Contributed by YOUR_NAME_OR_GITHUB_HANDLE in :gh:`92810`.) You can add some relevant numbers if you want. 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. This requires rebasing or merging the main branch. Which option is preferred? 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. Just merge the main branch by hitting the "update" button and pull the remote changes (that way you can be sure that your local branch won't be desynced). We squash-merge commits at the end and we rewrite the commit message so it doesn't matter how messy your commit history is. It's better not to force-push because it breaks incremental GitHub reviews (there are only specific cases when you can force push but generally, we recommend avoiding force-pushing). 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. Added a What's New entry, but without any numbers, as they depend on the size of class tree |
||
for :class:`abc.ABCMeta` and large class trees | ||
dolfinus marked this conversation as resolved.
Show resolved
Hide resolved
|
Uh oh!
There was an error while loading. Please reload this page.