Skip to content

Commit d78448e

Browse files
committed
Issue #27366: Implement PEP 487
- __init_subclass__ called when new subclasses defined - __set_name__ called when descriptors are part of a class definition
1 parent f6daa69 commit d78448e

File tree

9 files changed

+411
-24
lines changed

9 files changed

+411
-24
lines changed

Doc/reference/datamodel.rst

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1492,6 +1492,12 @@ class' :attr:`~object.__dict__`.
14921492
Called to delete the attribute on an instance *instance* of the owner class.
14931493

14941494

1495+
.. method:: object.__set_name__(self, owner, name)
1496+
1497+
Called at the time the owning class *owner* is created. The
1498+
descriptor has been assigned to *name*.
1499+
1500+
14951501
The attribute :attr:`__objclass__` is interpreted by the :mod:`inspect` module
14961502
as specifying the class where this object was defined (setting this
14971503
appropriately can assist in runtime introspection of dynamic class attributes).
@@ -1629,11 +1635,46 @@ Notes on using *__slots__*
16291635
* *__class__* assignment works only if both classes have the same *__slots__*.
16301636

16311637

1632-
.. _metaclasses:
1638+
.. _class-customization:
16331639

16341640
Customizing class creation
16351641
--------------------------
16361642

1643+
Whenever a class inherits from another class, *__init_subclass__* is
1644+
called on that class. This way, it is possible to write classes which
1645+
change the behavior of subclasses. This is closely related to class
1646+
decorators, but where class decorators only affect the specific class they're
1647+
applied to, ``__init_subclass__`` solely applies to future subclasses of the
1648+
class defining the method.
1649+
1650+
.. classmethod:: object.__init_subclass__(cls)
1651+
This method is called whenever the containing class is subclassed.
1652+
*cls* is then the new subclass. If defined as a normal instance method,
1653+
this method is implicitly converted to a class method.
1654+
1655+
Keyword arguments which are given to a new class are passed to
1656+
the parent's class ``__init_subclass__``. For compatibility with
1657+
other classes using ``__init_subclass__``, one should take out the
1658+
needed keyword arguments and pass the others over to the base
1659+
class, as in::
1660+
1661+
class Philosopher:
1662+
def __init_subclass__(cls, default_name, **kwargs):
1663+
super().__init_subclass__(**kwargs)
1664+
cls.default_name = default_name
1665+
1666+
class AustralianPhilosopher(Philosopher, default_name="Bruce"):
1667+
pass
1668+
1669+
The default implementation ``object.__init_subclass__`` does
1670+
nothing, but raises an error if it is called with any arguments.
1671+
1672+
1673+
.. _metaclasses:
1674+
1675+
Metaclasses
1676+
^^^^^^^^^^^
1677+
16371678
By default, classes are constructed using :func:`type`. The class body is
16381679
executed in a new namespace and the class name is bound locally to the
16391680
result of ``type(name, bases, namespace)``.

Doc/whatsnew/3.6.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,26 @@ evaluated at run time, and then formatted using the :func:`format` protocol.
110110
See :pep:`498` and the main documentation at :ref:`f-strings`.
111111

112112

113+
PEP 487: Simpler customization of class creation
114+
------------------------------------------------
115+
116+
Upon subclassing a class, the ``__init_subclass__`` classmethod (if defined) is
117+
called on the base class. This makes it straightforward to write classes that
118+
customize initialization of future subclasses without introducing the
119+
complexity of a full custom metaclass.
120+
121+
The descriptor protocol has also been expanded to include a new optional method,
122+
``__set_name__``. Whenever a new class is defined, the new method will be called
123+
on all descriptors included in the definition, providing them with a reference
124+
to the class being defined and the name given to the descriptor within the
125+
class namespace.
126+
127+
Also see :pep:`487` and the updated class customization documentation at
128+
:ref:`class-customization` and :ref:`descriptors`.
129+
130+
(Contributed by Martin Teichmann in :issue:`27366`)
131+
132+
113133
PYTHONMALLOC environment variable
114134
---------------------------------
115135

Lib/test/test_builtin.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1699,21 +1699,11 @@ def ham(self):
16991699
self.assertEqual(x.spam(), 'spam42')
17001700
self.assertEqual(x.to_bytes(2, 'little'), b'\x2a\x00')
17011701

1702-
def test_type_new_keywords(self):
1703-
class B:
1704-
def ham(self):
1705-
return 'ham%d' % self
1706-
C = type.__new__(type,
1707-
name='C',
1708-
bases=(B, int),
1709-
dict={'spam': lambda self: 'spam%s' % self})
1710-
self.assertEqual(C.__name__, 'C')
1711-
self.assertEqual(C.__qualname__, 'C')
1712-
self.assertEqual(C.__module__, __name__)
1713-
self.assertEqual(C.__bases__, (B, int))
1714-
self.assertIs(C.__base__, int)
1715-
self.assertIn('spam', C.__dict__)
1716-
self.assertNotIn('ham', C.__dict__)
1702+
def test_type_nokwargs(self):
1703+
with self.assertRaises(TypeError):
1704+
type('a', (), {}, x=5)
1705+
with self.assertRaises(TypeError):
1706+
type('a', (), dict={})
17171707

