10

I have a method within which I need to pass an ever-increasing integer to another function.

I can do this like so:

def foo(i):
    print i

def bar():

    class Incrementer(object):
        def __init__(self, start=0):
            self.i = start

        def __get__(self):
            j = self.i
            self.i += 1
            return j

    number = Incrementer()
    foo(number)
    foo(number)
    foo(number)

which correctly outputs 0 1 2 ... but I feel like I'm overlooking a much easier (or built-in) way of doing this?

aculich
  • 14,545
  • 9
  • 64
  • 71
Timmy O'Mahony
  • 53,000
  • 18
  • 155
  • 177
  • 1
    Are you sure your code is working? The `__get__()` method is only called when retrieving an attribute from a new-style class, which doesn't seem to be happening here. – Sven Marnach Jan 11 '12 at 18:13
  • Yep, it works fine. Do you mean it should only work if I do : `number = new Incrementer()`? Why is that? – Timmy O'Mahony Jan 11 '12 at 18:16
  • 1
    [I doubt your code prints `0 1 2`](http://ideone.com/kvCE1). And I'm not talking about `number = new Incrementer()` -- you seem to be confusing languages here, there is no `new` operator in Python. I'm talking about the invocation of [descriptors](http://docs.python.org/reference/datamodel.html#descriptors). – Sven Marnach Jan 11 '12 at 18:20

2 Answers2

19

Try itertools.count() -- it does exactly what you need:

>>> c = itertools.count()
>>> next(c)
0
>>> next(c)
1
>>> next(c)
2
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
7

In general, if you need to retain state between one call to a function and the next, what you want is either an object (your solution) or a generator. In some cases one will be simpler than the other, but there's nothing wrong with how you've done it, in principle (though you seem to have some issues with the implementation).

Sven's suggestion, itertools.count(), is a generator. Its implementation is something like this:

def count():
    i = 0
    while True:
        yield i
        i += 1

Now, if you wanted it to be callable like a function, rather than having to do next(c), you could define a wrapper that made it so:

 def count(c=itertools.count()):
     return next(c)

Or the inevitable one-line lambda:

count = lambda c=itertools.count(): next(c)

Then count() returns the next integer each time you call it.

Of course, if you want to be able to create any number of callable functions, each with their own counter, you can write a factory for that:

def counter():
    return lambda c=itertools.count(): next(c)

Then it's:

c = counter()
print c()   # 0
print c()   # 1
# etc

This still seems simpler to me than an object, but not by much. If your state or logic were any more complex, the encapsulation of the object might win out.

kindall
  • 178,883
  • 35
  • 278
  • 309