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, 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?

4

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)

9

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.

7

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 if branch (think StopIteration exception)
5

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.

4

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.

11

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.

5

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

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.

0

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.

3

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.

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.

Alexander Ross

Alexander Ross

Gaming, Esports & Interactive Media Writer

Alexander Ross has covered the video game industry for a decade, writing deep dives on game design, esports tournaments, VR developments, and gaming culture.