6

I have a hierarchy of objects in a python module as follows:

class BaseObject(object):
    initialized = False

    def __init__(self):
        self._initialize()

    @classmethod
    def _initialize(cls):
        print "cls.initialized = "+str(cls.initialized)
        if not cls.initialized:
            cls.x = 1
            cls.initialized = True

class ObjectOne(BaseObject):
    @classmethod
    def double_x(cls):
        cls.x = cls.x * 2
        print cls.x

class ObjectTwo(BaseObject):
    @classmethod
    def triple_x(cls):
        cls.x = cls.x * 3
        print cls.x

if __name__ == '__main__':
    obj_1 = ObjectOne()
    obj_1.double_x()
    obj_2 = ObjectTwo()
    obj_2.triple_x()

When I run this module I would like the output to be:

cls.initialized = False
2
cls.initialized = True
6

But what I get is:

cls.initialized = False
2
cls.initialized = False
3

What do I not understand?

5
  • This reads a bit more like Ruby than Python. There are different conventions for the latter as opposed to the former. Commented Dec 30, 2012 at 17:43
  • @Makoto that's interesting. Can you explain further? What conventions did I violate? Commented Dec 30, 2012 at 17:50
  • I wouldn't call them "violations" per se, as this is valid Python regardless. It's the case though, when one has a class method, they pass self through to the method instead of using the @clsmethod decorator. There's also no need for a self._initialize() method, as that's the role of __init__() to begin with. Commented Dec 30, 2012 at 17:51
  • Thanks. I'm trying to understand. __init__() is an instance method. I want the initialization to take place in a class method. Commented Dec 30, 2012 at 17:59
  • Your example code has really bad "code smell". Specifically the whole bit of setting a class variable from an instance constructor. Commented Mar 21, 2017 at 16:36

2 Answers 2

7

You need to use the full classname to set class variables. cls in double_x and tripple_x will refer to subclasses (ObjectOne and ObjectTwo, respectively), and setting attributes on those subclasses will store new variables, not alter the class variable BaseObject.x. You can only alter base class variables by directly accessing them.

Using your code, we get:

>>> obj_1 = ObjectOne()
cls.initialized = False
>>> obj_1.double_x()
2
>>> obj_2 = ObjectTwo()
cls.initialized = False
>>> obj_2.triple_x()
3
>>> BaseObject.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'BaseObject' has no attribute 'x'
>>> BaseObject.initialized, ObjectOne.initialized, ObjectOne.x, ObjectTwo.initialized, ObjectTwo.x
(False, True, 2, True, 3)

What happened is that in _initialize(), cls was set to ObjectOne or ObjectTwo, depending on what instance you created, and each subclass got their own copies of the variables initialized and x.

Using BaseObject._initialize() (to ensure that BaseObject is initialized, and not the subclasses) gives:

>>> obj_1 = ObjectOne()
cls.initialized = False
>>> obj_1.double_x()
2
>>> obj_2 = ObjectTwo()
cls.initialized = True
>>> obj_2.triple_x()
3
>>> BaseObject.x, ObjectOne.x, ObjectTwo.x
(1, 2, 3)
>>> BaseObject.initialized
True
>>> 'x' in ObjectOne.__dict__
True
>>> 'initialized' in ObjectOne.__dict__
False
>>> 'initialized' in ObjectTwo.__dict__
False

So now _initialize() used BaseObject as the target to set initialized and the initial value for x, but double_x and triple_x still used their own subclasses to set the new value of x and are not sharing that value through BaseObject.

The only option you have to set class variables on a specific base class is to refer to it directly in all class methods:

class BaseObject(object):
    initialized = False
    def __init__(self):
        BaseObject._initialize()

    @classmethod
    def _initialize(cls):
        print "cls.initialized = "+str(cls.initialized)
        if not cls.initialized:
            cls.x = 1
            cls.initialized = True
class ObjectOne(BaseObject):
    @classmethod
    def double_x(cls):
        BaseObject.x = BaseObject.x * 2
        print cls.x

class ObjectTwo(BaseObject):
    @classmethod
    def triple_x(cls):
        BaseObject.x = BaseObject.x * 3
        print cls.x

which would give:

>>> obj_1 = ObjectOne()
cls.initialized = False
>>> obj_1.double_x()
2
>>> obj_2 = ObjectTwo()
cls.initialized = True
>>> obj_2.triple_x()
6

Note that I called BaseObject._initialize() to make sure that cls is BasObject and not a subclass. Then, when setting x the double_x and triple_x methods still refer directly to BaseObject to ensure that the variable is set directly on the base class. When reading the value of x the above example still uses cls, which uses the class MRO to find x on the base class when not set locally.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks. But I know that the _initialize() method, which I have clearly designated as a @classmethod, is being called by the line self._initialize() because its print statement is printing. If it is not the class method that is being called, what is causing the print statement to print?
@JonCrowell: You are quite correct; even on instances class methods still will pass in the class instead.
@JonCrowell: There, after some more editing, that should be a much better answer to explain what you are seeing.
1

You have two issues.first of all in order to call the class method inside the class,you must use the COMPLETE name of the class:BaseObject._initialize() second of all, every time you make a new instance of ObjectOne or ObjectTwo,you are overwriting the BaseObject.x within its environment,so others use the initialized x attribute instead of the changed one.to fix this you must change two lines:

cls.x = cls.x * 2 To BaseObject.x = cls.x * 2

and

cls.x = cls.x * 3 To BaseObject.x = cls.x * 3

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.