BlogueroConnor
BlogueroConnor

Reputation: 1943

I need a Python class that keep tracks of how many times it is instantiated

I need a class that works like this:

>>> a=Foo()
>>> b=Foo()
>>> c=Foo()
>>> c.i
3

Here is my try:

class Foo(object):
    i = 0
    def __init__(self):
        Foo.i += 1

It works as required, but I wonder if there is a more pythonic way to do it.

Upvotes: 4

Views: 2636

Answers (4)

Neale Chaudhury
Neale Chaudhury

Reputation: 1

Could we use decorators ? So for example ..

class ClassCallCount:
    def __init__(self,dec_f):
        self._dec_f = dec_f
        self._count = 0

    def __call__(self, *args, **kwargs):
        self._count +=1
        return self._dec_f(*args, **kwargs)

    def PrintCalled(self):
        return (self._count)


@ClassCallCount
def somefunc(someval):
    print ('Value : {0}'.format(someval))



    somefunc('val.1')
    somefunc('val.2')
    somefunc('val.3')
    somefunc('val.4')
    ## Get the # of times the class was called
    print ('of times class was called : {0}'.format(somefunc._count))

Upvotes: 0

Tom
Tom

Reputation: 21902

If you want to worry about thread safety (so that the class variable can be modified from multiple threads that are instantiating Foos), the above answer is in correct. I asked this question about thread safety here. In summary, you would have to do something like this:

from __future__ import with_statement # for python 2.5

import threading

class Foo(object):
  lock = threading.Lock()
  instance_count = 0

  def __init__(self):
    with Foo.lock:
      Foo.instance_count += 1

Now Foo may be instantiated from multiple threads.

Upvotes: 4

ephemient
ephemient

Reputation: 204678

Abuse of decorators and metaclasses.

def counting(cls):
    class MetaClass(getattr(cls, '__class__', type)):
        __counter = 0
        def __new__(meta, name, bases, attrs):
            old_init = attrs.get('__init__')
            def __init__(*args, **kwargs):
                MetaClass.__counter += 1
                if old_init: return old_init(*args, **kwargs)
            @classmethod
            def get_counter(cls):
                return MetaClass.__counter
            new_attrs = dict(attrs)
            new_attrs.update({'__init__': __init__, 'get_counter': get_counter})
            return super(MetaClass, meta).__new__(meta, name, bases, new_attrs)
    return MetaClass(cls.__name__, cls.__bases__, cls.__dict__)

@counting
class Foo(object):
    pass

class Bar(Foo):
    pass

print Foo.get_counter()    # ==> 0
print Foo().get_counter()  # ==> 1
print Bar.get_counter()    # ==> 1
print Bar().get_counter()  # ==> 2
print Foo.get_counter()    # ==> 2
print Foo().get_counter()  # ==> 3

You can tell it's Pythonic by the frequent use of double underscored names. (Kidding, kidding...)

Upvotes: 5

Paolo Bergantino
Paolo Bergantino

Reputation: 488384

Nope. That's pretty good.

From The Zen of Python: "Simple is better than complex."

That works fine and is clear on what you're doing, don't complicate it. Maybe name it counter or something, but other than that you're good to go as far as pythonic goes.

Upvotes: 14

Related Questions