mirror of https://github.com/python/cpython
Issue #27366: Implement PEP 487
- __init_subclass__ called when new subclasses defined - __set_name__ called when descriptors are part of a class definition
This commit is contained in:
parent
f6daa690e4
commit
d78448e912
|
@ -1492,6 +1492,12 @@ class' :attr:`~object.__dict__`.
|
||||||
Called to delete the attribute on an instance *instance* of the owner class.
|
Called to delete the attribute on an instance *instance* of the owner class.
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: object.__set_name__(self, owner, name)
|
||||||
|
|
||||||
|
Called at the time the owning class *owner* is created. The
|
||||||
|
descriptor has been assigned to *name*.
|
||||||
|
|
||||||
|
|
||||||
The attribute :attr:`__objclass__` is interpreted by the :mod:`inspect` module
|
The attribute :attr:`__objclass__` is interpreted by the :mod:`inspect` module
|
||||||
as specifying the class where this object was defined (setting this
|
as specifying the class where this object was defined (setting this
|
||||||
appropriately can assist in runtime introspection of dynamic class attributes).
|
appropriately can assist in runtime introspection of dynamic class attributes).
|
||||||
|
@ -1629,11 +1635,46 @@ Notes on using *__slots__*
|
||||||
* *__class__* assignment works only if both classes have the same *__slots__*.
|
* *__class__* assignment works only if both classes have the same *__slots__*.
|
||||||
|
|
||||||
|
|
||||||
.. _metaclasses:
|
.. _class-customization:
|
||||||
|
|
||||||
Customizing class creation
|
Customizing class creation
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
|
Whenever a class inherits from another class, *__init_subclass__* is
|
||||||
|
called on that class. This way, it is possible to write classes which
|
||||||
|
change the behavior of subclasses. This is closely related to class
|
||||||
|
decorators, but where class decorators only affect the specific class they're
|
||||||
|
applied to, ``__init_subclass__`` solely applies to future subclasses of the
|
||||||
|
class defining the method.
|
||||||
|
|
||||||
|
.. classmethod:: object.__init_subclass__(cls)
|
||||||
|
This method is called whenever the containing class is subclassed.
|
||||||
|
*cls* is then the new subclass. If defined as a normal instance method,
|
||||||
|
this method is implicitly converted to a class method.
|
||||||
|
|
||||||
|
Keyword arguments which are given to a new class are passed to
|
||||||
|
the parent's class ``__init_subclass__``. For compatibility with
|
||||||
|
other classes using ``__init_subclass__``, one should take out the
|
||||||
|
needed keyword arguments and pass the others over to the base
|
||||||
|
class, as in::
|
||||||
|
|
||||||
|
class Philosopher:
|
||||||
|
def __init_subclass__(cls, default_name, **kwargs):
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
cls.default_name = default_name
|
||||||
|
|
||||||
|
class AustralianPhilosopher(Philosopher, default_name="Bruce"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
The default implementation ``object.__init_subclass__`` does
|
||||||
|
nothing, but raises an error if it is called with any arguments.
|
||||||
|
|
||||||
|
|
||||||
|
.. _metaclasses:
|
||||||
|
|
||||||
|
Metaclasses
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
By default, classes are constructed using :func:`type`. The class body is
|
By default, classes are constructed using :func:`type`. The class body is
|
||||||
executed in a new namespace and the class name is bound locally to the
|
executed in a new namespace and the class name is bound locally to the
|
||||||
result of ``type(name, bases, namespace)``.
|
result of ``type(name, bases, namespace)``.
|
||||||
|
|
|
@ -110,6 +110,26 @@ evaluated at run time, and then formatted using the :func:`format` protocol.
|
||||||
See :pep:`498` and the main documentation at :ref:`f-strings`.
|
See :pep:`498` and the main documentation at :ref:`f-strings`.
|
||||||
|
|
||||||
|
|
||||||
|
PEP 487: Simpler customization of class creation
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
Upon subclassing a class, the ``__init_subclass__`` classmethod (if defined) is
|
||||||
|
called on the base class. This makes it straightforward to write classes that
|
||||||
|
customize initialization of future subclasses without introducing the
|
||||||
|
complexity of a full custom metaclass.
|
||||||
|
|
||||||
|
The descriptor protocol has also been expanded to include a new optional method,
|
||||||
|
``__set_name__``. Whenever a new class is defined, the new method will be called
|
||||||
|
on all descriptors included in the definition, providing them with a reference
|
||||||
|
to the class being defined and the name given to the descriptor within the
|
||||||
|
class namespace.
|
||||||
|
|
||||||
|
Also see :pep:`487` and the updated class customization documentation at
|
||||||
|
:ref:`class-customization` and :ref:`descriptors`.
|
||||||
|
|
||||||
|
(Contributed by Martin Teichmann in :issue:`27366`)
|
||||||
|
|
||||||
|
|
||||||
PYTHONMALLOC environment variable
|
PYTHONMALLOC environment variable
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
|
|
@ -1699,21 +1699,11 @@ class TestType(unittest.TestCase):
|
||||||
self.assertEqual(x.spam(), 'spam42')
|
self.assertEqual(x.spam(), 'spam42')
|
||||||
self.assertEqual(x.to_bytes(2, 'little'), b'\x2a\x00')
|
self.assertEqual(x.to_bytes(2, 'little'), b'\x2a\x00')
|
||||||
|
|
||||||
def test_type_new_keywords(self):
|
def test_type_nokwargs(self):
|
||||||
class B:
|
with self.assertRaises(TypeError):
|
||||||
def ham(self):
|
type('a', (), {}, x=5)
|
||||||
return 'ham%d' % self
|
with self.assertRaises(TypeError):
|
||||||
C = type.__new__(type,
|
type('a', (), dict={})
|
||||||
name='C',
|
|
||||||
bases=(B, int),
|
|
||||||
dict={'spam': lambda self: 'spam%s' % self})
|
|
||||||
self.assertEqual(C.__name__, 'C')
|
|
||||||
self.assertEqual(C.__qualname__, 'C')
|
|
||||||
self.assertEqual(C.__module__, __name__)
|
|
||||||
self.assertEqual(C.__bases__, (B, int))
|
|
||||||
self.assertIs(C.__base__, int)
|
|
||||||
self.assertIn('spam', C.__dict__)
|
|
||||||
self.assertNotIn('ham', C.__dict__)
|
|
||||||
|
|
||||||
def test_type_name(self):
|
def test_type_name(self):
|
||||||
for name in 'A', '\xc4', '\U0001f40d', 'B.A', '42', '':
|
for name in 'A', '\xc4', '\U0001f40d', 'B.A', '42', '':
|
||||||
|
|
|
@ -182,6 +182,7 @@ You can get the information from the list type:
|
||||||
'__iadd__',
|
'__iadd__',
|
||||||
'__imul__',
|
'__imul__',
|
||||||
'__init__',
|
'__init__',
|
||||||
|
'__init_subclass__',
|
||||||
'__iter__',
|
'__iter__',
|
||||||
'__le__',
|
'__le__',
|
||||||
'__len__',
|
'__len__',
|
||||||
|
|
|
@ -638,8 +638,9 @@ class PydocDocTest(unittest.TestCase):
|
||||||
del expected['__doc__']
|
del expected['__doc__']
|
||||||
del expected['__class__']
|
del expected['__class__']
|
||||||
# inspect resolves descriptors on type into methods, but vars doesn't,
|
# inspect resolves descriptors on type into methods, but vars doesn't,
|
||||||
# so we need to update __subclasshook__.
|
# so we need to update __subclasshook__ and __init_subclass__.
|
||||||
expected['__subclasshook__'] = TestClass.__subclasshook__
|
expected['__subclasshook__'] = TestClass.__subclasshook__
|
||||||
|
expected['__init_subclass__'] = TestClass.__init_subclass__
|
||||||
|
|
||||||
methods = pydoc.allmethods(TestClass)
|
methods = pydoc.allmethods(TestClass)
|
||||||
self.assertDictEqual(methods, expected)
|
self.assertDictEqual(methods, expected)
|
||||||
|
|
|
@ -0,0 +1,244 @@
|
||||||
|
from unittest import TestCase, main
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
|
||||||
|
|
||||||
|
class Test(TestCase):
|
||||||
|
def test_init_subclass(self):
|
||||||
|
class A(object):
|
||||||
|
initialized = False
|
||||||
|
|
||||||
|
def __init_subclass__(cls):
|
||||||
|
super().__init_subclass__()
|
||||||
|
cls.initialized = True
|
||||||
|
|
||||||
|
class B(A):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertFalse(A.initialized)
|
||||||
|
self.assertTrue(B.initialized)
|
||||||
|
|
||||||
|
def test_init_subclass_dict(self):
|
||||||
|
class A(dict, object):
|
||||||
|
initialized = False
|
||||||
|
|
||||||
|
def __init_subclass__(cls):
|
||||||
|
super().__init_subclass__()
|
||||||
|
cls.initialized = True
|
||||||
|
|
||||||
|
class B(A):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertFalse(A.initialized)
|
||||||
|
self.assertTrue(B.initialized)
|
||||||
|
|
||||||
|
def test_init_subclass_kwargs(self):
|
||||||
|
class A(object):
|
||||||
|
def __init_subclass__(cls, **kwargs):
|
||||||
|
cls.kwargs = kwargs
|
||||||
|
|
||||||
|
class B(A, x=3):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertEqual(B.kwargs, dict(x=3))
|
||||||
|
|
||||||
|
def test_init_subclass_error(self):
|
||||||
|
class A(object):
|
||||||
|
def __init_subclass__(cls):
|
||||||
|
raise RuntimeError
|
||||||
|
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
class B(A):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_init_subclass_wrong(self):
|
||||||
|
class A(object):
|
||||||
|
def __init_subclass__(cls, whatever):
|
||||||
|
pass
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
class B(A):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_init_subclass_skipped(self):
|
||||||
|
class BaseWithInit(object):
|
||||||
|
def __init_subclass__(cls, **kwargs):
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
cls.initialized = cls
|
||||||
|
|
||||||
|
class BaseWithoutInit(BaseWithInit):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class A(BaseWithoutInit):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertIs(A.initialized, A)
|
||||||
|
self.assertIs(BaseWithoutInit.initialized, BaseWithoutInit)
|
||||||
|
|
||||||
|
def test_init_subclass_diamond(self):
|
||||||
|
class Base(object):
|
||||||
|
def __init_subclass__(cls, **kwargs):
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
cls.calls = []
|
||||||
|
|
||||||
|
class Left(Base):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Middle(object):
|
||||||
|
def __init_subclass__(cls, middle, **kwargs):
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
cls.calls += [middle]
|
||||||
|
|
||||||
|
class Right(Base):
|
||||||
|
def __init_subclass__(cls, right="right", **kwargs):
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
cls.calls += [right]
|
||||||
|
|
||||||
|
class A(Left, Middle, Right, middle="middle"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertEqual(A.calls, ["right", "middle"])
|
||||||
|
self.assertEqual(Left.calls, [])
|
||||||
|
self.assertEqual(Right.calls, [])
|
||||||
|
|
||||||
|
def test_set_name(self):
|
||||||
|
class Descriptor:
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
self.owner = owner
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
class A(object):
|
||||||
|
d = Descriptor()
|
||||||
|
|
||||||
|
self.assertEqual(A.d.name, "d")
|
||||||
|
self.assertIs(A.d.owner, A)
|
||||||
|
|
||||||
|
def test_set_name_metaclass(self):
|
||||||
|
class Meta(type):
|
||||||
|
def __new__(cls, name, bases, ns):
|
||||||
|
ret = super().__new__(cls, name, bases, ns)
|
||||||
|
self.assertEqual(ret.d.name, "d")
|
||||||
|
self.assertIs(ret.d.owner, ret)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
class Descriptor(object):
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
self.owner = owner
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
class A(object, metaclass=Meta):
|
||||||
|
d = Descriptor()
|
||||||
|
self.assertEqual(A, 0)
|
||||||
|
|
||||||
|
def test_set_name_error(self):
|
||||||
|
class Descriptor:
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
raise RuntimeError
|
||||||
|
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
class A(object):
|
||||||
|
d = Descriptor()
|
||||||
|
|
||||||
|
def test_set_name_wrong(self):
|
||||||
|
class Descriptor:
|
||||||
|
def __set_name__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
class A(object):
|
||||||
|
d = Descriptor()
|
||||||
|
|
||||||
|
def test_set_name_init_subclass(self):
|
||||||
|
class Descriptor:
|
||||||
|
def __set_name__(self, owner, name):
|
||||||
|
self.owner = owner
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
class Meta(type):
|
||||||
|
def __new__(cls, name, bases, ns):
|
||||||
|
self = super().__new__(cls, name, bases, ns)
|
||||||
|
self.meta_owner = self.owner
|
||||||
|
self.meta_name = self.name
|
||||||
|
return self
|
||||||
|
|
||||||
|
class A(object):
|
||||||
|
def __init_subclass__(cls):
|
||||||
|
cls.owner = cls.d.owner
|
||||||
|
cls.name = cls.d.name
|
||||||
|
|
||||||
|
class B(A, metaclass=Meta):
|
||||||
|
d = Descriptor()
|
||||||
|
|
||||||
|
self.assertIs(B.owner, B)
|
||||||
|
self.assertEqual(B.name, 'd')
|
||||||
|
self.assertIs(B.meta_owner, B)
|
||||||
|
self.assertEqual(B.name, 'd')
|
||||||
|
|
||||||
|
def test_errors(self):
|
||||||
|
class MyMeta(type):
|
||||||
|
pass
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
class MyClass(object, metaclass=MyMeta, otherarg=1):
|
||||||
|
pass
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
types.new_class("MyClass", (object,),
|
||||||
|
dict(metaclass=MyMeta, otherarg=1))
|
||||||
|
types.prepare_class("MyClass", (object,),
|
||||||
|
dict(metaclass=MyMeta, otherarg=1))
|
||||||
|
|
||||||
|
class MyMeta(type):
|
||||||
|
def __init__(self, name, bases, namespace, otherarg):
|
||||||
|
super().__init__(name, bases, namespace)
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
class MyClass(object, metaclass=MyMeta, otherarg=1):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MyMeta(type):
|
||||||
|
def __new__(cls, name, bases, namespace, otherarg):
|
||||||
|
return super().__new__(cls, name, bases, namespace)
|
||||||
|
|
||||||
|
def __init__(self, name, bases, namespace, otherarg):
|
||||||
|
super().__init__(name, bases, namespace)
|
||||||
|
self.otherarg = otherarg
|
||||||
|
|
||||||
|
class MyClass(object, metaclass=MyMeta, otherarg=1):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertEqual(MyClass.otherarg, 1)
|
||||||
|
|
||||||
|
def test_errors_changed_pep487(self):
|
||||||
|
# These tests failed before Python 3.6, PEP 487
|
||||||
|
class MyMeta(type):
|
||||||
|
def __new__(cls, name, bases, namespace):
|
||||||
|
return super().__new__(cls, name=name, bases=bases,
|
||||||
|
dict=namespace)
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
class MyClass(object, metaclass=MyMeta):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MyMeta(type):
|
||||||
|
def __new__(cls, name, bases, namespace, otherarg):
|
||||||
|
self = super().__new__(cls, name, bases, namespace)
|
||||||
|
self.otherarg = otherarg
|
||||||
|
return self
|
||||||
|
|
||||||
|
class MyClass(object, metaclass=MyMeta, otherarg=1):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertEqual(MyClass.otherarg, 1)
|
||||||
|
|
||||||
|
def test_type(self):
|
||||||
|
t = type('NewClass', (object,), {})
|
||||||
|
self.assertIsInstance(t, type)
|
||||||
|
self.assertEqual(t.__name__, 'NewClass')
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
type(name='NewClass', bases=(object,), dict={})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -1475,6 +1475,7 @@ Amy Taylor
|
||||||
Julian Taylor
|
Julian Taylor
|
||||||
Monty Taylor
|
Monty Taylor
|
||||||
Anatoly Techtonik
|
Anatoly Techtonik
|
||||||
|
Martin Teichmann
|
||||||
Gustavo Temple
|
Gustavo Temple
|
||||||
Mikhail Terekhov
|
Mikhail Terekhov
|
||||||
Victor Terrón
|
Victor Terrón
|
||||||
|
|
|
@ -31,6 +31,10 @@ Core and Builtins
|
||||||
- Issue #27514: Make having too many statically nested blocks a SyntaxError
|
- Issue #27514: Make having too many statically nested blocks a SyntaxError
|
||||||
instead of SystemError.
|
instead of SystemError.
|
||||||
|
|
||||||
|
- Issue #27366: Implemented PEP 487 (Simpler customization of class creation).
|
||||||
|
Upon subclassing, the __init_subclass__ classmethod is called on the base
|
||||||
|
class. Descriptors are initialized with __set_name__ after class creation.
|
||||||
|
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -53,10 +53,12 @@ _Py_IDENTIFIER(__doc__);
|
||||||
_Py_IDENTIFIER(__getattribute__);
|
_Py_IDENTIFIER(__getattribute__);
|
||||||
_Py_IDENTIFIER(__getitem__);
|
_Py_IDENTIFIER(__getitem__);
|
||||||
_Py_IDENTIFIER(__hash__);
|
_Py_IDENTIFIER(__hash__);
|
||||||
|
_Py_IDENTIFIER(__init_subclass__);
|
||||||
_Py_IDENTIFIER(__len__);
|
_Py_IDENTIFIER(__len__);
|
||||||
_Py_IDENTIFIER(__module__);
|
_Py_IDENTIFIER(__module__);
|
||||||
_Py_IDENTIFIER(__name__);
|
_Py_IDENTIFIER(__name__);
|
||||||
_Py_IDENTIFIER(__new__);
|
_Py_IDENTIFIER(__new__);
|
||||||
|
_Py_IDENTIFIER(__set_name__);
|
||||||
_Py_IDENTIFIER(__setitem__);
|
_Py_IDENTIFIER(__setitem__);
|
||||||
_Py_IDENTIFIER(builtins);
|
_Py_IDENTIFIER(builtins);
|
||||||
|
|
||||||
|
@ -2027,6 +2029,8 @@ static void object_dealloc(PyObject *);
|
||||||
static int object_init(PyObject *, PyObject *, PyObject *);
|
static int object_init(PyObject *, PyObject *, PyObject *);
|
||||||
static int update_slot(PyTypeObject *, PyObject *);
|
static int update_slot(PyTypeObject *, PyObject *);
|
||||||
static void fixup_slot_dispatchers(PyTypeObject *);
|
static void fixup_slot_dispatchers(PyTypeObject *);
|
||||||
|
static int set_names(PyTypeObject *);
|
||||||
|
static int init_subclass(PyTypeObject *, PyObject *);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Helpers for __dict__ descriptor. We don't want to expose the dicts
|
* Helpers for __dict__ descriptor. We don't want to expose the dicts
|
||||||
|
@ -2202,7 +2206,8 @@ type_init(PyObject *cls, PyObject *args, PyObject *kwds)
|
||||||
assert(args != NULL && PyTuple_Check(args));
|
assert(args != NULL && PyTuple_Check(args));
|
||||||
assert(kwds == NULL || PyDict_Check(kwds));
|
assert(kwds == NULL || PyDict_Check(kwds));
|
||||||
|
|
||||||
if (kwds != NULL && PyDict_Check(kwds) && PyDict_Size(kwds) != 0) {
|
if (kwds != NULL && PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 &&
|
||||||
|
PyDict_Check(kwds) && PyDict_Size(kwds) != 0) {
|
||||||
PyErr_SetString(PyExc_TypeError,
|
PyErr_SetString(PyExc_TypeError,
|
||||||
"type.__init__() takes no keyword arguments");
|
"type.__init__() takes no keyword arguments");
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -2269,7 +2274,6 @@ static PyObject *
|
||||||
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
||||||
{
|
{
|
||||||
PyObject *name, *bases = NULL, *orig_dict, *dict = NULL;
|
PyObject *name, *bases = NULL, *orig_dict, *dict = NULL;
|
||||||
static char *kwlist[] = {"name", "bases", "dict", 0};
|
|
||||||
PyObject *qualname, *slots = NULL, *tmp, *newslots;
|
PyObject *qualname, *slots = NULL, *tmp, *newslots;
|
||||||
PyTypeObject *type = NULL, *base, *tmptype, *winner;
|
PyTypeObject *type = NULL, *base, *tmptype, *winner;
|
||||||
PyHeapTypeObject *et;
|
PyHeapTypeObject *et;
|
||||||
|
@ -2296,7 +2300,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
||||||
/* SF bug 475327 -- if that didn't trigger, we need 3
|
/* SF bug 475327 -- if that didn't trigger, we need 3
|
||||||
arguments. but PyArg_ParseTupleAndKeywords below may give
|
arguments. but PyArg_ParseTupleAndKeywords below may give
|
||||||
a msg saying type() needs exactly 3. */
|
a msg saying type() needs exactly 3. */
|
||||||
if (nargs + nkwds != 3) {
|
if (nargs != 3) {
|
||||||
PyErr_SetString(PyExc_TypeError,
|
PyErr_SetString(PyExc_TypeError,
|
||||||
"type() takes 1 or 3 arguments");
|
"type() takes 1 or 3 arguments");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -2304,9 +2308,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check arguments: (name, bases, dict) */
|
/* Check arguments: (name, bases, dict) */
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "UO!O!:type", kwlist,
|
if (!PyArg_ParseTuple(args, "UO!O!:type", &name, &PyTuple_Type, &bases,
|
||||||
&name,
|
|
||||||
&PyTuple_Type, &bases,
|
|
||||||
&PyDict_Type, &orig_dict))
|
&PyDict_Type, &orig_dict))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
@ -2587,6 +2589,20 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
||||||
Py_DECREF(tmp);
|
Py_DECREF(tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Special-case __init_subclass__: if it's a plain function,
|
||||||
|
make it a classmethod */
|
||||||
|
tmp = _PyDict_GetItemId(dict, &PyId___init_subclass__);
|
||||||
|
if (tmp != NULL && PyFunction_Check(tmp)) {
|
||||||
|
tmp = PyClassMethod_New(tmp);
|
||||||
|
if (tmp == NULL)
|
||||||
|
goto error;
|
||||||
|
if (_PyDict_SetItemId(dict, &PyId___init_subclass__, tmp) < 0) {
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
/* Add descriptors for custom slots from __slots__, or for __dict__ */
|
/* Add descriptors for custom slots from __slots__, or for __dict__ */
|
||||||
mp = PyHeapType_GET_MEMBERS(et);
|
mp = PyHeapType_GET_MEMBERS(et);
|
||||||
slotoffset = base->tp_basicsize;
|
slotoffset = base->tp_basicsize;
|
||||||
|
@ -2667,6 +2683,12 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
|
||||||
et->ht_cached_keys = _PyDict_NewKeysForClass();
|
et->ht_cached_keys = _PyDict_NewKeysForClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (set_names(type) < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
if (init_subclass(type, kwds) < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
Py_DECREF(dict);
|
Py_DECREF(dict);
|
||||||
return (PyObject *)type;
|
return (PyObject *)type;
|
||||||
|
|
||||||
|
@ -4312,6 +4334,19 @@ PyDoc_STRVAR(object_subclasshook_doc,
|
||||||
"NotImplemented, the normal algorithm is used. Otherwise, it\n"
|
"NotImplemented, the normal algorithm is used. Otherwise, it\n"
|
||||||
"overrides the normal algorithm (and the outcome is cached).\n");
|
"overrides the normal algorithm (and the outcome is cached).\n");
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
object_init_subclass(PyObject *cls, PyObject *arg)
|
||||||
|
{
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(object_init_subclass_doc,
|
||||||
|
"This method is called when a class is subclassed\n"
|
||||||
|
"\n"
|
||||||
|
"Whenever a class is subclassed, this method is called. The default\n"
|
||||||
|
"implementation does nothing. It may be overridden to extend\n"
|
||||||
|
"subclasses.\n");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
from PEP 3101, this code implements:
|
from PEP 3101, this code implements:
|
||||||
|
|
||||||
|
@ -4416,6 +4451,8 @@ static PyMethodDef object_methods[] = {
|
||||||
PyDoc_STR("helper for pickle")},
|
PyDoc_STR("helper for pickle")},
|
||||||
{"__subclasshook__", object_subclasshook, METH_CLASS | METH_VARARGS,
|
{"__subclasshook__", object_subclasshook, METH_CLASS | METH_VARARGS,
|
||||||
object_subclasshook_doc},
|
object_subclasshook_doc},
|
||||||
|
{"__init_subclass__", object_init_subclass, METH_CLASS | METH_NOARGS,
|
||||||
|
object_init_subclass_doc},
|
||||||
{"__format__", object_format, METH_VARARGS,
|
{"__format__", object_format, METH_VARARGS,
|
||||||
PyDoc_STR("default object formatter")},
|
PyDoc_STR("default object formatter")},
|
||||||
{"__sizeof__", object_sizeof, METH_NOARGS,
|
{"__sizeof__", object_sizeof, METH_NOARGS,
|
||||||
|
@ -6925,6 +6962,54 @@ update_all_slots(PyTypeObject* type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Call __set_name__ on all descriptors in a newly generated type */
|
||||||
|
static int
|
||||||
|
set_names(PyTypeObject *type)
|
||||||
|
{
|
||||||
|
PyObject *key, *value, *tmp;
|
||||||
|
Py_ssize_t i = 0;
|
||||||
|
|
||||||
|
while (PyDict_Next(type->tp_dict, &i, &key, &value)) {
|
||||||
|
if (PyObject_HasAttr(value, _PyUnicode_FromId(&PyId___set_name__))) {
|
||||||
|
tmp = PyObject_CallMethodObjArgs(
|
||||||
|
value, _PyUnicode_FromId(&PyId___set_name__),
|
||||||
|
type, key, NULL);
|
||||||
|
if (tmp == NULL)
|
||||||
|
return -1;
|
||||||
|
else
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Call __init_subclass__ on the parent of a newly generated type */
|
||||||
|
static int
|
||||||
|
init_subclass(PyTypeObject *type, PyObject *kwds)
|
||||||
|
{
|
||||||
|
PyObject *super, *func, *tmp, *tuple;
|
||||||
|
|
||||||
|
super = PyObject_CallFunctionObjArgs((PyObject *) &PySuper_Type,
|
||||||
|
type, type, NULL);
|
||||||
|
func = _PyObject_GetAttrId(super, &PyId___init_subclass__);
|
||||||
|
Py_DECREF(super);
|
||||||
|
|
||||||
|
if (func == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
tuple = PyTuple_New(0);
|
||||||
|
tmp = PyObject_Call(func, tuple, kwds);
|
||||||
|
Py_DECREF(tuple);
|
||||||
|
Py_DECREF(func);
|
||||||
|
|
||||||
|
if (tmp == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* recurse_down_subclasses() and update_subclasses() are mutually
|
/* recurse_down_subclasses() and update_subclasses() are mutually
|
||||||
recursive functions to call a callback for all subclasses,
|
recursive functions to call a callback for all subclasses,
|
||||||
but refraining from recursing into subclasses that define 'name'. */
|
but refraining from recursing into subclasses that define 'name'. */
|
||||||
|
|
Loading…
Reference in New Issue