Think of it this way. Say you create your own metaclass (class of a class), and a subclass of your metaclass (which is itself just a class):
>>> class MyMeta(type): # <-- a custom metaclass, similar to function
pass
>>> print(MyMeta.__name__)
MyMeta
>>> print(__class__)
<class 'type'>
>>> class MyClass(metaclass = MyMeta):
pass
>>> print(MyClass.__class__)
<class 'MyMeta'>
Now, we will delete the identifier MyMeta:
>>> del MyMeta
Can you still get to the metaclass object that was represented by MyMeta although MyMeta has been deleted? Sure:
>>> print(MyClass.__class__)
<class 'MyMeta'>
So there is still a reference to the metaclass object in the MyClass dict.
However, the name MyMeta is now invalid, since it was deleted:
>>> class MyClassTwo(metaclass = MyMeta):
pass
NameError!!
>>> print(MyMeta.__name__)
NameError!!
>>> print(MyMeta)
NameError!!
IMPORTANT: The metaclass name has been deleted, not the metaclass object itself.
Therefore you can still access the metaclass object this way:
>>> class MyClassTwo(metaclass = MyClass.__class__):
pass
So it is with the function name (which is itself kind of like a built-in metaclass; i.e., it is the class, or type, of function objects)- by default, the name function doesn't exist in an interactive Python session. But the object does still exist. You can access it this way:
>>> def f(): pass
>>> f.__class__.__class__
<class 'type'>
And if you like, you can even go ahead and assign the name function to the function <class 'type'> object (although there's not much reason to do that):
>>> function = f.__class__
>>> print(function)
<class 'function'>
>>> print(function.__class__)
<class 'type'>
functionis not a defined name, which is why it throws an exception. What you wanted to do wasfunction = T1.__class__; function.__class__orT1.__class__.__class_.