Simple function that returns a number incremented by 1 on each call, without globals?
Asked Answered
J

8

11

I am trying to write a python function that on the first call, returns a 1. On the second call, returns a 2. On the third, a 3. Etc.

Currently, I have achieved this using a global variable:

index = 0

def foo():
    global index
    index += 1
    return index

When calling the function three times:

print(foo())
print(foo())
print(foo())

It returns the values expected:

1
2
3

But, I've read that it is bad practice to use global variables. So, I was wondering if the same result could be achieved without using globals.

Any suggestion?

Thank you for your help.

Joggle answered 19/7, 2016 at 10:3 Comment(0)
S
12

Using a closure:

def make_inc():
    val = [0]
    def inc():
        val[0] += 1
        return val[0]
    return inc

inc = make_inc()
print inc()
print inc()
print inc()

Using a class (the most obvious solution in an OOPL ):

class Inc(object):
    def __init__(self):
        self._val = 0

    def __call__(self):
        self._val += 1
        return self._val


inc = Inc()
print inc()
print inc()
print inc()

Using a generator (not directly callable, you'll have to use the .next() method):

def incgen():
    val = 0
    while True:
        val += 1
        yield val


inc = incgen()
print inc.next()
print inc.next()
print inc.next()
Scrimpy answered 19/7, 2016 at 10:18 Comment(2)
Your generator code should probably use next(inc); next(inc) instead of inc.next() because the former works in python2.6+ and also in python3, while the latter only works in python2.xWyandotte
@Wyandotte yes sorry I still have to switch to py3K <g>Scrimpy
P
7

You can use function attributes:

def f():
    f.counter = getattr(f, 'counter', 0) + 1
    return f.counter

Or closures:

def w():
    counter = 0
    def f():
        nonlocal counter
        counter += 1
        return counter
    return f
Plumber answered 19/7, 2016 at 10:8 Comment(1)
nonlocal is Python3 only IIRC. wrt/ to the "function attribute" trick, I would definitly not recommand it, as it's really brittle (add g = f; f = lambda : None after f definition and you'll find out why).Scrimpy
W
4

I will provide an alternative solution to Sergey's answer: exploit mutable default arguments!

def f(_=[0]):
    _[0] += 1
    return _[0]

This has the disadvantage that a user might incorrectly call the function with one argument and it will not receive an error message.

Wyandotte answered 19/7, 2016 at 10:20 Comment(0)
S
2

Can you use an object?

class Incrementer:
    def __init__(self):
        self.value = 0

    def __call__(self):
        print(self.value)
        self.value += 1

Then you can call it like a function after instantiating it:

>>> a = Incrementer()
>>> a()
0
>>>a()
1
Sammysamoan answered 19/7, 2016 at 10:21 Comment(0)
M
2

You can use generator also.

def yrange(n):
    i = 0
    while i < n:
        yield i
        i += 1

Output:

>>> y = yrange(3)
>>> y
<generator object yrange at 0x401f30>
>>> y.next()
0
>>> y.next()
1
>>> y.next()
2
>>> y.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
Mislike answered 19/7, 2016 at 10:25 Comment(0)
C
0

You can also improve generator, make it more flexible:

def counter_gen():
    count = 0
    while True:
        res = yield count
        count += res

>>> counter = counter_gen()
>>> counter.send(None) #initialize generator
>>> 0
>>> counter.send(1)
>>> 1
>>> counter.send(3)
>>> 4  
Calondra answered 19/7, 2016 at 19:15 Comment(0)
J
0

Besides what @bruno desthuilliers made in his first "closure solving", i would like to point, it is just ok to use a list instead of doing some closures.

What i mean is, it will work fine, if you modify it this way.

index = [0]

def foo():
    index[0] = index[0] + 1
    return index[0]

we used a list rather than using a simple variable, it doesn't try to create another list named index, because to create a list you need to something like this,

anotherlist= []

or

anotherlist= list(())

why a problem happens when you use a simple variable is,

the script attempts to make an assignment to the variable index, which creates a new name in the local scope. Then, when Python executes the right-hand side of the assignment it finds the name index in the local scope with nothing assigned to it yet.

Jaquez answered 11/12, 2021 at 20:14 Comment(1)
The issue with this solution is it still relies on mutable global state, so it doesn't address the OP's question.Scrimpy
T
0
foo = lambda x=[0]: (x.__setitem__(0, (x[0] + 1)) or x[0])
Trincomalee answered 16/3, 2022 at 22:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.