Is Python a “static variable” hack ok to use?

A frequently asked question is whether there is an equivalent of static variables inside functions in Python. There are many answers, such as creating wrapper classes, using nested functions, decorators, etc.

One of the most elegant solutions I found was this , which I slightly modified:

def foo(): # see if foo.counter already exists try: test = foo.counter # if not, initialize it to whatever except AttributeError: foo.counter = 0 # do stuff with foo.counter ..... ..... 

Example:

static.py

 def foo(x): # see if foo.counter already exists try: test = foo.counter # if not, initialize it to whatever except AttributeError: foo.counter = 0 foo.counter += x print(foo.counter) for i in range(10): foo(i) 

Exit

 $ python static.py 0 1 3 6 10 15 21 28 36 45 

Is there a reason I should avoid this method? How does it work?

+7
python static
source share
6 answers

How it works?

This works because the function name is just another entry in the local scope, and the function is an object, like everything else in Python, and can have arbitrary attributes on it:

 def foo(): # The foo function object has already been fully constructed # by the time we get into our `try` try: test = foo.counter # Only run when foo is invoked except AttributeError: foo.counter = 0 foo.counter += 1 if hasattr(foo, 'counter'): print('Foo has a counter attribute') else: # This prints out - we've parsed `foo` but not executed it yet print('Foo.counter does not exist yet') # Now, we invoke foo foo() if hasattr(foo, 'counter'): # And from now on (until we remove the attribute) # this test will always succeed because we've added the counter # attribute to the function foo. print('Foo has a counter attribute') else: print('Foo.counter does not exist yet') # No longer true 
+2
source share

Why not this:

 def foo(x): foo.counter += x print(foo.counter) foo.counter = 0 # init on module import 

And then:

 for i in range(10): foo(i) 

I get the same output with py2.7, py3.4.

+1
source share

The solution you have works fine, but if you are after the most elegant solution, you may prefer this (adapted from one of the answers you referred to):

 def foo(x): foo.counter = getattr(foo, 'counter', 0) + x print(foo.counter) for i in range(10): foo(i) 

It works essentially the same, but getattr returns the default value (0), which applies only if foo does not have the counter attribute.

+1
source share

In python, you will probably be much better off serving generator functions.

  • It includes several simultaneous areas (each generator instance may have its own instance of foo.counter ).
  • "Static" variables are correctly encapsulated in the scope of the function ( foo.counter actually located in the outer scope (file-level scope)).

Here is an example of using two simultaneous generators, each with its own version of the counter variable (not possible with "static" variables).

 def foo(): counter = 0 while True: # You can yield None here if you don't want any value. yield counter counter += 1 gen1 = foo() gen2 = foo() gen1.next() # 0 gen1.next() # 1 gen2.next() # 0 

You can provide some initial values ​​to the generator, as well as send the data back to the generators.

 def foo(x=0) counter = x val = 1 while True: sent = (yield counter) if sent is None: counter += val val = 1 else: val = sent gen1 = foo(3) gen1.next() # 3 gen1.send(3) gen1.next() # 6 gen1.next() # 7 

You can do much more than just loop over the counter. Generators are a powerful tool in python and are much more flexible than simple "static" variables.

+1
source share

I feel that the object is exactly what you are looking for here. These are few conditions associated with certain actions (in this case, one action) that use and manage this state. So why not:

 class Foo(object): def __init__(self, start=0): self.counter = start def foo(self, x): self.counter += x print(self.counter) foo = Foo() for i in range(10): foo.foo(i) 

As others have stated, if you really want to avoid a class that you can. A function is already an object and can have any attribute added to it, just like any ordinary object. But why do you really want this? I understand that writing a class for one function is a bit like busting, but you stated that your actual code has various operations that occur. Without seeing the various operations, etc., it appears that you have a reasonable argument for using a class here.

+1
source share

You may encounter some problems, since the test will look outside the function area for foo.counter if it does not find it in the function. For example, the following numbers: 101 instead of 1

 class Bar(): counter = 100 class Hoo(): def foo(x): # see if foo.counter already exists try: test = foo.counter # if not, initialize it to whatever except AttributeError: foo.counter = 0 foo.counter += x print(foo.counter) # make an object called foo that has an attribute counter foo = Bar() # call the static foo function Hoo.foo(1) 
0
source share

All Articles