4

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.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
BlogueroConnor
  • 1,893
  • 4
  • 17
  • 18
  • 1
    what is with the word pythonic? if it works in python... isn't that python**ic**? – Victor Jul 02 '09 at 00:14
  • No need to waste time implementing any other way – fuentesjr Jul 02 '09 at 00:16
  • I take "pythonic" to mean "in the python idiom". It's possible to write Python like a Java programmer, which doesn't necessarily show off its best qualities or style. – duffymo Jul 02 '09 at 00:21
  • 2
    Pythonic is taken to mean "idiomatic python" or code that follows general Python conventions and guidelines. As opposed to Python code that was clearly written by a Java developer, for example. The difference becomes obvious. – FogleBird Jul 02 '09 at 00:22
  • Wow. Can't believe we basically wrote the same response... – FogleBird Jul 02 '09 at 00:22
  • Duplicate: http://stackoverflow.com/questions/1045344/how-do-you-create-an-incremental-id-in-a-python-class – S.Lott Jul 02 '09 at 00:32
  • Works in Python is not always Pythonic. Look at some of the SO questions from Python beginners -- sometimes the post remarkably un-pythonic code. Hard to read, bad use of language, libraries and idiom. – S.Lott Jul 02 '09 at 00:33
  • It took me some time to understand what pythonic is, and certainly is not easy to gasp it if you are not a Python programmer. Not all my code is Pythonic but at least most of the time I recognize pythonic code when I see it. – BlogueroConnor Jul 02 '09 at 00:36
  • Nitpick: "instantiated" It wouldn't matter except that it will make it easier to search for this question. – Tyler Jul 02 '09 at 01:57
  • I shouldn't have posted so quickly. According to Wiktionary, both spellings are acceptable. Nevermind. – Tyler Jul 02 '09 at 01:58
  • Please see my answer if you want to take a threadsafe approach. – Tom Jul 02 '09 at 13:11

4 Answers4

14

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.

Paolo Bergantino
  • 480,997
  • 81
  • 517
  • 436
  • 1
    Maybe I should post my comment here, instead of on the question: I'm just curious, is something like this threadsafe? Would you be able to instantiate Foo from multiple threads and have a proper count? – Tom Jul 02 '09 at 02:29
  • @Tom: Good question. I honestly am not sure, but I would think that it does. – Paolo Bergantino Jul 02 '09 at 03:16
  • I asked the question here: http://stackoverflow.com/questions/1072821/is-modifying-a-class-variable-in-python-threadsafe. – Tom Jul 02 '09 at 07:00
5

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

ephemient
  • 198,619
  • 38
  • 280
  • 391
4

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.

Community
  • 1
  • 1
Tom
  • 21,468
  • 6
  • 39
  • 44
0

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