简体   繁体   中英

Why the class attribute does not always appear as key in the instance's dictionary?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM