Search code examples
pythonobjecttypesinstancesubclass

Chicken or Egg paradox with type and object in Python


I remembered learning that type is its own instance and that all new-style classes inherit from object, so I started messing around:

issubclass(type, object) # True
issubclass(object, type) # False
# issubclass(cls, cls) is always True
type(type) is type # True
type(object) is type # True

To sum that up in words, type like any new-style class is a subclass of object, and like any class, both type and object are instances of type. Here's a handy picture:

Picture of relationship between type and object in Python

Big disclaimer here that I'm not a programmer and know little about the underlying mechanics of Python, so bear with me if I sound dumb. Normally a class has to be made before its instances. However, a superclass normally has to be made before its subclass. Applying those general Python rules to type and object makes an apparent chicken-egg paradox.

So which of these classes are made first? I also suspect that because these classes are implemented in a lower-level language, they don't have to follow Python rules.


Solution

  • From a language semantics perspective, both type and object exist, fully initialized, from the moment the program begins. Whatever the implementation does to get things into that state doesn't have to follow the rules of what the implementation allows you to do.

    From a CPython implementation perspective, both type and object are statically allocated at C level, and neither is created first. You can see the variable definitions in Objects/typeobject.c. Here's object:

    PyTypeObject PyBaseObject_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "object",                                   /* tp_name */
        sizeof(PyObject),                           /* tp_basicsize */
        0,                                          /* tp_itemsize */
        object_dealloc,                             /* tp_dealloc */
        0,                                          /* tp_print */
        0,                                          /* tp_getattr */
        0,                                          /* tp_setattr */
        0,                                          /* tp_reserved */
        object_repr,                                /* tp_repr */
        ...
    };
    

    and here's type:

    PyTypeObject PyType_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "type",                                     /* tp_name */
        sizeof(PyHeapTypeObject),                   /* tp_basicsize */
        sizeof(PyMemberDef),                        /* tp_itemsize */
        (destructor)type_dealloc,                   /* tp_dealloc */
        0,                                          /* tp_print */
        0,                                          /* tp_getattr */
        0,                                          /* tp_setattr */
        0,                                          /* tp_reserved */
        (reprfunc)type_repr,                        /* tp_repr */
        ...
    };
    

    When interpreter initialization begins, both type and object are in a half-initialized state, and the PyType_Ready function is responsible for finishing their initialization. The type pointers are set up in the variable definitions, but setting the superclass pointers is part of PyType_Ready's job, and plenty of other initialization needs to be handled by PyType_Ready - for example, the types don't have a __dict__ yet.

    Incidentally, using some weird metaclasses and the fact that Python allows you to reassign __class__ on instances of user-defined classes, we can set up our own classes A and B where B is a subclass of A and both A and B are instances of B, much like the situation with object and type. This is nothing like how object and type are actually created, though:

    class DummyMeta(type):
        pass
    
    class A(type, metaclass=DummyMeta):
        pass
    
    class B(A):
        pass
    
    B.__class__ = B
    A.__class__ = B
    
    print(isinstance(A, B))
    print(isinstance(B, B))
    print(issubclass(B, A))
    

    Output:

    True
    True
    True