Reputation: 11614
In my endeavours as a python-apprentice i got recently stuck at some odd (from my point of view) behaviour if i tried to work with class attributes. I'm not complaining, but would appreciate some helpful comments to shed some light on this issue.
To reduce a complex matter into a more concise question i would formulate it like this:
What is the "pythonic" way to ensure that a class-attribute behaves more like a static variable in an inheritance tree?
It seems to me like a class-attribute behaves like a "copy on read" default value with polymorphic characteristics. As long as i do "read-only" operations it stays a "singleton", but as soon, as i access the class-attribute with an assignment through the derived class or instance it gets morphed into a new reference loosing the relation to the inherited base-reference.
(It has sure potential for some interessting features, but you have to understand it to embrace it, so some insight is highly appreciated.)
class A(object):
classvar = 'A'
def setclassvar(self, value):
A.classvar = value
def __str__(self):
return "%s id(%s) " %(A.classvar, hex(id(A.classvar))[2:-1].upper())
class A1(A):
pass
class B(object):
classvar = 'B'
def setclassvar(self, value):
self.__class__.classvar = value
def __str__(self):
cvar = self.__class__.classvar
return "%s id(%s) " %(cvar, hex(id(cvar))[2:-1].upper())
class B1(B):
def setclassvar(self, value):
self.__class__.classvar = value
a, a1 = A(), A1()
a1.setclassvar('a')
print "new instance A: %s" %a
print "new instance A1: %s" %a
b, b1 = B(), B1()
b1.setclassvar('bb')
print "new instance B: %s" %b
print "new instance B1: %s" %b1
a1.setclassvar('aa')
print "new value a1: %s" %a
print "new value a: %s" %a
a1.classvar = 'aaa'
print "direct access a1: %s id(%s)" %(a1.classvar, hex(id(a1.classvar))[2:-1].upper())
print "method access a1: %s" %a1
print "direct access a: %s" %a
produces the following:
new instance A: a id(B73468A0) new instance A1: a id(B73468A0) new instance B: B id(B73551C0) new instance B1: bb id(AD1BFC) new value a1: aa id(AD1BE6) new value a: aa id(AD1BE6) direct access a1: aaa id(A3A494) method access a1: aa id(AD1BE6) direct access a: aa id(AD1BE6)
So either the direct (assigning) access object.classvar
or mediated through self.__class__.classvar
are not the same as BASECLASS.classvar
.
Is this a scope issue or somethin totaly different.
Looking forward to your answers and thanks in forward. :-)
Edit: There was an answer for a very short time suggesting the use of class-descriptors like: How to make a class property?.
Unfortunatly that doesn't seem to work:
class Hotel(Bar):
def __init__(self):
Hotel.bar += 1
hotel = Hotel()
assert hotel.bar == 51
assert hotel.bar == foo.bar
The 2nd assertion fails! hotel.bar doesn't reference the same object as foo.bar
and hotel.bar
references somethin other then Hotel.bar!
2nd Edit: I'm quite aware that singletons are considered an "antipattern" and i didn't intend to use them (extensivly). Therefore i didn't mention them in the question-titel. Even so there are many solutions discussing and providing solutions with and about singletons, my question stays: Why can a class-variable detach it's reference so easily? Ruby behaves more the way it feels natural to me: http://snippets.dzone.com/posts/show/6649
Upvotes: 7
Views: 3130
Reputation: 110301
If the implementation is hard to explain, it's a bad idea.
In this case, I am including an implementation, for sake of completness, and for this being the kind of tricky thing I like in Python.
Therefore, the snippet bellow abuse somewhat of closures to come up with a Class Decorator that fills the needs of the O.P. : a class variable that remains "unified" for reading and writing within derived classes.
Moreover, as a bonus, I wrap the attribute in a descriptor which makes the attribute unchangeable within instances as well - so whenever the attribute is written - in either a subclass or instance of a subclass of the original class, the class attribute is properly updated.
As the Zen of Python puts it: "If the implementation is hard to explain, it is a bad idea" - I don't think I could come with anything harder -- we are talking of scoped dynamically generated meta-classes here. It will work, but it looses this is "unpythonic" code as it is very cryptic due to the heavy use of the class, metaclass, closures and descriptor mechanisms.
def SingletonAttrs(**names):
keys = names.keys()
def class_decorator(cls):
class Meta(type):
def __getattribute__(cls, attr):
if attr in keys:
return type.__getattribute__(owner_cls, attr)
return type.__getattribute__(cls, attr)
def __setattr__(cls, attr, value):
if attr in keys:
class Wrapper(object):
def __init__(self, value):
self.__set__(None, value)
__set__ = lambda self, instance, value: setattr(owner_cls,"__" + attr, value)
__get__ = lambda self, instance, owner: type.__getattribute__(owner_cls, "__" + attr)
return type.__setattr__(owner_cls, attr, Wrapper(value))
return type.__setattr__(cls, attr, value)
owner_cls = Meta(cls.__name__, cls.__bases__, cls.__dict__.copy())
for key in keys:
setattr(owner_cls, key, names[key])
return owner_cls
return class_decorator
if __name__ == "__main__":
@SingletonAttrs(a="value 1", b="value 2")
class Test(object):
pass
class TestB(Test):
pass
t = Test()
print t.a
print t.b
tb = TestB()
tb.a = "value 3"
print Test.a
Upvotes: 1
Reputation: 391852
a1.classvar = 'aaa'
This is not a "reference" to a class variable.
That is a new instance variable in the object 'a1'.
An expression like A.classvar
is the class variable. The class object (and it's superclasses) all have a class level dictionary (A.__dict__
) with class-level objects defined in it. Name resolution works by checking the class, then all the super-classes in Method Resolution Order (MRO).
An expression like a.classvar
is resolved by a search through the object's namespace. When this is a "reading" reference, the object and the class (and the superclasses) are searched.
When this appears on the left side of assignment, the instance variable ("classvar") is simply created on the referenced object ("a"). No searching through parent namespaces to resolve a name, since there's nothing to resolve. It's being created.
Upvotes: 3