Home > Net >  Why is numeric type deleted and list type not, when they are attributes to a class instance (which g
Why is numeric type deleted and list type not, when they are attributes to a class instance (which g

Time:11-15

In the below code sample I would expect both a and b to be deleted at the end of each loop.

What happens is that a is deleted but not b. Why is that? And how can I make sure that b is also deleted at the end of each loop?

class bag: 
    a = 5
    b = []
    
    def funcie(self): 
        self.a = self.a   1  
        self.b.append(1)



for i in range(5): 
    inst = bag()
    inst.funcie()
    print(inst.a)
    print(inst.b)
    # del inst

output:

6
[1]
6
[1, 1]
6
[1, 1, 1]
6
[1, 1, 1, 1]
6
[1, 1, 1, 1, 1]

EDIT: So this post explains why the b list keeps growing in each loop i.e. I should have declared the b list in the __init(self)__ function. However it doesn't explain why the a variable gets overwritten at the end of each loop whilst the b variable doesn't.

CodePudding user response:

bag.a (a class attribute) is not being overridden, it's being shadowed by the instance's a (an instance attribute) for inst specifically.

Python's general rule is that reading will read from outer/shadowed scopes if there is no inner/shadowing scope hiding it. An inner/shadowing scope is created by assignment, not merely mutation (which is just reading from the variable then asking it to mutate itself). There are some subtle distinctions between how scopes and attributes work, so I'm going to focus on attributes only (since that's what you asked about).

When you do self.a = self.a 1 on a new instance of bag, the self.a you read comes from the class attribute, but on writing to self.a, you create a new shadowing instance attribute. The class attribute (bag.a == 5) still exists, and is unmodified, but from the instance's point of view, it only sees the instance attribute (inst.a == 6). If you added print(bag.a), you'd see if never changed.

By contrast, self.b.append(1) reads the class attribute and asks it to modify itself in place, which changes object bound to the class attribute itself.

If you want to modify a class attribute without creating an instance attribute, you have to operate on the class itself. For example, you could change:

self.a = self.a   1

to:

type(self).a = self.a   1  # Or type(self).a = type(self).a   1

or more simply use = to avoid repeating more complex stuff:

type(self).a  = 1

CodePudding user response:

Numbers differ from the list in that Python uses their value and stores the result of computations in a new place. So the result of a = a 1 is stored in a different location in memory each time. Lists remain in the same memory location and are updated in place. The following code:

class bag: 
    a = 5
    b = []
    
    def funcie(self): 
        self.a  = 1  
        self.b.append(1)

inst = bag()
print("bag memory ids:     ", id(bag.a), id(bag.b))
print("inst memory ids:    ", id(inst.a), id(inst.b))
inst.funcie()
print("ids after 1x funcie:", id(inst.a), id(inst.b))
inst.funcie()
print("ids after 2x funcie:", id(inst.a), id(inst.b))

has this output, where you see the id of inst.a changing, while inst.b retains the same id:

bag memory ids:      9789120 139707042123328
inst memory ids:     9789120 139707042123328
ids after 1x funcie: 9789152 139707042123328
ids after 2x funcie: 9789184 139707042123328

So the updated value of a is stored in a different location, without altering the original value.

  • Related