What is the Python equivalent of static variables

2018-12-31 07:38发布

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?

标签: python
26条回答
还给你的自由
2楼-- · 2018-12-31 08:35

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)
查看更多
一个人的天荒地老
3楼-- · 2018-12-31 08:35

Sure this is an old question but I think I might provide some update.

It seems that the performance argument is obsolete. The same test suite appears to give similar results for siInt_try and isInt_re2. Of course results vary, but this is one session on my computer with python 3.4.4 on kernel 4.3.01 with Xeon W3550. I have run it several times and the results seem to be similar. I moved the global regex into function static, but the performance difference is negligible.

isInt_try: 0.3690
isInt_str: 0.3981
isInt_re: 0.5870
isInt_re2: 0.3632

With performance issue out of the way, it seems that try/catch would produce the most future- and cornercase- proof code so maybe just wrap it in function

查看更多
听够珍惜
4楼-- · 2018-12-31 08:37

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.

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.

查看更多
低头抚发
5楼-- · 2018-12-31 08:37

Instead of creating a function having a static local variable, you can always create what is called a "function object" and give it a standard (non-static) member variable.

Since you gave an example written C++, I will first explain what a "function object" is in C++. A "function object" is simply any class with an overloaded operator(). Instances of the class will behave like functions. For example, you can write int x = square(5); even if square is an object (with overloaded operator()) and not technically not a "function." You can give a function-object any of the features that you could give a class object.

# C++ function object
class Foo_class {
    private:
        int counter;     
    public:
        Foo_class() {
             counter = 0;
        }
        void operator() () {  
            counter++;
            printf("counter is %d\n", counter);
        }     
   };
   Foo_class foo;

In Python, we can also overload operator() except that the method is instead named __call__:

Here is a class definition:

class Foo_class:
    def __init__(self): # __init__ is similair to a C++ class constructor
        self.counter = 0
        # self.counter is like a static member
        # variable of a function named "foo"
    def __call__(self): # overload operator()
        self.counter += 1
        print("counter is %d" % self.counter);
foo = Foo_class() # call the constructor

Here is an example of the class being used:

from foo import foo

for i in range(0, 5):
    foo() # function call

The output printed to the console is:

counter is 1
counter is 2
counter is 3
counter is 4
counter is 5

If you want your function to take input arguments, you can add those to __call__ as well:

# FILE: foo.py - - - - - - - - - - - - - - - - - - - - - - - - -

class Foo_class:
    def __init__(self):
        self.counter = 0
    def __call__(self, x, y, z): # overload operator()
        self.counter += 1
        print("counter is %d" % self.counter);
        print("x, y, z, are %d, %d, %d" % (x, y, z));
foo = Foo_class() # call the constructor

# FILE: main.py - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

from foo import foo

for i in range(0, 5):
    foo(7, 8, 9) # function call

# Console Output - - - - - - - - - - - - - - - - - - - - - - - - - - 

counter is 1
x, y, z, are 7, 8, 9
counter is 2
x, y, z, are 7, 8, 9
counter is 3
x, y, z, are 7, 8, 9
counter is 4
x, y, z, are 7, 8, 9
counter is 5
x, y, z, are 7, 8, 9
查看更多
看淡一切
6楼-- · 2018-12-31 08:42

This answer shows that setdefault does not truly satisfy the OPs question of how to create static local variables.

def fn():
    fn.counter = vars(fn).setdefault('counter',-1)

It works as long as fn. is prefixed to each variable name. If you remove them like so:

def fn():
   counter = vars(fn).setdefault('counter',-1)
   counter += 1
   print (counter)

there are no errors but counter is always 0 and that tells me the vars(fn) is NOT accessing local variables but rather a global, probably a decorator or attribute stash.

Had this worked it would have been my preferred solution. However since it doesn't, I'm inclined to go with a fully encapsulated class definition to create such static vars.

IMHO that is the most straightforward. Of course it depends on if you are more familiar with functional vs. OOP coding styles.

查看更多
情到深处是孤独
7楼-- · 2018-12-31 08:43

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.

查看更多
登录 后发表回答