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?
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 id
s 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]