18

The itertools.count counter in Python (2.7.9) is very handy for thread-safe counting. How can I get the current value of the counter though?

The counter increments and returns the last value every time you call next():

import itertools
x = itertools.count()
print x.next()  # 0
print x.next()  # 1
print x.next()  # 2

So far, so good.

I can't find a way to get the current value of the counter without calling next(), which would have the undesirable side-effect of increasing the counter, or using the repr() function.

Following on from the above:

print repr(x)  # "count(3)"

So you could parse the output of repr(). Something like

current_value = int(repr(x)[6:-1])

would do the trick, but is really ugly.

Is there a way to get the current value of the counter more directly?

Carl
  • 1,027
  • 1
  • 9
  • 21
  • Where does it say it's specifically thread safe? – Matti Virkkunen Jun 29 '16 at 13:48
  • @MattiVirkkunen I read [this answer](http://stackoverflow.com/a/27062830/2291710) and after testing it myself, it appears that `next(count())` is indeed thread safe. – Delgan Jun 29 '16 at 13:50
  • 1
    Testing for thread safety is not easy. Additionally if you're relying on the GIL to keep it thread safe, is there really any difference from just using a plain variable and `+=`? – Matti Virkkunen Jun 29 '16 at 13:51
  • @Mitch Sure, you can do that and that works in most cases. When dealing with threading need to check the value twice though, once outside and once inside a lock: value = x.next() if x > threshold: with my_lock: if x.current_value() > threshold: do_stuff() – Carl Jun 29 '16 at 13:55
  • Looking through the source code, the count value is not directly accessible. There may be a simpler option if you include the part of the code where you use it. – Aya Jun 29 '16 at 14:04
  • @Aya But where does the value for `repr()` come from? – Carl Jun 29 '16 at 14:07
  • 1
    @Carl It's implemented in a C function called `count_repr()`. – Aya Jun 29 '16 at 14:20
  • 1
    @MattiVirkkunen `+=` is definitely not thread safe. http://stackoverflow.com/questions/1717393/is-this-simple-python-code-thread-safe – Carl Jun 29 '16 at 14:54

5 Answers5

12

Another hack to get next value without advancing iterator is to abuse copy protocol:

>>> c = itertools.count()
>>> c.__reduce__()[1][0]
0
>>> next(c)
0
>>> c.__reduce__()[1][0]
1

Or just take it from object copy:

>>> from copy import copy
>>> next(copy(c))
1
Denis Otkidach
  • 32,032
  • 8
  • 79
  • 100
8

Use the source, Luke!

According to module implementation, it's not possible.

typedef struct {
    PyObject_HEAD
    Py_ssize_t cnt;
    PyObject *long_cnt;
    PyObject *long_step;
} countobject;

Current state is stored in cnt and long_cnt members, and neither of them is exposed in object API. Only place where it may be retrieved is object __repr__, as you suggested.

Note that while parsing string you have to consider a non-singular increment case. repr(itertools.count(123, 4)) is equal to 'count(123, 4)' - logic suggested by you in question would fail in that case.

Łukasz Rogalski
  • 22,092
  • 8
  • 59
  • 93
  • 1
    Great, thanks for clearing that up nicely. Now I understand why it's impossible, at least :-) – Carl Jun 29 '16 at 14:55
3

According to the documentation there is no way to access the current value of the function. itertools.count() is a generator method from the itertools module. As such, it is common practice to just simply assign the value of a generator's current value to a variable.

Simply store the the result of the next call:

current_value = x.next()

or ( Built-in python method for Python version ≥ 2.6 )

current_value = next(x)

You could make a wrapper function, or a utility decorator class if you would like some added syntactic sugar, but assignment is standard.

ospahiu
  • 3,465
  • 2
  • 13
  • 24
1

It is a generator, it wouldn't be easy to do what you want.

If you want to use it's value in several places, I'd recommend getting a value via .next() and storing it in a variable. If you are concerned that counter may be incremented between these 2 uses, you'd need to put them both in a critical section anyway.

If you don't want to pollute that counter with additional '+1's generated by those checks, you can use one more counter to count checks (put this in critical section too). Substracting latter from the former would give you what you need.

Also, are you really sure about thread-safety? Docs page has nothing about threads.

Daerdemandt
  • 2,281
  • 18
  • 19
0

Ran into the same thing today. Here's what I ended up with:

class alt_count:

    def __init__(self, start=0, step=1):
        self.current = start - step
        self.step = step

    def __next__(self):
        self.current = self.current + self.step
        return self.current

Should give you almost all the itertools.count functionality plus the current property.

i = alt_count()
print(next(i))  # 0
print(next(i))  # 1
print(i.current)  # 1

If the current value is not needed, I found using this simple closure also works. Note that nonlocal only works for Python version > 3.

def alt_next_maker(start=0, step=1):
    res = start - step

    def alt_next():
        nonlocal res, step
        res = res + step
        return res

    return alt_next

Can be used as a simple alternative if you don't want to use the itertools module.

alt_next = alt_next_maker()
print(alt_next())  # 0
print(alt_next())  # 1

The docs also mention the following as equivalent:

def count(start=0, step=1):
    # count(10) --> 10 11 12 13 14 ...
    # count(2.5, 0.5) -> 2.5 3.0 3.5 ...
    n = start
    while True:
        yield n
        n += step
Bitto
  • 7,937
  • 1
  • 16
  • 38