In [75]:
a = 1
print(type(a))
In [76]:
print(type(int))
This shows that the int
class is an object, an instance of the type
class.
This concept is not so difficult to grasp as it can seem at first sight: in the real world we deal with concepts using them like things: for example we can talk about the concept of "door", telling people how a door looks like and how it works. In this case the concept of door is the topic of our discussion, so in our everyday experience the type of an object is an object itself. In Python this can be expressed by saying that everything is an object.
If the class of an object is itself an instance it is a concrete object and is stored somewhere in memory. Let us leverage the inspection capabilities of Python and its id()
function to check the status of our objects. The id()
built-in function returns the memory position of an object.
In the first post we defined this class
In [77]:
class Door:
def __init__(self, number, status):
self.number = number
self.status = status
def open(self):
self.status = 'open'
def close(self):
self.status = 'closed'
First of all, let's create two instances of the Door
class and check that the two objects are stored at different addresses
In [78]:
door1 = Door(1, 'closed')
door2 = Door(1, 'closed')
In [79]:
hex(id(door1))
Out[79]:
In [80]:
hex(id(door2))
Out[80]:
This confirms that the two instances are separate and unrelated. The second instance was given the same attributes of the first instance to show that the two are different objects regardless of the value of the attributes.
However if we use id()
on the class of the two instances we discover that the class is exactly the same
In [81]:
hex(id(door1.__class__))
Out[81]:
In [82]:
hex(id(door2.__class__))
Out[82]:
Well this is very important. In Python, a class is not just the schema used to build an object. Rather, the class is a shared living object, which code is accessed at run time.
As we already tested, however, attributes are not stored in the class but in every instance, due to the fact that __init__()
works on self
when creating them. Classes, however, can be given attributes like any other object; with a terrific effort of imagination, let's call them class attributes.
As you can expect, class attributes are shared among the class instances just like their container
In [83]:
class Door:
colour = 'brown'
def __init__(self, number, status):
self.number = number
self.status = status
def open(self):
self.status = 'open'
def close(self):
self.status = 'closed'
Pay attention: the colour
attribute here is not created using self
, so it is contained in the class and shared among instances
In [84]:
door1 = Door(1, 'closed')
door2 = Door(2, 'closed')
In [85]:
Door.colour
Out[85]:
In [86]:
door1.colour
Out[86]:
In [87]:
door2.colour
Out[87]:
Until here things are not different from the previous case. Let's see if changes of the shared value reflect on all instances
In [88]:
Door.colour = 'white'
Door.colour
Out[88]:
In [89]:
door1.colour
Out[89]:
In [90]:
door2.colour
Out[90]:
In [91]:
hex(id(Door.colour))
Out[91]:
In [92]:
hex(id(door1.colour))
Out[92]:
In [93]:
hex(id(door2.colour))
Out[93]:
In [94]:
door1 = Door(1, 'closed')
door2 = Door(2, 'closed')
print(type(Door.__dict__))
Door.__dict__
Out[94]:
In [95]:
print(type(door1.__dict__))
door1.__dict__
Out[95]:
Leaving aside the difference between a dictionary and a mappingproxy
object, you can see that the colour
attribute is listed among the Door
class attributes, while status
and number
are listed for the instance.
How comes that we can call door1.colour
, if that attribute is not listed for that instance? This is a job performed by the magic __getattribute__()
method; in Python the dotted syntax automatically invokes this method so when we write door1.colour
, Python executes door1.__getattribute__('colour')
. That method performs the attribute lookup action, i.e. finds the value of the attribute by looking in different places.
The standard implementation of __getattribute__()
searches first the internal dictionary (__dict__
) of an object, then the type of the object itself; in this case door1.__getattribute__('colour')
executes first door1.__dict__['colour']
and then, since the latter raises a KeyError
exception, door1.__class__.__dict__['colour']
In [96]:
try:
door1.__dict__['colour']
except KeyError as e:
print("Cannot find key {}".format(e))
In [97]:
door1.__class__.__dict__['colour']
Out[97]:
Indeed, if we compare the objects' equality through the is
operator we can confirm that both door1.colour
and Door.colour
are exactly the same object
In [98]:
door1.colour is Door.colour
Out[98]:
When we try to assign a value to a class attribute directly on an instance, we just put in the __dict__
of the instance a value with that name, and this value masks the class attribute since it is found first by __getattribute__()
. As you can see from the examples of the previous section, this is different from changing the value of the attribute on the class itself.
In [99]:
door1.colour = 'white'
door1.__dict__['colour']
Out[99]:
In [100]:
door1.__class__.__dict__['colour']
Out[100]:
In [101]:
Door.colour = 'red'
door1.__dict__['colour']
Out[101]:
In [102]:
door1.__class__.__dict__['colour']
Out[102]:
In [103]:
door1.open is Door.open
Out[103]:
Whoops. Let us further investigate the matter
In [104]:
Door.__dict__['open']
Out[104]:
In [105]:
Door.open
Out[105]:
In [106]:
door1.open
Out[106]:
So, the class method is listed in the members dictionary as function. So far, so good. The same happens when taking it directly from the class; here Python 2 needed to introduce unbound methods, which are not present in Python 3. Taking it from the instance returns a bound method.
Well, a function is a procedure you named and defined with the def
statement. When you refer to a function as part of a class in Python 3 you get a plain function, without any difference from a function defined outside a class.
When you get the function from an instance, however, it becomes a bound method. The name method simply means "a function inside an object", according to the usual OOP definitions, while bound signals that the method is linked to that instance. Why does Python bother with methods being bound or not? And how does Python transform a function into a bound method?
First of all, if you try to call a class function you get an error
In [107]:
try:
Door.open()
except TypeError as e:
print(e)
Yes. Indeed the function was defined to require an argument called 'self', and calling it without an argument raises an exception. This perhaps means that we can give it one instance of the class and make it work
In [108]:
Door.open(door1)
door1.status
Out[108]:
Python does not complain here, and the method works as expected. So Door.open(door1)
is the same as door1.open()
, and this is the difference between a plain function coming from a class an a bound method: the bound method automatically passes the instance as an argument to the function.
Again, under the hood, __getattribute__()
is working to make everything work and when we call door1.open()
, Python actually calls door1.__class__.open(door1)
. However, door1.__class__.open
is a plain function, so there is something more that converts it into a bound method that Python can safely call.
When you access a member of an object, Python calls __getattribute__()
to satisfy the request. This magic method, however, conforms to a procedure known as descriptor protocol. For the read access __getattribute__()
checks if the object has a __get__()
method and calls this latter. So the converstion of a function into a bound method happens through such a mechanism. Let us review it by means of an example.
In [109]:
door1.__class__.__dict__['open']
Out[109]:
This syntax retrieves the function defined in the class; the function knows nothing about objects, but it is an object (remember "everything is an object"). So we can look inside it with the dir()
built-in function
In [110]:
dir(door1.__class__.__dict__['open'])
Out[110]:
In [111]:
door1.__class__.__dict__['open'].__get__
Out[111]:
As you can see, a __get__
method is listed among the members of the function, and Python recognizes it as a method-wrapper. This method shall connect the open
function to the door1
instance, so we can call it passing the instance alone
In [112]:
door1.__class__.__dict__['open'].__get__(door1)
Out[112]:
In [113]:
Door.open
Out[113]:
In [114]:
door1.open
Out[114]:
In [115]:
type(Door.open)
Out[115]:
In [116]:
type(door1.open)
Out[116]:
As you can see, Python tells the two apart recognizing the first as a function and the second as a method, where the second is a function bound to an instance.
What if we want to define a function that operates on the class instead of operating on the instance? As we may define class attributes, we may also define class methods in Python, through the classmethod
decorator. Class methods are functions that are bound to the class and not to an instance.
In [117]:
class Door:
colour = 'brown'
def __init__(self, number, status):
self.number = number
self.status = status
@classmethod
def knock(cls):
print("Knock!")
def open(self):
self.status = 'open'
def close(self):
self.status = 'closed'
Such a definition makes the method callable on both the instance and the class
In [118]:
door1 = Door(1, 'closed')
door1.knock()
In [119]:
Door.knock()
and Python identifies both as (bound) methods
In [120]:
door1.__class__.__dict__['knock']
Out[120]:
In [121]:
door1.knock
Out[121]:
In [122]:
Door.knock
Out[122]:
In [135]:
print(type(Door.knock))
In [136]:
print(type(door1.knock))
As you can see the knock()
function accepts one argument, which is called cls
just to remember that it is not an instance but the class itself. This means that inside the function we can operate on the class, and the class is shared among instances.
In [125]:
class Door:
colour = 'brown'
def __init__(self, number, status):
self.number = number
self.status = status
@classmethod
def knock(cls):
print("Knock!")
@classmethod
def paint(cls, colour):
cls.colour = colour
def open(self):
self.status = 'open'
def close(self):
self.status = 'closed'
The paint()
classmethod now changes the class attribute colour
which is shared among instances. Let's check how it works
In [126]:
door1 = Door(1, 'closed')
door2 = Door(2, 'closed')
Door.colour
Out[126]:
In [127]:
door1.colour
Out[127]:
In [128]:
door2.colour
Out[128]:
In [129]:
Door.paint('white')
Door.colour
Out[129]:
In [130]:
door1.colour
Out[130]:
In [131]:
door2.colour
Out[131]:
The class method can be called on the class, but this affects both the class and the instances, since the colour
attribute of instances is taken at runtime from the shared class.
In [132]:
door1.paint('yellow')
Door.colour
Out[132]:
In [133]:
door1.colour
Out[133]:
In [134]:
door2.colour
Out[134]:
Class methods can be called on instances too, however, and their effect is the same as before. The class method is bound to the class, so it works on this latter regardless of the actual object that calls it (class or instance).
Section titles come from the following movies: The Empire Strikes Back (1980), Raiders of the Lost Ark (1981), Revenge of the Nerds (1984), When Harry Met Sally (1989).
You will find a lot of documentation in this Reddit post. Most of the information contained in this series come from those sources.
Feel free to use the blog Google+ page to comment the post. The GitHub issues page is the best place to submit corrections.