From 6b14bb186ccfcf27bfcae0564c09056d5ef1f2d5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 17 Jun 2019 17:00:58 +0300 Subject: [PATCH 1/4] bpo-28869: Change some details in type(). * Move handling of one-argument call of type() from type.__new__() to type.__call__(). * Move deducing __module__ from the caller's frame from type.__new__() to type.__call__(). --- Objects/typeobject.c | 92 ++++++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index ba128a90778c65..c9782c1259990a 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -81,6 +81,12 @@ clear_slotdefs(void); static PyObject * lookup_maybe_method(PyObject *self, _Py_Identifier *attrid, int *unbound); +static PyObject * +type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds); + +static PyObject * +type_new_ex(PyTypeObject *metatype, PyObject *args, PyObject *kwds, int setmodule); + /* * finds the beginning of the docstring's introspection signature. * if present, returns a pointer pointing to the first '('. @@ -952,13 +958,6 @@ type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *obj; - if (type->tp_new == NULL) { - PyErr_Format(PyExc_TypeError, - "cannot create '%.100s' instances", - type->tp_name); - return NULL; - } - #ifdef Py_DEBUG /* type_call() must not be called with an exception set, because it can clear it (directly or indirectly) and so the @@ -966,19 +965,47 @@ type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) assert(!PyErr_Occurred()); #endif - obj = type->tp_new(type, args, kwds); + /* Special case: type(x) should return x->ob_type */ + /* We only want type itself to accept the one-argument form (#27157). */ + if (type == &PyType_Type) { + assert(args != NULL && PyTuple_Check(args)); + assert(kwds == NULL || PyDict_Check(kwds)); + Py_ssize_t nargs = PyTuple_GET_SIZE(args); + + if (nargs == 1 && (kwds == NULL || PyDict_GET_SIZE(kwds))) { + obj = (PyObject *) Py_TYPE(PyTuple_GET_ITEM(args, 0)); + Py_INCREF(obj); + return obj; + } + + /* SF bug 475327 -- if that didn't trigger, we need 3 + arguments. But PyArg_ParseTuple in type_new() may give + a msg saying type() needs exactly 3. */ + if (nargs != 3) { + PyErr_SetString(PyExc_TypeError, + "type() takes 1 or 3 arguments"); + return NULL; + } + assert (type->tp_new == type_new); + } + + if (type->tp_new == NULL) { + PyErr_Format(PyExc_TypeError, + "cannot create '%.100s' instances", + type->tp_name); + return NULL; + } + + if (type->tp_new == type_new) { + obj = type_new_ex(type, args, kwds, 1); + } + else { + obj = type->tp_new(type, args, kwds); + } obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL); if (obj == NULL) return NULL; - /* Ugly exception: when the call was type(something), - don't call tp_init on the result. */ - if (type == &PyType_Type && - PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 && - (kwds == NULL || - (PyDict_Check(kwds) && PyDict_GET_SIZE(kwds) == 0))) - return obj; - /* If the returned object is not an instance of type, it won't be initialized. */ if (!PyType_IsSubtype(Py_TYPE(obj), type)) @@ -2320,7 +2347,7 @@ _PyType_CalculateMetaclass(PyTypeObject *metatype, PyObject *bases) } static PyObject * -type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) +type_new_ex(PyTypeObject *metatype, PyObject *args, PyObject *kwds, int setmodule) { PyObject *name, *bases = NULL, *orig_dict, *dict = NULL; PyObject *qualname, *slots = NULL, *tmp, *newslots, *cell; @@ -2336,29 +2363,6 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) assert(args != NULL && PyTuple_Check(args)); assert(kwds == NULL || PyDict_Check(kwds)); - /* Special case: type(x) should return x->ob_type */ - /* We only want type itself to accept the one-argument form (#27157) - Note: We don't call PyType_CheckExact as that also allows subclasses */ - if (metatype == &PyType_Type) { - const Py_ssize_t nargs = PyTuple_GET_SIZE(args); - const Py_ssize_t nkwds = kwds == NULL ? 0 : PyDict_GET_SIZE(kwds); - - if (nargs == 1 && nkwds == 0) { - PyObject *x = PyTuple_GET_ITEM(args, 0); - Py_INCREF(Py_TYPE(x)); - return (PyObject *) Py_TYPE(x); - } - - /* SF bug 475327 -- if that didn't trigger, we need 3 - arguments. but PyArg_ParseTuple below may give - a msg saying type() needs exactly 3. */ - if (nargs != 3) { - PyErr_SetString(PyExc_TypeError, - "type() takes 1 or 3 arguments"); - return NULL; - } - } - /* Check arguments: (name, bases, dict) */ if (!PyArg_ParseTuple(args, "UO!O!:type.__new__", &name, &PyTuple_Type, &bases, &PyDict_Type, &orig_dict)) @@ -2606,7 +2610,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) type->tp_dict = dict; /* Set __module__ in the dict */ - if (_PyDict_GetItemIdWithError(dict, &PyId___module__) == NULL) { + if (setmodule && _PyDict_GetItemIdWithError(dict, &PyId___module__) == NULL) { if (PyErr_Occurred()) { goto error; } @@ -2843,6 +2847,12 @@ static const short slotoffsets[] = { #include "typeslots.inc" }; +static PyObject * +type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) +{ + return type_new_ex(metatype, args, kwds, 0); +} + PyObject * PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases) { From fe3b8c6c95a3e92c96b15a70ae5f1d1749c82645 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 13 Oct 2019 13:42:59 +0300 Subject: [PATCH 2/4] Skip additional frame if type.__new__ is not called from type.__call__. --- Lib/test/test_abc.py | 7 ++++++ Objects/typeobject.c | 57 ++++++++++++++++++++++++++++---------------- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index 000e5838e3c712..5a24c0a9b113a9 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -488,6 +488,13 @@ class C(with_metaclass(abc_ABCMeta, A, B)): pass self.assertEqual(C.__class__, abc_ABCMeta) + def test_type_module(self): + class A(metaclass=abc_ABCMeta): + pass + self.assertEqual(A.__module__, __name__) + B = abc_ABCMeta('B', (), {}) + self.assertEqual(B.__module__, __name__) + class TestABCWithInitSubclass(unittest.TestCase): def test_works_with_init_subclass(self): diff --git a/Objects/typeobject.c b/Objects/typeobject.c index ee85a973d2eded..ffcac9bbb98368 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -85,7 +85,7 @@ static PyObject * type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds); static PyObject * -type_new_ex(PyTypeObject *metatype, PyObject *args, PyObject *kwds, int setmodule); +type_new_ex(PyTypeObject *metatype, PyObject *args, PyObject *kwds, int depth); /* * finds the beginning of the docstring's introspection signature. @@ -997,7 +997,7 @@ type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) } if (type->tp_new == type_new) { - obj = type_new_ex(type, args, kwds, 1); + obj = type_new_ex(type, args, kwds, 0); } else { obj = type->tp_new(type, args, kwds); @@ -2347,8 +2347,39 @@ _PyType_CalculateMetaclass(PyTypeObject *metatype, PyObject *bases) return winner; } + +static int +type_set_default_module(PyObject *dict, int depth) +{ + if (_PyDict_GetItemIdWithError(dict, &PyId___module__) != NULL) { + return 0; + } + if (PyErr_Occurred()) { + return -1; + } + + PyFrameObject *f = PyEval_GetFrame(); + if (f == NULL) { + return 0; + } + while (depth-- > 0 && f->f_back) { + f = f->f_back; + } + + PyObject *name = _PyDict_GetItemIdWithError(f->f_globals, &PyId___name__); + if (name != NULL) { + if (_PyDict_SetItemId(dict, &PyId___module__, name) < 0) { + return -1; + } + } + else if (PyErr_Occurred()) { + return -1; + } + return 0; +} + static PyObject * -type_new_ex(PyTypeObject *metatype, PyObject *args, PyObject *kwds, int setmodule) +type_new_ex(PyTypeObject *metatype, PyObject *args, PyObject *kwds, int depth) { PyObject *name, *bases = NULL, *orig_dict, *dict = NULL; PyObject *qualname, *slots = NULL, *tmp, *newslots, *cell; @@ -2611,22 +2642,8 @@ type_new_ex(PyTypeObject *metatype, PyObject *args, PyObject *kwds, int setmodul type->tp_dict = dict; /* Set __module__ in the dict */ - if (setmodule && _PyDict_GetItemIdWithError(dict, &PyId___module__) == NULL) { - if (PyErr_Occurred()) { - goto error; - } - tmp = PyEval_GetGlobals(); - if (tmp != NULL) { - tmp = _PyDict_GetItemIdWithError(tmp, &PyId___name__); - if (tmp != NULL) { - if (_PyDict_SetItemId(dict, &PyId___module__, - tmp) < 0) - goto error; - } - else if (PyErr_Occurred()) { - goto error; - } - } + if (type_set_default_module(dict, depth) < 0) { + goto error; } /* Set ht_qualname to dict['__qualname__'] if available, else to @@ -2851,7 +2868,7 @@ static const short slotoffsets[] = { static PyObject * type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds) { - return type_new_ex(metatype, args, kwds, 0); + return type_new_ex(metatype, args, kwds, 1); } PyObject * From 8b76f9fad02722f16ecd411de44d4816d3dc4cf0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 13 Oct 2019 13:45:21 +0300 Subject: [PATCH 3/4] Rever moving handling of one-argument call of type(). --- Objects/typeobject.c | 69 ++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index ffcac9bbb98368..fbfde01310d4d9 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -958,6 +958,13 @@ type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *obj; + if (type->tp_new == NULL) { + PyErr_Format(PyExc_TypeError, + "cannot create '%.100s' instances", + type->tp_name); + return NULL; + } + #ifdef Py_DEBUG /* type_call() must not be called with an exception set, because it can clear it (directly or indirectly) and so the @@ -965,37 +972,6 @@ type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) assert(!PyErr_Occurred()); #endif - /* Special case: type(x) should return x->ob_type */ - /* We only want type itself to accept the one-argument form (#27157). */ - if (type == &PyType_Type) { - assert(args != NULL && PyTuple_Check(args)); - assert(kwds == NULL || PyDict_Check(kwds)); - Py_ssize_t nargs = PyTuple_GET_SIZE(args); - - if (nargs == 1 && (kwds == NULL || PyDict_GET_SIZE(kwds))) { - obj = (PyObject *) Py_TYPE(PyTuple_GET_ITEM(args, 0)); - Py_INCREF(obj); - return obj; - } - - /* SF bug 475327 -- if that didn't trigger, we need 3 - arguments. But PyArg_ParseTuple in type_new() may give - a msg saying type() needs exactly 3. */ - if (nargs != 3) { - PyErr_SetString(PyExc_TypeError, - "type() takes 1 or 3 arguments"); - return NULL; - } - assert (type->tp_new == type_new); - } - - if (type->tp_new == NULL) { - PyErr_Format(PyExc_TypeError, - "cannot create '%.100s' instances", - type->tp_name); - return NULL; - } - if (type->tp_new == type_new) { obj = type_new_ex(type, args, kwds, 0); } @@ -1006,6 +982,14 @@ type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) if (obj == NULL) return NULL; + /* Ugly exception: when the call was type(something), + don't call tp_init on the result. */ + if (type == &PyType_Type && + PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 && + (kwds == NULL || + (PyDict_Check(kwds) && PyDict_GET_SIZE(kwds) == 0))) + return obj; + /* If the returned object is not an instance of type, it won't be initialized. */ if (!PyType_IsSubtype(Py_TYPE(obj), type)) @@ -2395,6 +2379,29 @@ type_new_ex(PyTypeObject *metatype, PyObject *args, PyObject *kwds, int depth) assert(args != NULL && PyTuple_Check(args)); assert(kwds == NULL || PyDict_Check(kwds)); + /* Special case: type(x) should return x->ob_type */ + /* We only want type itself to accept the one-argument form (#27157) + Note: We don't call PyType_CheckExact as that also allows subclasses */ + if (metatype == &PyType_Type) { + const Py_ssize_t nargs = PyTuple_GET_SIZE(args); + const Py_ssize_t nkwds = kwds == NULL ? 0 : PyDict_GET_SIZE(kwds); + + if (nargs == 1 && nkwds == 0) { + PyObject *x = PyTuple_GET_ITEM(args, 0); + Py_INCREF(Py_TYPE(x)); + return (PyObject *) Py_TYPE(x); + } + + /* SF bug 475327 -- if that didn't trigger, we need 3 + arguments. but PyArg_ParseTuple below may give + a msg saying type() needs exactly 3. */ + if (nargs != 3) { + PyErr_SetString(PyExc_TypeError, + "type() takes 1 or 3 arguments"); + return NULL; + } + } + /* Check arguments: (name, bases, dict) */ if (!PyArg_ParseTuple(args, "UO!O!:type.__new__", &name, &PyTuple_Type, &bases, &PyDict_Type, &orig_dict)) From c8a471d4bea0024f12dad6c7ee9350b0fb5cc689 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 13 Oct 2019 13:58:53 +0300 Subject: [PATCH 4/4] Add a NEWS entry. --- .../2019-10-13-13-58-45.bpo-28869.vuckM1.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-10-13-13-58-45.bpo-28869.vuckM1.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-10-13-13-58-45.bpo-28869.vuckM1.rst b/Misc/NEWS.d/next/Core and Builtins/2019-10-13-13-58-45.bpo-28869.vuckM1.rst new file mode 100644 index 00000000000000..2082e0971c5fa9 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-10-13-13-58-45.bpo-28869.vuckM1.rst @@ -0,0 +1,4 @@ +:meth:`type.__new__` now skips one additional frame when determine the +default value for ``__module__`` if it was not called directly from +:meth:`type.__call__`. This fixes ``__module__`` of classes created by +calling a metaclass (like :class:`abc.ABCMeta`).