I was reading about OOP in python on this section about Attributes and was literally shocked by the below example.
I am not getting why the dictionary of an instance (whose class
is given a new attribute) is empty:
class Robot(object):
pass
x = Robot()
Robot.brand = "Kuka"
x.brand
Output:
'Kuka'
x.brand = "Thales"
Robot.brand
Output:
'Kuka'
y = Robot()
y.brand
Output:
'Kuka'
Robot.brand = "Thales"
y.brand
Output:
'Thales'
x.brand
Output:
'Thales'
If you look at the __dict__
dictionaries, you can see what's happening:
x.__dict__
Output:
{'brand': 'Thales'}
y.__dict__
Output:
{}
Another quote from same website:
If you try to access y.brand, Python checks first, if "brand" is a key of the y.
__dict__
dictionary. If it is not, Python checks if "brand" is a key of the Robot.__dict__
. If so, the value can be retrieved.
My question is: Why does y.__dict__
give an empty dictionary? What is the logic and mechanism behind it?
Why would it have one? Instances do not automatically copy class attributes (that would be unexpected and inefficient). Your y
object doesn't have any instance attributes ( y.brand
is simply a proxy to Robot.brand
because of the quote from the documentation you posted in your question), hence its .__dict__
is empty.
Looking at this tutorial on __dict__
, we can see the following code sample (slightly modified from original):
class MyClass(object):
class_var = 1
def __init__(self, i_var):
self.i_var = i_var
foo = MyClass(2)
bar = MyClass(3)
print(foo.__dict__)
print(bar.__dict__)
With the following output:
{'i_var': 2}
{'i_var': 3}
As you can see, the output of foo.__dict__
and bar.__dict__
do not include class_var
. This is the case with all classes; __dict__
does not list class-wide attributes.
Looking at your entire program:
class Robot(object):
pass
x = Robot()
Robot.brand = "Kuka"
x.brand
x.brand = "Thales"
Robot.brand
y = Robot()
y.brand
Robot.brand = "Thales" # Sets class variable
y.brand
x.brand
x.__dict__
y.__dict__
We can see that, at the time x
and y
were created, Robot.brand
was set to "Kuka"
. We also see that there was an explicit declaration setting x.brand
to "Thales"
; however, it does not include a y.brand = "Whatever"
declaration.
In other words, the value of y.brand
is inherited from the class attribute Robot.brand
, and class attributes are not listed with __dict__
. On the other hand, x.brand
is specifically set to "Thales"
, so x.brand
is now referring to an object-specific variable, meaning it will show up in a __dict__
listing.
When you attempt to access a method or attribute in an instance, then python first asks the instance if the instance has such a thing. If not, then python asks the parent if the parent has that thing, etc. Python continues climbing the layers of inheritance till it finds something or it raises and error. For example,
class A:
x = 9
"x" is a class variable and exists with the class. Hence, ALL instances have "x" attribute automatically, unless they override their own attribute.
I = A()
print(I.x) #prints 9, python asks the parent for "x" since I didn't have an "x"
I.x = 10
print(I.x) #prints 10, python returns what I already has, doesn't ask parent
Z = A()
print(Z.x) #prints 9, python asks the parent for "x" since Z didn't have an "x"
This is very memory efficient. For example, this means that all instances share a "global" (global with respect to the class) variable. Hence, only one copy of Ax is written in memory. The only time python writes another "x" value in memory is if an instance changed their "x" value explicitly. So generating a million instances of A does not make a million copies of the value at Ax
We can use id(<object>)
to check what's going on.
Declaration of a new empty class Robot
class Robot(object):
... pass
...
Robot
<class '__main__.Robot'>
Robot.__dict__
mappingproxy({'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Robot' objects>, '__weakref__': <attribute '__weakref__' of 'Robot' objects>, '__doc__': None})
New instance of Robot
class
x = Robot()
x
<__main__.Robot object at 0x0000022811C8A978>
The x
instance is empty
x.__dict__
{}
New class property brand
is defined
Robot.brand = "Kuka"
Robot.brand
'Kuka'
If we try to access x.brand
, Python will look for brand
in x.__dict__
, find nothing so it goes to Robot.__dict__
and find the class property brand
.
x.brand
'Kuka'
We can verify that we are actually seen the same
id(Robot.brand)
2371120205752
id(x.brand)
2371120205752
New instance property brand
is defined
x.brand = "Thales"
And the class property brand
remains unaltered
Robot.brand
'Kuka'
We can verify that we are actually seen two different properties
id(x.brand)
2371119992200
id(Robot.brand)
2371120205752
New instance y
is created and is empty
y = Robot()
y.__dict__
{}
We can verify that is a new one:
id(y)
2371119989200
If we try to access y.brand
, Python will look for brand
in y.__dict__
, found nothing so then goes to Robot.__dict__
and find the class property brand
.
y.brand
'Kuka'
And we can verify that the id is the same of Robot.brand
. So y
is a reference to Robot.brand
id(y.brand)
2371120205752
If we modify class property brand
Robot.brand = "Thales"
id(Robot.brand)
2371119992200
y.brand
is modified because at this moment isn't an instance property but a reference to class property Robot.brand
. We can check the id of y.brand
is the same as Robot.brand
.
y.brand
'Thales'
id(y.brand)
2371119992200
Now we can check that x
has an instance property x.brand
x.brand
'Thales'
x.__dict__
{'brand': 'Thales'}
but y has nothing because is just a reference to Robot.brand
y.__dict__
{}
and Robot.brand has the class property brand
with value Thales
Robot.__dict__
mappingproxy({'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Robot' objects>, '__weakref__': <attribute '__weakref__' of 'Robot' objects>, '__doc__': None, 'brand': 'Thales'})
See additional notes here: https://github.com/leocjj/0123/blob/master/Python/0123P_9_Classes_objects.txt
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.