Reputation: 14836
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?
Upvotes: 6
Views: 21065
Reputation: 3761
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
Upvotes: 1
Reputation: 1121346
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.
Upvotes: 7