Search code examples
pythonclassclass-variablessubobject

Count of full objects and subobjects in Python


I would like to maintain count of A and B objects, B is subclassed from A. So the counts should be specific to A and B. For example, if I create 3 A objects and 2 B objects, by virtue of constructor call, count for A becomes 3+2=5, but I would like to keep as 3 (not when used as a subobject as part of B). Please comment on the following code snippet:

class A:
    acount = 0 # class variable
    def __init__(self, isFullA = True):
        if (isFullA):
            self.iamFullA = True
            A.acount += 1
        else:
            self.iamFullA = False
    def __del__(self):
        if (self.iamFullA):
            A.acount -= 1

class B(A):
    bcount = 0 # class variable
    def __init__(self, isFullB = True):
        A.__init__(self,False)
        if (isFullB):
            self.iamFullB = True
            B.bcount += 1
        else:
            self.iamFullB = False
    def __del__(self):
        if (self.iamFullB):
            B.bcount -= 1
#MAIN
L=[]
for i in range(3):
  L.append(A())
for i in range(2):
  L.append(B())
print "A.acount = " + str(A.acount)
print "B.bcount = " + str(B.bcount)

The output is:

A.acount = 3
B.bcount = 2

Solution

  • You're making it way to complicated - all you need is to have a distinct count class attribute for each class:

    class A(object):
        _counter = 0
    
        @classmethod
        def _inc(cls):
            cls._counter += 1
    
        @classmethod
        def _dec(cls):
            cls._counter -= 1
    
        @classmethod
        def get_count(cls):
            return cls._counter
    
        def __init__(self):
            self._inc()
    
        def __del__(self):
            self._dec()
    
    
    class B(A):
        _counter = 0
    
        def __init__(self, wot):
            super(B, self).__init__()
            self.wot = wot
    
    L=[]
    for i in range(3):
      L.append(A())
    for i in range(2):
      L.append(B(i))
    print "A.count = {}".format(A.get_count())
    print "B.count = {}".format(B.get_count())
    

    Note that I used classmethods to ensure we're accessing the class attribute, as self._counter += 1 in the __init__ would create an instance attribute. You could also get the right behaviour using type(self)._counter += 1 (or self.__class__._counter += 1) but that's a bit ugly imho.

    If this is for an API other devs will build upon, you may want to use a custom metaclass to ensure each subclass has it's own _counter, ie:

     class CounterType(type):
        def __new__(meta, name, bases, attribs):
            if "_counter" not in attribs:
                attribs["_counter"] = 0
            return type.__new__(meta, name, bases, attribs)
    
    class CounterBase(object):
        __metaclass__ = CounterType
        @classmethod
        def _inc(cls):
            cls._counter += 1
    
        @classmethod
        def _dec(cls):
            cls._counter -= 1
    
        @classmethod
        def get_count(cls):
            return cls._counter
    
        def __init__(self):
            self._inc()
    
        def __del__(self):
            self._dec()
    
    
    class A(CounterBase):
        pass
    
    
    class B(A):
        def __init__(self, wot):
            super(B, self).__init__()
            self.wot = wot
    
    L=[]
    for i in range(3):
      L.append(A())
    for i in range(2):
      L.append(B(i))
    print "A.count = {}".format(A.get_count())
    print "B.count = {}".format(B.get_count())