17181708
def test_type_name(self):
17191709
for name in 'A', '\xc4', '\U0001f40d', 'B.A', '42', '':

Lib/test/test_descrtut.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ def merge(self, other):
182182
'__iadd__',
183183
'__imul__',
184184
'__init__',
185+
'__init_subclass__',
185186
'__iter__',
186187
'__le__',
187188
'__len__',

Lib/test/test_pydoc.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,8 +638,9 @@ def method_returning_true(self):
638638
del expected['__doc__']
639639
del expected['__class__']
640640
# inspect resolves descriptors on type into methods, but vars doesn't,
641-
# so we need to update __subclasshook__.
641+
# so we need to update __subclasshook__ and __init_subclass__.
642642
expected['__subclasshook__'] = TestClass.__subclasshook__
643+
expected['__init_subclass__'] = TestClass.__init_subclass__
643644

644645
methods = pydoc.allmethods(TestClass)
645646
self.assertDictEqual(methods, expected)

Lib/test/test_subclassinit.py

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
from unittest import TestCase, main
2+
import sys
3+
import types
4+
5+
6+
class Test(TestCase):
7+
def test_init_subclass(self):
8+
class A(object):
9+
initialized = False
10+
11+
def __init_subclass__(cls):
12+
super().__init_subclass__()
13+
cls.initialized = True
14+
15+
class B(A):
16+
pass
17+
18+
self.assertFalse(A.initialized)
19+
self.assertTrue(B.initialized)
20+
21+
def test_init_subclass_dict(self):
22+
class A(dict, object):
23+
initialized = False
24+
25+
def __init_subclass__(cls):
26+
super().__init_subclass__()
27+
cls.initialized = True
28+
29+
class B(A):
30+
pass
31+
32+
self.assertFalse(A.initialized)
33+
self.assertTrue(B.initialized)
34+
35+
def test_init_subclass_kwargs(self):
36+
class A(object):
37+
def __init_subclass__(cls, **kwargs):
38+
cls.kwargs = kwargs
39+
40+
class B(A, x=3):
41+
pass
42+
43+
self.assertEqual(B.kwargs, dict(x=3))
44+
45+
def test_init_subclass_error(self):
46+
class A(object):
47+
def __init_subclass__(cls):
48+
raise RuntimeError
49+
50+
with self.assertRaises(RuntimeError):
51+
class B(A):
52+
pass
53+
54+
def test_init_subclass_wrong(self):
55+
class A(object):
56+
def __init_subclass__(cls, whatever):
57+
pass
58+
59+
with self.assertRaises(TypeError):
60+
class B(A):
61+
pass
62+
63+
def test_init_subclass_skipped(self):
64+
class BaseWithInit(object):
65+
def __init_subclass__(cls, **kwargs):
66+
super().__init_subclass__(**kwargs)
67+
cls.initialized = cls
68+
69+
class BaseWithoutInit(BaseWithInit):
70+
pass
71+
72+
class A(BaseWithoutInit):
73+
pass
74+
75+
self.assertIs(A.initialized, A)
76+
self.assertIs(BaseWithoutInit.initialized, BaseWithoutInit)
77+
78+
def test_init_subclass_diamond(self):
79+
class Base(object):
80+
def __init_subclass__(cls, **kwargs):
81+
super().__init_subclass__(**kwargs)
82+
cls.calls = []
83+
84+
class Left(Base):
85+
pass
86+
87+
class Middle(object):
88+
def __init_subclass__(cls, middle, **kwargs):
89+
super().__init_subclass__(**kwargs)
90+
cls.calls += [middle]
91+
92+
class Right(Base):
93+
def __init_subclass__(cls, right="right", **kwargs):
94+
super().__init_subclass__(**kwargs)
95+
cls.calls += [right]
96+
97+
class A(Left, Middle, Right, middle="middle"):
98+
pass
99+
100+
self.assertEqual(A.calls, ["right", "middle"])
101+
self.assertEqual(Left.calls, [])
102+
self.assertEqual(Right.calls, [])
103+
104+
def test_set_name(self):
105+
class Descriptor:
106+
def __set_name__(self, owner, name):
107+
self.owner = owner
108+
self.name = name
109+
110+
class A(object):
111+
d = Descriptor()
112+
113+
self.assertEqual(A.d.name, "d")
114+
self.assertIs(A.d.owner, A)
115+
116+
def test_set_name_metaclass(self):
117+
class Meta(type):
118+
def __new__(cls, name, bases, ns):
119+
ret = super().__new__(cls, name, bases, ns)
120+
self.assertEqual(ret.d.name, "d")
121+
self.assertIs(ret.d.owner, ret)
122+
return 0
123+
124+
class Descriptor(object):
125+
def __set_name__(self, owner, name):
126+
self.owner = owner
127+
self.name = name
128+
129+
class A(object, metaclass=Meta):
130+
d = Descriptor()
131+
self.assertEqual(A, 0)
132+
133+
def test_set_name_error(self):
134+
class Descriptor:
135+
def __set_name__(self, owner, name):
136+
raise RuntimeError
137+
138+
with self.assertRaises(RuntimeError):
139+
class A(object):
140+
d = Descriptor()
141+
142+
def test_set_name_wrong(self):
143+
class Descriptor:
144+
def __set_name__(self):
145+
pass
146+
147+
with self.assertRaises(TypeError):
148+
class A(object):
149+
d = Descriptor()
150+
151+
def test_set_name_init_subclass(self):
152+
class Descriptor:
153+
def __set_name__(self, owner, name):
154+
self.owner = owner
155+
self.name = name
156+
157+
class Meta(type):
158+
def __new__(cls, name, bases, ns):
159+
self = super().__new__(cls, name, bases, ns)
160+
self.meta_owner = self.owner
161+
self.meta_name = self.name
162+
return self
163+
164+
class A(object):
165+
def __init_subclass__(cls):
166+
cls.owner = cls.d.owner
167+
cls.name = cls.d.name
168+
169+
class B(A, metaclass=Meta):
170+
d = Descriptor()
171+
172+
self.assertIs(B.owner, B)
173+
self.assertEqual(B.name, 'd')
174+
self.assertIs(B.meta_owner, B)
175+
self.assertEqual(B.name, 'd')
176+
177+
def test_errors(self):
178+
class MyMeta(type):
179+
pass
180+
181+
with self.assertRaises(TypeError):
182+
class MyClass(object, metaclass=MyMeta, otherarg=1):
183+
pass
184+
185+
with self.assertRaises(TypeError):
186+
types.new_class("MyClass", (object,),
187+
dict(metaclass=MyMeta, otherarg=1))
188+
types.prepare_class("MyClass", (object,),
189+
dict(metaclass=MyMeta, otherarg=1))
190+
191+
class MyMeta(type):
192+
def __init__(self, name, bases, namespace, otherarg):
193+
super().__init__(name, bases, namespace)
194+
195+
with self.assertRaises(TypeError):
196+
class MyClass(object, metaclass=MyMeta, otherarg=1):
197+
pass
198+
199+
class MyMeta(type):
200+
def __new__(cls, name, bases, namespace, otherarg):
201+
return super().__new__(cls, name, bases, namespace)
202+
203+
def __init__(self, name, bases, namespace, otherarg):
204+
super().__init__(name, bases, namespace)
205+
self.otherarg = otherarg
206+
207+
class MyClass(object, metaclass=MyMeta, otherarg=1):
208+
pass
209+
210+
self.assertEqual(MyClass.otherarg, 1)
211+
212+
def test_errors_changed_pep487(self):
213+
# These tests failed before Python 3.6, PEP 487
214+
class MyMeta(type):
215+
def __new__(cls, name, bases, namespace):
216+
return super().__new__(cls, name=name, bases=bases,
217+
dict=namespace)
218+
219+
with self.assertRaises(TypeError):
220+
class MyClass(object, metaclass=MyMeta):
221+
pass
222+
223+
class MyMeta(type):
224+
def __new__(cls, name, bases, namespace, otherarg):
225+
self = super().__new__(cls, name, bases, namespace)
226+
self.otherarg = otherarg
227+
return self
228+
229+
class MyClass(object, metaclass=MyMeta, otherarg=1):
230+
pass
231+
232+
self.assertEqual(MyClass.otherarg, 1)
233+
234+
def test_type(self):
235+
t = type('NewClass', (object,), {})
236+
self.assertIsInstance(t, type)
237+
self.assertEqual(t.__name__, 'NewClass')
238+
239+
with self.assertRaises(TypeError):
240+
type(name='NewClass', bases=(object,), dict={})
241+
242+
243+
if __name__ == "__main__":
244+
main()

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,6 +1475,7 @@ Amy Taylor
14751475
Julian Taylor
14761476
Monty Taylor
14771477
Anatoly Techtonik
1478+
Martin Teichmann
14781479
Gustavo Temple
14791480
Mikhail Terekhov
14801481
Victor Terrón

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ Core and Builtins
3131
- Issue #27514: Make having too many statically nested blocks a SyntaxError
3232
instead of SystemError.
3333

34+
- Issue #27366: Implemented PEP 487 (Simpler customization of class creation).
35+
Upon subclassing, the __init_subclass__ classmethod is called on the base
36+
class. Descriptors are initialized with __set_name__ after class creation.
37+
3438
Library
3539
-------
3640

0 commit comments

Comments
 (0)