scope strangeness in python class attributes

Question:

Came across what seems to be a weird case of ‘spooky action at a distance’ using python class attributes.

If I define X as:

class X():
    a = list()
    b = int
    def __init__(self, value):
        self.a.append(value)
        self.b = value

Then instantiate:

u = X(0)
v = X(1)

It outputs the following strangeness:

u.a == [0,1]
u.b == 0  
v.a == [0,1]
v.b == 1

As if the list() from "a" is acting as a shared class attribute while the int from "b" only as an instance attribute. I mean, why would types affect the scope of an attribute ? what am i missing here?

Asked By: tikitakitok

||

Answers:

The difference isn’t the type, it’s the way you’re rebinding the attributes (or not).

    def __init__(self, value):
        self.a.append(value)
        self.b = value

In this code, self.a is not being rebound; the object it references is being mutated. Hence self.a remains a reference to the (shared) class attribute, X.a.

self.b is being rebound, and so a new instance attribute is created that shadows the class attribute X.b. Note that X.b is the int type, not an actual int!

>>> X.b
<class 'int'>
>>> u.b
0
>>> v.b
1

The id function can be used to verify that there is only a single X.a object:

>>> id(X.a)
2052257864960
>>> id(u.a)
2052257864960
>>> id(v.a)
2052257864960

whereas obviously the ids for the b attributes are all different:

>>> id(X.b)
140711576840944
>>> id(u.b)
2052256170192
>>> id(v.b)
2052256170224

If you wanted a class that mutates its class attributes every time a new instance is created (note: this is kind of a weird thing to do), you can do that by making sure you’re modifying the type of self rather than the self instance:

class X():
    # Initializing a and b with literal list and int values.
    # This is more idiomatic than doing stuff like list() and int().
    a = []
    b = 0
    def __init__(self, value):
        # Using type(self) as a way to reference X (or a subclass) explicitly.
        # As discussed, this isn't necessary when we're doing self.a.append,
        # but using type() consistently makes it obvious that we're
        # dealing with class attributes, not instance attributes.
        type(self).a.append(value)
        type(self).b = value

Trying it out:

>>> X(0)
<__main__.X object at 0x000001DDD416AE90>
>>> X(1)
<__main__.X object at 0x000001DDD416B970>
>>> X.b
1
>>> X.a
[0, 1]
Answered By: Samwise
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.