7

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

Community
  • 1
  • 1
Don Question
  • 11,227
  • 5
  • 36
  • 54
  • Please search for Python Singleton. (a) this has been answered and (b) it's usually a really bad idea to try to make Singletons. http://stackoverflow.com/search?q=python+singleton – S.Lott Dec 20 '11 at 14:53
  • possible duplicate of [Creating a singleton in python](http://stackoverflow.com/questions/6760685/creating-a-singleton-in-python) – S.Lott Dec 20 '11 at 14:54
  • I'm not interested in an "antipattern" discussion, so please bear with me on this issue, but you avoided the bullet of the inheritance issue. All solutions i looked into didn't address the polymorphic characteritic of an assignment, which kills the original reference. Ruby on the other hand, does what seems more "natural" to me (but obiously not for others): http://snippets.dzone.com/posts/show/6649. So i guess it's a name-scope issue or the like, but im still wondering. – Don Question Dec 20 '11 at 15:33
  • "I'm not interested in an "antipattern" discussion". Here's the point. You should actually consider that there's almost no discernible use for this. Consider a "module global" or simple global for this, since that covers the use cases better than a Singleton class. – S.Lott Dec 20 '11 at 16:09
  • Your vigor against singletons surely has it's own merits, but unless you can explain what they have to do with my concern, i would humbly ask you to step back from the religious-crusade-against-evil-singletons and maybe lend me some insight on this inheritance issue. Tanks! – Don Question Dec 20 '11 at 16:29
  • Vigor. Are you saying that facts == vigor == bad? That seems odd to me. But, your original question was very confusing in that regard. Dismissing common advice as a "religious-crusade-against-evil-singletons " seems a bit over-the-top. You're free to be angry, however. – S.Lott Dec 20 '11 at 16:35

2 Answers2

3
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.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • Great! That's exactly what i suspected. So theres's no easy way out? – Don Question Dec 20 '11 at 17:03
  • So if i want to avoid using BASECLASS.classvar all the time i would either have a) to access the class-dictionary of the BASECLASS someway or b) obtaining the reference to the original object prior reassigning it? Wait, i guess b) is not possible because i would create a new variable with a new object associated in a higer namespace if not using the BASECLASS.__dict__?! so maybe c) defining a property in the BASECLASS encapsulating the BASECLASS.classvar? – Don Question Dec 20 '11 at 17:08
  • `class A(object): _classvar = SOMEVALUE; ... classvar = property(getter, setter)` – Don Question Dec 20 '11 at 17:14
  • And all child-classes would have to use the property. Would this be the solution to avoid the creation of new class-/instance-variables? – Don Question Dec 20 '11 at 17:21
  • Updatable class-level variables are a design mistake. They're not well-supported for a really good reason. Whenever you think you have an updatable class-level variable, you've really got two things mashed into one class and they need to be separated into two classes. – S.Lott Dec 20 '11 at 19:53
  • This maybe true, but that wasn't my avenue of aproach. I was simply puzzled at this (for me) odd behaviour, which i couldn't grasp at first. It's nothing im using in any production-code, but simply unexpected when experimenting with some design possibilities. And if im not mistaken, even the Zope guys *Thumbs up* which i really look up to, used to work with the singleton pattern. I'm not sure if they are still using it, because i didn' realy look into their utility part of the ZCA for a longer time. – Don Question Dec 20 '11 at 22:06
  • @DonQuestion: "Would this be the solution to avoid the creation of new class-/instance-variables?" I'm trying to answer that. The answer is to not create a design where this question even arises. Avoiding class-level stateful variables is easy and prevents these questions from even arising. – S.Lott Dec 20 '11 at 22:23
  • Why would someone want to do something stupid? Well, it's just human! ;-) Smoking, drinking, marrying, ... are all obvious stupid things to do, so why bother? To me it doesn't matter if something is stupid from an objective POV to decide if it may be interesting to explore or study. In this case i was merly puzzled about the "COW"-behaviour of class-variables. A circumstance i wasn't aware till today. I just wanted to understand the why and the implications. As a follower of the Law of Demeter i would never question the wrongness of global state issues, but every religion may lead to fanatism. – Don Question Dec 20 '11 at 23:28
  • And don't get me wrong: im very thankful for your comments regarding the clarification of these issues, but on the other hand i really don't appreciate being lectured about being catholic if i feel like the pope! I guess you wouldn't either! But being a modern Pope i would never invoke the global state of absoulte prohibition of curiosity! ;-) – Don Question Dec 20 '11 at 23:34
  • @DonQuestion: You are free to ignore advice. Complaining about free advice seems a bit silly. The answer remains the answer. Your follow-up questions are quite hard to fathom. Either (1) you have a poor design and want help or (2) you have a lot of hypothetical questions centered on a poor design. Either way, it remains a poor design. The answer, however, does not change, irrespective of the follow-up questions or the extremely rude comments. Bad design is lot a "religious" issue. It's advice. Freely offered. Rudely rejected. I apologize for trying to help. – S.Lott Dec 21 '11 at 02:41
1

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
jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • I have the unceasing feeling it could be achieved with code an order of magnitude simpler. But it just won's spring to my mind. – jsbueno Dec 21 '11 at 05:11
  • Thank you very much. Great example. I learned quite a lot, simply by going through your implementation. So just "for sake of completness, "and for this being the kind of tricky thing I like": Am i right in my assumption, that your solution is even garbage-collector-"safe"? – Don Question Dec 21 '11 at 11:13