Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python subclass counter

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)                         
like image 588
Pier-Olivier Marquis Avatar asked Jun 21 '17 14:06

Pier-Olivier Marquis


People also ask

What is collections counter () in Python?

Python Counter Counter is an unordered collection where elements are stored as Dict keys and their count as dict value. Counter elements count can be positive, zero or negative integers. However there is no restriction on it's keys and values.

How do you count the number of classes in a Python object?

Using the list count() method. The count() method counts the number of times an element appears in the list. In this method, we use the python count() method to count the occurrence of an element in a list. This method calculates the total occurrence of a given element of a list.

How do you access a counter element in Python?

Accessing Elements in Python Counter To get the list of elements in the counter we can use the elements() method. It returns an iterator object for the values in the Counter.


3 Answers

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
like image 158
Alfe Avatar answered Oct 13 '22 01:10

Alfe


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().

like image 32
obskyr Avatar answered Oct 12 '22 23:10

obskyr


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
like image 43
Billy Avatar answered Oct 13 '22 00:10

Billy