[issue27157] Unhelpful error message when one calls a subclass of type with a custom metaclass

Eryk Sun report at bugs.python.org
Sun May 29 23:17:17 EDT 2016


Eryk Sun added the comment:

All types are instances of `type`, so the single argument case makes sense to me as a 'constructor'. It always returns an instance of `type`, just not a new instance. 

    >>> X = type('X', (type,), {})
    >>> type(X)
    <class 'type'>
    >>> isinstance(type(X), type)
    True

OTOH, implementing this for subclasses of `type` doesn't generally make sense to me. That this is allowed (sometimes) is I think a mistake:

    >>> X(X)
    <class 'type'>
    >>> isinstance(X(X), X)
    False

PyType_CheckExact(metatype) isn't checking that metatype is `type`. It's checking that the type of metatype is exactly `type`, which is true for `type` and immediate instances of type, i.e. normal metaclasses. But it's not the case for a metaclass that's an instance of another metaclass:

    >>> Y = X('Y', (X,), {})
    >>> Y(Y)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: type() takes 1 or 3 arguments

Maybe I'm missing something, but it makes more sense to me if metatype is required to be *exactly* `type`, i.e. metatype == &PyType_Type, and that this check is used to gate the entire special case:

    /* Special case: type(x) should return x->ob_type */
    if (metatype == &PyType_Type) {
        const Py_ssize_t nargs = PyTuple_GET_SIZE(args);
        const Py_ssize_t nkwds = kwds == NULL ? 0 : PyDict_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_ParseTupleAndKeywords below may give
           a msg saying type() needs exactly 3. */
        if (nargs + nkwds != 3) {
            PyErr_SetString(PyExc_TypeError,
                            "type() takes 1 or 3 arguments");
            return NULL;
        }
    }

This change yields the following behavior:

    >>> X = type('X', (type,), {})

    >>> type(X)
    <class 'type'>

    >>> type(1, 2)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: type() takes 1 or 3 arguments

    >>> X()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: Required argument 'name' (pos 1) not found

    >>> X(X)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: type() argument 1 must be str, not type

----------
nosy: +eryksun

_______________________________________
Python tracker <report at bugs.python.org>
<http://bugs.python.org/issue27157>
_______________________________________


More information about the Python-bugs-list mailing list