bpo-32226: Implementation of PEP 560 (core components) (#4732) · pythoncapi/cpython@2b5fd1e · GitHub
Skip to content

Commit 2b5fd1e

Browse files
authored
bpo-32226: Implementation of PEP 560 (core components) (python#4732)
This part of the PEP implementation adds support for __mro_entries__ and __class_getitem__ by updating __build_class__ and PyObject_GetItem.
1 parent 15a8728 commit 2b5fd1e

7 files changed

Lines changed: 492 additions & 5 deletions

File tree

Lib/test/test_genericclass.py

Lines changed: 252 additions & 0 deletions

Lib/test/test_types.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,68 @@ def func(ns):
844844
self.assertEqual(C.y, 1)
845845
self.assertEqual(C.z, 2)
846846

847+
def test_new_class_with_mro_entry(self):
848+
class A: pass
849+
class C:
850+
def __mro_entries__(self, bases):
851+
return (A,)
852+
c = C()
853+
D = types.new_class('D', (c,), {})
854+
self.assertEqual(D.__bases__, (A,))
855+
self.assertEqual(D.__orig_bases__, (c,))
856+
self.assertEqual(D.__mro__, (D, A, object))
857+
858+
def test_new_class_with_mro_entry_none(self):
859+
class A: pass
860+
class B: pass
861+
class C:
862+
def __mro_entries__(self, bases):
863+
return ()
864+
c = C()
865+
D = types.new_class('D', (A, c, B), {})
866+
self.assertEqual(D.__bases__, (A, B))
867+
self.assertEqual(D.__orig_bases__, (A, c, B))
868+
self.assertEqual(D.__mro__, (D, A, B, object))
869+
870+
def test_new_class_with_mro_entry_error(self):
871+
class A: pass
872+
class C:
873+
def __mro_entries__(self, bases):
874+
return A
875+
c = C()
876+
with self.assertRaises(TypeError):
877+
types.new_class('D', (c,), {})
878+
879+
def test_new_class_with_mro_entry_multiple(self):
880+
class A1: pass
881+
class A2: pass
882+
class B1: pass
883+
class B2: pass
884+
class A:
885+
def __mro_entries__(self, bases):
886+
return (A1, A2)
887+
class B:
888+
def __mro_entries__(self, bases):
889+
return (B1, B2)
890+
D = types.new_class('D', (A(), B()), {})
891+
self.assertEqual(D.__bases__, (A1, A2, B1, B2))
892+
893+
def test_new_class_with_mro_entry_multiple_2(self):
894+
class A1: pass
895+
class A2: pass
896+
class A3: pass
897+
class B1: pass
898+
class B2: pass
899+
class A:
900+
def __mro_entries__(self, bases):
901+
return (A1, A2, A3)
902+
class B:
903+
def __mro_entries__(self, bases):
904+
return (B1, B2)
905+
class C: pass
906+
D = types.new_class('D', (A(), C, B()), {})
907+
self.assertEqual(D.__bases__, (A1, A2, A3, C, B1, B2))
908+
847909
# Many of the following tests are derived from test_descr.py
848910
def test_prepare_class(self):
849911
# Basic test of metaclass derivation
@@ -886,6 +948,28 @@ def __prepare__(*args):
886948
class Bar(metaclass=BadMeta()):
887949
pass
888950

951+
def test_resolve_bases(self):
952+
class A: pass
953+
class B: pass
954+
class C:
955+
def __mro_entries__(self, bases):
956+
if A in bases:
957+
return ()
958+
return (A,)
959+
c = C()
960+
self.assertEqual(types.resolve_bases(()), ())
961+
self.assertEqual(types.resolve_bases((c,)), (A,))
962+
self.assertEqual(types.resolve_bases((C,)), (C,))
963+
self.assertEqual(types.resolve_bases((A, C)), (A, C))
964+
self.assertEqual(types.resolve_bases((c, A)), (A,))
965+
self.assertEqual(types.resolve_bases((A, c)), (A,))
966+
x = (A,)
967+
y = (C,)
968+
z = (A, C)
969+
t = (A, C, B)
970+
for bases in [x, y, z, t]:
971+
self.assertIs(types.resolve_bases(bases), bases)
972+
889973
def test_metaclass_derivation(self):
890974
# issue1294232: correct metaclass calculation
891975
new_calls = [] # to check the order of __new__ calls

Lib/types.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,34 @@ def _m(self): pass
6060
# Provide a PEP 3115 compliant mechanism for class creation
6161
def new_class(name, bases=(), kwds=None, exec_body=None):
6262
"""Create a class object dynamically using the appropriate metaclass."""
63-
meta, ns, kwds = prepare_class(name, bases, kwds)
63+
resolved_bases = resolve_bases(bases)
64+
meta, ns, kwds = prepare_class(name, resolved_bases, kwds)
6465
if exec_body is not None:
6566
exec_body(ns)
66-
return meta(name, bases, ns, **kwds)
67+
if resolved_bases is not bases:
68+
ns['__orig_bases__'] = bases
69+
return meta(name, resolved_bases, ns, **kwds)
70+
71+
def resolve_bases(bases):
72+
"""Resolve MRO entries dynamically as specified by PEP 560."""
73+
new_bases = list(bases)
74+
updated = False
75+
shift = 0
76+
for i, base in enumerate(bases):
77+
if isinstance(base, type):
78+
continue
79+
if not hasattr(base, "__mro_entries__"):
80+
continue
81+
new_base = base.__mro_entries__(bases)
82+
updated = True
83+
if not isinstance(new_base, tuple):
84+
raise TypeError("__mro_entries__ must return a tuple")
85+
else:
86+
new_bases[i+shift:i+shift+1] = new_base
87+
shift += len(new_base) - 1
88+
if not updated:
89+
return bases
90+
return tuple(new_bases)
6791

6892
def prepare_class(name, bases=(), kwds=None):
6993
"""Call the __prepare__ method of the appropriate metaclass.
Lines changed: 2 additions & 0 deletions

0 commit comments

Comments
 (0)