I'm reading through this blog, which contains:
Since in Python everything is an object, everything is the instance of a class, even classes. Well, type is the class that is instanced to get classes. So remember this: object is the base of every object, type is the class of every type. Sounds puzzling? It is not your fault, don't worry. However, just to strike you with the finishing move, this is what Python is built on.
>>> type(object)
<class 'type'>
>>> type.__bases__
(<class 'object'>,)
I'm having trouble understanding this. Can anyone explain this relationship in a different way to make it clearer?
The relationship between type(x)
is basically the same as the result of x.__class__
:
for obj in (object,type,1,str):
assert type(obj) is obj.__class__
print("type(obj) and obj.__class__ gave the same results in all test cases")
__bases__
represents the bases that a class is derived from:
class parent:
pass
class child(parent,int):
pass
print(child.__bases__) # prints (<class '__main__.parent'>, <class 'int'>)
however if you are asking about the odd relationship between object
and type
:
# are `object` and `type` both instances of the `object` class?
>>> isinstance(object, object) and isinstance(type, object)
True
# are they also instances of the `type` class?
>>> isinstance(object, type) and isinstance(type, type)
True
# `type` is a subclass of `object` and not the other way around (this makes sense)
>>> [issubclass(type, object), issubclass(object, type)]
[True, False]
that is more of a chicken vs. egg question: which came first?
The answer is PyObject
which is defined in C.
before either object
or type
is available to the python interpreter their underlying mechanisms are defined in C and the instance checking is overrided after they are defined. (act like abstract classes, see PEP 3119)
you can consider it something like this python implementation:
#this wouldn't be available in python
class superTYPE(type):
def __instancecheck__(cls,inst):
if inst ==TYPE:
return True
else:
return NotImplemented #for this demo
class TYPE(type,metaclass=superTYPE):
def __instancecheck__(cls,inst):
if inst in (OBJECT,TYPE):
return True
else:
return NotImplemented #for this demo
class OBJECT(metaclass=TYPE):
pass
# these all pass
assert isinstance(TYPE,OBJECT)
assert isinstance(OBJECT,TYPE)
assert isinstance(TYPE,TYPE)
assert isinstance(OBJECT,OBJECT)
actually it may be better represented as:
#this isn't available in python
class superTYPE(type):
def __instancecheck__(cls,inst):
if inst in (TYPE,OBJECT):
return True
else:
return NotImplemented #for this demo
class OBJECT(metaclass=superTYPE):
pass
class TYPE(OBJECT):
pass
but again if you want to know exactly how it works you would need to look at the source code written in C.
This is really weird and feels like turtles all the way down. I've actually not delved into this arena very much before, though it's something that sounded fun and powerful. This explanation was confusing, and so was the rest of the information on that page, but I feel like I have some enlightenment. Whether or not I can explain that clearly, I'm not sure, but I'll have a go.
Let's look at the turtles, first:
>>> isinstance(type, object)
True
>>> isinstance(object, type)
True
Wait, what?
How is object
an instance of type
, when type
is an instance of object
? That feels like saying something like:
class Parrot: pass
ex = Parrot()
isinstance(ex, Parrot)
isinstance(Parrot, ex)
Should be True
both times. But obviously it's not. Even (as Tadhg McDonald-Jensen pointed out)
>>> isinstance(type, type)
True
This should indicate to you that there is some magic going on behind the scenes. So at this point, let's just completely forget about Python (I know, why would we ever want to do such a horrible thing?)
In general, all computer programs are are 1's and 0's (and more accurately they're just a bunch of logic gates and electrons at >~2.5v and ~<2.5v, but 0's and 1's are good enough). Whether you wrote it in assembly, actual machine code, Python, C#, Java, Perl, whatever - they're all just bits.
If you write a class definition, that class is just bits. An instance of that class is just more bits. And a programming language and a compiler and an interpreter is just even more bits.
In the case of Python, it's the python
interpreter that gives meaning to the bits that are our Python programs. As an interesting point, a lot of what we typically consider to be Python is actually written in Python (though most of it is C, for us CPython folks, Java for Jython, etc.).
So now we come to this thing we call type
and object
. As the article points out, they're kind of special. So, we know that we can create a class, and then that class is an object:
>>> class Confusion: pass
...
>>> isinstance(Confusion, object)
Which makes sense, if you think about it - you may have created class-level variables:
>>> class Counter:
... count = 0
... def __init__(self):
... Counter.count += 1
... print(self.count)
...
>>> Counter()
1
<__main__.Counter object at 0x7fa03fca4518>
>>> Counter()
2
<__main__.Counter object at 0x7fa03fca4470>
>>> Counter()
3
<__main__.Counter object at 0x7fa03fca4518>
>>> Counter()
4
<__main__.Counter object at 0x7fa03fca4470>
>>> Counter.count
4
>>> Counter.__repr__(Counter)
'<type object at 0x1199738>'
But as this last example shows (and is mentioned in the post), a class declaration, what you get with class SomeClass: pass
, that declaration of a class is actually an instance of another class. In particular, it's an instance of the type
class. And that instance (which we call a class) when called will produce an instance of itself:
>>> Counter.__call__()
5
<__main__.Counter object at 0x7fa03fca4518>
So what does all this have to do with the relationship between type
and object
?
Well, somewhere, python
creates a series of bits that is object
, and a series of bits that is type
, and then wires them together in such a way that
>>> type.__bases__
(<class 'object'>,)
>>> object.__bases__
()
Because I currently don't feel like looking through the source, I'm going to make a guess that type
is created first, and object
is produced from that type and that type.__bases__
is set to (class 'object')
. By creating this circular relationships between type
and object
, it gives the appearance that it's just turtles all the way down, when really the last two turtles are just standing on top of each other.
I don't think there's really a better way to explain what's going on here than how the article describes it - at least in a classical OOP is-a/has-a style of thinking, because it's not actually that sort of thing. Like trying to plot a 3d figure in 2d space, you're going to have problems.
It's just two sets of bits that have some bits inside them that happen to be the address of one another.
The relationship between type() and object() is tightly interwoven.
Each is an instance of the other:
>>> isinstance(object, type)
True
>>> isinstance(object, object)
True
>>> isinstance(type, type)
True
>>> isinstance(type, object)
True
With respect to subclassing, a type is a kind of object, but not vice versa:
>>> issubclass(object, type)
False
>>> issubclass(type, object)
True
I think of object() as providing the baseline capabilities for all objects:
>>> dir(object)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
The only connection to type() is that __class__
is set to type(); otherwise the latter is not used at all:
>>> object.__class__
<class 'type'>
Then type() inherits from object(), but overrides several critical methods for creating classes:
for methname in dir(type):
if 'type' in repr(getattr(type, methname, '')):
print(methname)
__call__
__class__
__delattr__
__dict__
__dir__
__doc__
__getattribute__
__init__
__init_subclass__
__instancecheck__
__mro__
__name__
__new__
__or__
__prepare__
__qualname__
__repr__
__ror__
__setattr__
__sizeof__
__subclasscheck__
__subclasses__
__subclasshook__
mro
With these methods and attributes, type can now create classes. Classes aren't really special. They can just an instance of object with methods suitable for making subclasses and instances.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With