1đź‘Ť
This is definitely a tricky one, but I think I have figured out what is going on here. The metaclass is taking all of the class attributes that are “fields” and converting them to properties.
Now normally when you assign to an attribute on an instance it will always be an instance attribute, even if there is also a class attribute with the same name. However if your class attribute is a property with a setter defined, then assigning to that attribute (whether it is from the class or an instance) will call the setter function. The end result here is that assigning to a class property through an instance will result in that property being changed for all instances.
The easiest way to think of this is that assigning to a property isn’t really an assignment, it is a mutation. It shouldn’t be too surprising that in the following code f1.foo
and f2.foo
are the same object and modification to one will modify the other:
class Foo(object):
test = []
f1 = Foo()
f2 = Foo()
f1.test.append(1) # f1.test is actually Foo.test, so class attribute changes
print f2.test # f2.test is also Foo.test, so this prints [1]
The same principle applies to properties and descriptors, for example:
class Test(object):
def __init__(self, val=None):
self.val = val
def __get__(self, obj, type=None):
return self.val
def __set__(self, obj, val):
self.val = val
class Foo(object):
test = Test()
f1 = Foo()
f2 = Foo()
f1.test = 'abc' # calls Foo.test.__set__
print f2.test # prints 'abc', because Foo.test was modified above