close
Skip to content
7 changes: 7 additions & 0 deletions Lib/test/test_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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`).
70 changes: 52 additions & 18 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,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 depth);

/*
* finds the beginning of the docstring's introspection signature.
* if present, returns a pointer pointing to the first '('.
Expand Down Expand Up @@ -995,7 +1001,12 @@ type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
return NULL;
}

obj = type->tp_new(type, args, kwds);
if (type->tp_new == type_new) {
obj = type_new_ex(type, args, kwds, 0);
}
else {
obj = type->tp_new(type, args, kwds);
}
obj = _Py_CheckFunctionResult(tstate, (PyObject*)type, obj, NULL);
if (obj == NULL)
return NULL;
Expand Down Expand Up @@ -2344,8 +2355,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;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I am missing something, depth can be only either 0 or 1, maybe it is better to use a name like skip or nested and write this as:

if (skip_frame && 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(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
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;
Expand Down Expand Up @@ -2608,22 +2650,8 @@ 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 (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
Expand Down Expand Up @@ -2845,6 +2873,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, 1);
}

PyObject *
PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
{
Expand Down