Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
356 views
in Technique[技术] by (71.8m points)

Python class attribute inconsistency

I am trying to understand how class attributes work in Python. I have confusion based on following example.

#!/usr/bin/python3

class Bag:
    val = 100
    items = []

    def add(self, x):
        self.items.append(x)
        print(self.items)

b1 = Bag()
b1.add('book')
b1.val += 1
print(b1.val)

b1.add('pen')
b1.val += 1
print(b1.val)

b2 = Bag()
b2.add('sketches')
b2.val += 1
print(b2.val)

b2.add('text')
b2.val += 1
print(b2.val)

b2.add('canvas')
b2.val += 1
print(b2.val)

Output expected:

['book']
101
['book', 'pen']
102
['book', 'pen', 'sketches']
103
['book', 'pen', 'sketches', 'text']
104
['book', 'pen', 'sketches', 'text', 'canvas']
105

Output seen:

['book']
101
['book', 'pen']
102
['book', 'pen', 'sketches']
101
['book', 'pen', 'sketches', 'text']
102
['book', 'pen', 'sketches', 'text', 'canvas']
103

Why is there inconsistency between list being shared vs different copies of integers?

Here is another example that shows a different behavior for int.

#!/usr/bin/python3

class Person:
    cl_roll = 0

    def __init__(self, name):
        self.name = name
        self.roll = self.next_roll()
        print(self.roll, self.name)

    @classmethod
    def next_roll(cls):
        cls.cl_roll += 1
        return cls.cl_roll

p1 = Person('Eve')
p2 = Person('Abel')
p3 = Person('Eva')

Output expected based on previous output:

1 Eve
1 Abel
1 Eva

Actual output:

1 Eve
2 Abel
3 Eva
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I will extend your example a bit

class Bag:
   items = []
   items1 = []
   val = 100
   def add(self, x):
       self.items.append(x)
       self.val += 1
       self.items1 += [x]


b1 = Bag()
print(Bag.__dict__)
#op-1>>> 'items': [], 'items1': [], 'val': 100,
print(b1.__dict__)
#op-2>>> {}

b1.add(111)
print(Bag.__dict__)
#op-3>>> {'items': [111], 'items1': [111], 'val': 100}
print(b1.__dict__)
#op-4>>>{'items1': [111], 'val': 101}

To go step-by-step:

  1. self.items.append(x):

    First, python tries to find if we have a items in the object (self.__dict__), if not then it tries to find items from the class scope and appends it. Nothing is returned as part of this expression. self.__dict__ is untouched after this expression.

  2. self.val += 1

    This is augmented assignment for int. So __iadd__ will be called, if this is not implemented 's __add__ will be called which will always return a new int. The old int is not changed in-place because it is immutable. To elaborate

    self.val = self.val + 1

    The first time self.val on the rhs refers to the class attribute (since b1.dict does not have it) and it creates a new int which is now stored in the object's __dict__ (because of lhs).. The second time self.val in the rhs refers to the val in self.__dict__ Instead if you would have done Bag.val += 1, then it will always manipulate the class variable (like your second example)

  3. self.items1 += [x]

So this is also augmented addition of the list.__iadd__. self.items1 is changed in-place for mutable sequences and the reference to the same list is also returned as part of this expression. So, after this statement, you should see that self.__dict__ will contain items1 but with same contents as Bag.__dict__['items1'].

Your second example is altogether different:

cls.cl_roll += 1 

this statement always manipulates the class variable.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...