What Is the Python Equivalent of Static Variables Inside a Function?
What Is the Idiomatic Python Equivalent of This C/C++ Code? Void Foo() { Static Int Counter = 0; Counter++; Printf("Counter Is %D\N", Counter); } Specifically...
What is the idiomatic Python equivalent of this C/C++ code?
void foo()
{
static int counter = 0;
counter++;
printf("counter is %d\n", counter);
}
specifically, how does one implement the static member at the function level, as opposed to the class level? And does placing the function into a class change anything?
27 Answers
A bit reversed, but this should work:
def foo():
foo.counter += 1
print "Counter is %d" % foo.counter
foo.counter = 0
If you want the counter initialization code at the top instead of the bottom, you can create a decorator:
def static_vars(**kwargs):
def decorate(func):
for k in kwargs:
setattr(func, k, kwargs[k])
return func
return decorate
Then use the code like this:
@static_vars(counter=0)
def foo():
foo.counter += 1
print "Counter is %d" % foo.counter
It'll still require you to use the foo. prefix, unfortunately.
(Credit: @ony)
You can add attributes to a function, and use it as a static variable.
def myfunc():
myfunc.counter += 1
print myfunc.counter
# attribute must be initialized
myfunc.counter = 0
Alternatively, if you don't want to setup the variable outside the function, you can use hasattr() to avoid an AttributeError exception:
def myfunc():
if not hasattr(myfunc, "counter"):
myfunc.counter = 0 # it doesn't exist yet, so initialize it
myfunc.counter += 1
Anyway static variables are rather rare, and you should find a better place for this variable, most likely inside a class.
One could also consider:
def foo():
try:
foo.counter += 1
except AttributeError:
foo.counter = 1
Reasoning:
- much pythonic ("ask for forgiveness not permission")
- use exception (thrown only once) instead of
ifbranch (think StopIteration exception)
Many people have already suggested testing 'hasattr', but there's a simpler answer:
def func():
func.counter = getattr(func, 'counter', 0) + 1
No try/except, no testing hasattr, just getattr with a default.
Other answers have demonstrated the way you should do this. Here's a way you shouldn't:
>>> def foo(counter=[0]):
... counter[0] += 1
... print("Counter is %i." % counter[0]);
...
>>> foo()
Counter is 1.
>>> foo()
Counter is 2.
>>>
Default values are initialized only when the function is first evaluated, not each time it is executed, so you can use a list or any other mutable object to store static values.
Python doesn't have static variables but you can fake it by defining a callable class object and then using it as a function. Also see this answer.
class Foo(object):
# Class variable, shared by all instances of this class
counter = 0
def __call__(self):
Foo.counter += 1
print Foo.counter
# Create an object instance of class "Foo," called "foo"
foo = Foo()
# Make calls to the "__call__" method, via the object's name itself
foo() #prints 1
foo() #prints 2
foo() #prints 3
Note that __call__ makes an instance of a class (object) callable by its own name. That's why calling foo() above calls the class' __call__ method. From the documentation:
Instances of arbitrary classes can be made callable by defining a
__call__()method in their class.
Here is a fully encapsulated version that doesn't require an external initialization call:
def fn():
fn.counter=vars(fn).setdefault('counter',-1)
fn.counter+=1
print (fn.counter)
In Python, functions are objects and we can simply add, or monkey patch, member variables to them via the special attribute __dict__. The built-in vars() returns the special attribute __dict__.
EDIT: Note, unlike the alternative try:except AttributeError answer, with this approach the variable will always be ready for the code logic following initialization. I think the try:except AttributeError alternative to the following will be less DRY and/or have awkward flow:
def Fibonacci(n):
if n<2: return n
Fibonacci.memo=vars(Fibonacci).setdefault('memo',{}) # use static variable to hold a results cache
return Fibonacci.memo.setdefault(n,Fibonacci(n-1)+Fibonacci(n-2)) # lookup result in cache, if not available then calculate and store it
EDIT2: I only recommend the above approach when the function will be called from multiple locations. If instead the function is only called in one place, it's better to use nonlocal:
def TheOnlyPlaceStaticFunctionIsCalled():
memo={}
def Fibonacci(n):
nonlocal memo # required in Python3. Python2 can see memo
if n<2: return n
return memo.setdefault(n,Fibonacci(n-1)+Fibonacci(n-2))
...
print (Fibonacci(200))
...
Use a generator function to generate an iterator.
def foo_gen():
n = 0
while True:
n+=1
yield n
Then use it like
foo = foo_gen().next
for i in range(0,10):
print foo()
If you want an upper limit:
def foo_gen(limit=100000):
n = 0
while n < limit:
n+=1
yield n
If the iterator terminates (like the example above), you can also loop over it directly, like
for i in foo_gen(20):
print i
Of course, in these simple cases it's better to use xrange :)
Here is the documentation on the yield statement.
Other solutions attach a counter attribute to the function, usually with convoluted logic to handle the initialization. This is inappropriate for new code.
In Python 3, the right way is to use a nonlocal statement:
counter = 0
def foo():
nonlocal counter
counter += 1
print(f'counter is {counter}')
See PEP 3104 for the specification of the nonlocal statement.
If the counter is intended to be private to the module, it should be named _counter instead.
Using an attribute of a function as static variable has some potential drawbacks:
- Every time you want to access the variable, you have to write out the full name of the function.
- Outside code can access the variable easily and mess with the value.
Idiomatic python for the second issue would probably be naming the variable with a leading underscore to signal that it is not meant to be accessed, while keeping it accessible after the fact.
Must Read
Using closures
An alternative would be a pattern using lexical closures, which are supported with the nonlocal keyword in python 3.
def make_counter():
i = 0
def counter():
nonlocal i
i = i + 1
return i
return counter
counter = make_counter()
Sadly I know no way to encapsulate this solution into a decorator.