What is the Python equivalent of static variables inside a function?

前端 未结 26 2737
天命终不由人
天命终不由人 2020-11-22 00:45

What is the idiomatic Python equivalent of this C/C++ code?

void foo()
{
    static int counter = 0;
    counter++;
          


        
相关标签:
26条回答
  • 2020-11-22 01:36

    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.

    0 讨论(0)
  • 2020-11-22 01:36

    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.

    0 讨论(0)
  • 2020-11-22 01:36

    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
    
    0 讨论(0)
  • 2020-11-22 01:44

    Prompted by this question, may I present another alternative which might be a bit nicer to use and will look the same for both methods and functions:

    @static_var2('seed',0)
    def funccounter(statics, add=1):
        statics.seed += add
        return statics.seed
    
    print funccounter()       #1
    print funccounter(add=2)  #3
    print funccounter()       #4
    
    class ACircle(object):
        @static_var2('seed',0)
        def counter(statics, self, add=1):
            statics.seed += add
            return statics.seed
    
    c = ACircle()
    print c.counter()      #1
    print c.counter(add=2) #3
    print c.counter()      #4
    d = ACircle()
    print d.counter()      #5
    print d.counter(add=2) #7
    print d.counter()      #8    
    

    If you like the usage, here's the implementation:

    class StaticMan(object):
        def __init__(self):
            self.__dict__['_d'] = {}
    
        def __getattr__(self, name):
            return self.__dict__['_d'][name]
        def __getitem__(self, name):
            return self.__dict__['_d'][name]
        def __setattr__(self, name, val):
            self.__dict__['_d'][name] = val
        def __setitem__(self, name, val):
            self.__dict__['_d'][name] = val
    
    def static_var2(name, val):
        def decorator(original):
            if not hasattr(original, ':staticman'):    
                def wrapped(*args, **kwargs):
                    return original(getattr(wrapped, ':staticman'), *args, **kwargs)
                setattr(wrapped, ':staticman', StaticMan())
                f = wrapped
            else:
                f = original #already wrapped
    
            getattr(f, ':staticman')[name] = val
            return f
        return decorator
    
    0 讨论(0)
  • 2020-11-22 01:46

    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.

    0 讨论(0)
  • 2020-11-22 01:46

    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.

    0 讨论(0)
提交回复
热议问题