This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author eryksun
Recipients abarry, eryksun, ppperry, r.david.murray, steven.daprano
Date 2016-05-30.03:17:16
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1464578237.33.0.0785742202227.issue27157@psf.upfronthosting.co.za>
In-reply-to
Content
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
History
Date User Action Args
2016-05-30 03:17:17eryksunsetrecipients: + eryksun, steven.daprano, r.david.murray, ppperry, abarry
2016-05-30 03:17:17eryksunsetmessageid: <1464578237.33.0.0785742202227.issue27157@psf.upfronthosting.co.za>
2016-05-30 03:17:17eryksunlinkissue27157 messages
2016-05-30 03:17:16eryksuncreate