7

I have this python code. The result is TopTest: attr1=0, attr2=1 for X which is fine but the result is SubTest: attr1=2, attr2=3 for Y which I don't quite understand.

Basically, I have a class attribute, which is a counter, and it runs in the __init__ method. When I launch Y, the counter is set to 2 and only after are the attributes are assigned. I don't understand why it starts at 2. Shouldn't the subclass copy the superclass and the counter restart at 0?

class AttrDisplay: def gatherAttrs(self): attrs = [] for key in sorted(self.__dict__): attrs.append('%s=%s' % (key, getattr(self, key))) return ', '.join(attrs) def __repr__(self): return '[%s: %s]' % (self.__class__.__name__, self.gatherAttrs()) class TopTest(AttrDisplay): count = 0 def __init__(self): self.attr1 = TopTest.count self.attr2 = TopTest.count+1 TopTest.count += 2 class SubTest(TopTest): pass X, Y = TopTest(), SubTest() print(X) print(Y) 
0

5 Answers 5

2

You access and use explicitly TopTest.count, and your subclass will stick to this explicitness. You might want to consider to use type(self).count instead, then each instance will use its own class's variable which can be made a different one in each subclass.

To make your subclass have its own class variable, just add a count = 0 to its definition:

class SubTest(TopTest): count = 0 
Sign up to request clarification or add additional context in comments.

4 Comments

self.count += 2 would create instance variable, leaving the class variable intact
Right, my mistake. I'm gonna fix it.
I know i only criticize, but: To avoid using the double underscores, you could use count = [0], but should use type(self). 1-element list is just ugly workaround for a problem that already has a solution.
I like that constructive critique ;-) and removed the reference-stuff again. Using explicitly a class variable is probably more Pythonic than using the poor-man's-references.
2

It looks like you want to keep a counter for each instance of each subclass of TopTest, but you do not want to repeat yourself by declaring a new count class variable for each subclass. You can achieve this using a Metaclass:

class TestMeta(type): def __new__(cls, name, bases, attrs): new_class = super().__new__(cls, name, bases, attrs) new_class.count = 0 return new_class class TopTest(AttrDisplay, metaclass=TestMeta): def __init__(self): self.attr1 = self.count self.attr2 = self.count + 1 self.increment_count(2) @classmethod def increment_count(cls, val): cls.count += val class SubTest(TopTest): pass 

The count attribute of your x and y objects should now be independent, and subsequent instances of TopTest and SubTest will increment the count:

>>> x, y = TopTest(), SubTest() >>> x.attr2 1 >>> y.attr2 1 >>> y2 = SubTest() >>> y2.attr2 3 

However, metaclasses can be confusing and should only be used if they are truly necessary. In your particular case it would be much simpler just to re-define the count class attribute for every subclass of TopTest:

class SubTest(TopTest): count = 0 

Comments

1

You're close - when you look up a property of an object, you're not necessarily looking up a property belonging to the object itself. Rather, lookups follow Python's method resolution order, which... isn't entirely simple. In this case, however, only three steps are performed:

  1. Check if Y has a property named count.
  2. It doesn't, so check if its class SubTest has a property named count.
  3. It doesn't, so check if its parent TopTest has a property named count. It does, so access that.

Simply put, when you access Y.count, you're actually accessing TopTest.count.


There's also the fact that you have a bug in your code - SubTest increments TopTest's count and not its own. The title of your question says "subclass counter", but since you're counting in __init__() I assume you're looking for an instance counter (to count subclasses I'm fairly certain you'd need to use metaclasses). This is a perfect use case for self.__class__, a property which contains an object's class! In order to use it:

def __init__(self): self.attr1 = self.__class__.count self.attr2 = self.__class__.count + 1 self.__class__.count += 2 

Using that, SubTest.count will be incremented instead of TopTest.count when you call SubTest().

2 Comments

isn't cls = type(self) better?
@then0rTh You're right, I'm just so used to using super(), hahah. super() would actually get TopTest in this case! I've gone ahead and changed it to __class__, which works just as well as type() (and also supports old-style classes).
0

When a new instance of SubTest is created TopTest.__init__() is called - since SubTest inherited TopTest.__init__() - which increments TopTest.count by two.

And since SubTest never defines a class level count variable, when SubTest.count is executed, Python falls back and uses TopTest.count.

This behavior can be fixed by redefining count local to SubTest.

class SubTest(TopTest): count = 0 

3 Comments

You can fix this by redefining count inside SubTest - but you are modifying TopTest.count in init, SubTest.count will be forever 0
@then0rTh Right. I'm not sure I follow. Is that not the behavior the OP wanted?
I thought SubTest.count should count instances of SubTest, but i may be wrong
0

If you want each class to have it's own class variable implicitly, you can use a metaclass to add in this variable.

class MetaCount(type): def __new__(cls, name, bases, attrs): new_cls = super(MetaCount, cls).__new__(cls, name, bases, attrs) new_cls.count = 0 return new_cls class Parent(metaclass=MetaCount): def __init__(self): self.attr1 = self.count self.attr2 = self.count + 1 type(self).count += 2 # self.count += 2 creates an *instance* variable class Child(Parent): pass p, c = Parent(), Child() print(p.count) # 2 print(c.count) # 2 

2 Comments

This code fails in Python 3. That is because Python 3 changed the way metaclass were specified. You need to do Parent(metaclass=MetaCount): instead of __metaclass__ = MetaCount inside of Parent.
@Christian updated, thanks, my machine running 2.7 and I'm too lazy to REPL

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.