问题:函数中静态变量的Python等效项是什么?

此C / C ++代码的惯用Python等效项是什么?

void foo()
{
    static int counter = 0;
    counter++;
    printf("counter is %d\n", counter);
}

具体来说,如何在函数级别而非类级别实现静态成员?并将函数放入类中是否会发生任何变化?

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?


回答 0

有点相反,但这应该起作用:

def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter
foo.counter = 0

如果要将计数器初始化代码放在顶部而不是底部,则可以创建一个装饰器:

def static_vars(**kwargs):
    def decorate(func):
        for k in kwargs:
            setattr(func, k, kwargs[k])
        return func
    return decorate

然后使用如下代码:

@static_vars(counter=0)
def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter

foo.不幸的是,它仍然需要您使用前缀。

(信用:@ony

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)


回答 1

您可以向函数添加属性,并将其用作静态变量。

def myfunc():
  myfunc.counter += 1
  print myfunc.counter

# attribute must be initialized
myfunc.counter = 0

另外,如果您不想在函数外部设置变量,则hasattr()可以避免出现AttributeError异常:

def myfunc():
  if not hasattr(myfunc, "counter"):
     myfunc.counter = 0  # it doesn't exist yet, so initialize it
  myfunc.counter += 1

无论如何,静态变量很少见,您应该为该变量找到一个更好的位置,很可能在类中。

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.


回答 2

还可以考虑:

def foo():
    try:
        foo.counter += 1
    except AttributeError:
        foo.counter = 1

推理:

  • 大量pythonic(“请求宽恕而不是许可”)
  • 使用异常(仅抛出一次)而不是if分支(请考虑StopIteration异常)

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

其他答案已经说明了您应该执行此操作的方式。这是您不应该使用的方法:

>>> def foo(counter=[0]):
...   counter[0] += 1
...   print("Counter is %i." % counter[0]);
... 
>>> foo()
Counter is 1.
>>> foo()
Counter is 2.
>>> 

仅在第一次评估该函数时才初始化缺省值,而不是在每次执行该函数时才初始化缺省值,因此可以使用列表或任何其他可变对象存储静态值。

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.


回答 4

很多人已经建议测试“ hasattr”,但是答案很简单:

def func():
    func.counter = getattr(func, 'counter', 0) + 1

没有try / except,没有测试hasattr,只有默认的getattr。

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.


回答 5

这是一个完全封装的版本,不需要外部初始化调用:

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

在Python中,函数是对象,我们可以通过special属性将成员变量简单地添加或Monkey补丁__dict__。内置vars()函数返回special属性__dict__

编辑:请注意,与替代try:except AttributeError答案不同,使用此方法,变量将始终为初始化后的代码逻辑做好准备。我认为以下try:except AttributeError替代方案将减少DRY和/或流程笨拙:

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:仅当从多个位置调用该函数时,才建议使用上述方法。如果只在一个地方调用该函数,则最好使用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))
    ...

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

Python没有静态变量,但是您可以通过定义可调用的类对象然后将其用作函数来伪造它。另请参阅此答案

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

请注意,这__call__使得类(对象)的实例可以通过其自己的名称来调用。这就是为什么foo()上面的调用会调用类的__call__方法的原因。从文档中

可以通过在任意类的类中定义一个__call__()方法来使其实例化。

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.


回答 7

使用生成器函数生成迭代器。

def foo_gen():
    n = 0
    while True:
        n+=1
        yield n

然后像

foo = foo_gen().next
for i in range(0,10):
    print foo()

如果需要上限:

def foo_gen(limit=100000):
    n = 0
    while n < limit:
       n+=1
       yield n

如果迭代器终止(如上面的示例),您也可以直接在其上循环,例如

for i in foo_gen(20):
    print i

当然,在这些简单的情况下,最好使用xrange :)

这是关于yield声明的文档。

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.


回答 8

其他解决方案通常使用复杂的逻辑来将计数器属性添加到函数,以处理初始化。这不适用于新代码。

在Python 3中,正确的方法是使用以下nonlocal语句:

counter = 0
def foo():
    nonlocal counter
    counter += 1
    print(f'counter is {counter}')

有关声明的说明,请参见PEP 3104nonlocal

如果计数器是模块专用的,则应_counter改为命名。

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.


回答 9

将函数的属性用作静态变量有一些潜在的缺点:

  • 每次您要访问变量时,都必须写出函数的全名。
  • 外部代码可以轻松访问变量并弄乱值。

第二个问题的惯用python可能是用一个前导下划线将变量命名,以表示该变量不是可被访问的,而在事发后仍可访问。

另一种选择是使用词法闭包的模式,这nonlocal在python 3 中受关键字支持。

def make_counter():
    i = 0
    def counter():
        nonlocal i
        i = i + 1
        return i
    return counter
counter = make_counter()

可悲的是,我不知道将这种解决方案封装到装饰器中的方法。

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.


回答 10

def staticvariables(**variables):
    def decorate(function):
        for variable in variables:
            setattr(function, variable, variables[variable])
        return function
    return decorate

@staticvariables(counter=0, bar=1)
def foo():
    print(foo.counter)
    print(foo.bar)

就像上面的vincent的代码一样,它将用作函数装饰器,并且必须使用函数名称作为前缀来访问静态变量。该代码的优点(尽管可以承认,任何人都可以聪明地解决它)是您可以拥有多个静态变量,并可以以更常规的方式对其进行初始化。

def staticvariables(**variables):
    def decorate(function):
        for variable in variables:
            setattr(function, variable, variables[variable])
        return function
    return decorate

@staticvariables(counter=0, bar=1)
def foo():
    print(foo.counter)
    print(foo.bar)

Much like vincent’s code above, this would be used as a function decorator and static variables must be accessed with the function name as a prefix. The advantage of this code (although admittedly anyone might be smart enough to figure it out) is that you can have multiple static variables and initialise them in a more conventional manner.


回答 11

更具可读性,但更冗长(Python的Zen:显式优于隐式):

>>> def func(_static={'counter': 0}):
...     _static['counter'] += 1
...     print _static['counter']
...
>>> func()
1
>>> func()
2
>>>

请参阅此处以了解其工作原理。

A little bit more readable, but more verbose (Zen of Python: explicit is better than implicit):

>>> def func(_static={'counter': 0}):
...     _static['counter'] += 1
...     print _static['counter']
...
>>> func()
1
>>> func()
2
>>>

See here for an explanation of how this works.


回答 12

_counter = 0
def foo():
   全局_counter
   _counter + = 1
   打印“计数器是”,_counter

Python通常使用下划线指示私有变量。C语言中在函数内部声明静态变量的唯一原因是将其隐藏在函数外部,这并不是真正的Python。

_counter = 0
def foo():
   global _counter
   _counter += 1
   print 'counter is', _counter

Python customarily uses underscores to indicate private variables. The only reason in C to declare the static variable inside the function is to hide it outside the function, which is not really idiomatic Python.


回答 13

在尝试了几种方法之后,我最终使用了@warvariuc的答案的改进版本:

import types

def func(_static=types.SimpleNamespace(counter=0)):
    _static.counter += 1
    print(_static.counter)

After trying several approaches I end up using an improved version of @warvariuc’s answer:

import types

def func(_static=types.SimpleNamespace(counter=0)):
    _static.counter += 1
    print(_static.counter)

回答 14

惯用的方法是使用一个,它可以有属性。如果需要不分离实例,请使用单例。

您可以通过多种方法将“静态”变量伪造或修改为Python(到目前为止,尚未提及的一种方法是使用可变的默认参数),但这不是Pythonic惯用的方法。只需使用一个类。

如果您的使用模式合适,则可能是生成器。

The idiomatic way is to use a class, which can have attributes. If you need instances to not be separate, use a singleton.

There are a number of ways you could fake or munge “static” variables into Python (one not mentioned so far is to have a mutable default argument), but this is not the Pythonic, idiomatic way to do it. Just use a class.

Or possibly a generator, if your usage pattern fits.


回答 15

这个问题的提示下,我可以提出另一种选择,它可能会更好用,并且对于方法和函数来说都一样:

@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    

如果您喜欢这种用法,请执行以下操作:

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

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

回答 16

另一个(不建议!)在可调用对象(如https://stackoverflow.com/a/279598/916373)上的扭曲,如果您不介意使用时髦的调用签名,则可以这样做

class foo(object):
    counter = 0;
    @staticmethod
    def __call__():
        foo.counter += 1
        print "counter is %i" % foo.counter

>>> foo()()
counter is 1
>>> foo()()
counter is 2

Another (not recommended!) twist on the callable object like https://stackoverflow.com/a/279598/916373, if you don’t mind using a funky call signature, would be to do

class foo(object):
    counter = 0;
    @staticmethod
    def __call__():
        foo.counter += 1
        print "counter is %i" % foo.counter

>>> foo()()
counter is 1
>>> foo()()
counter is 2

回答 17

除了创建具有静态局部变量的函数外,您始终可以创建所谓的“函数对象”并为其提供标准(非静态)成员变量。

由于您提供了用C ++编写的示例,因此我将首先解释C ++中的“函数对象”。“功能对象”就是带有重载的任何类operator()。该类的实例的行为类似于函数。例如,int x = square(5);即使square是一个对象(具有重载operator()),但从技术上讲不是“函数” ,您也可以编写。您可以给功能对象提供可以给类对象提供的任何功能。

# 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;

在Python中,我们也可以重载,operator()只是方法改为命名为__call__

这是一个类定义:

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

这是使用的类的示例:

from foo import foo

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

打印到控制台的输出是:

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

如果要让函数接受输入参数,也可以将其添加到其中__call__

# 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

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

回答 18

溶液n + = 1

def foo():
  foo.__dict__.setdefault('count', 0)
  foo.count += 1
  return foo.count

Soulution n +=1

def foo():
  foo.__dict__.setdefault('count', 0)
  foo.count += 1
  return foo.count

回答 19

全局声明提供了此功能。在下面的示例(使用“ f”的python 3.5或更高版本)中,计数器变量在函数外部定义。在功能中将其定义为全局表示表示该功能之外的“全局”版本应可用于该功能。因此,每次函数运行时,它都会修改函数外部的值,并将其保留在函数之外。

counter = 0

def foo():
    global counter
    counter += 1
    print("counter is {}".format(counter))

foo() #output: "counter is 1"
foo() #output: "counter is 2"
foo() #output: "counter is 3"

A global declaration provides this functionality. In the example below (python 3.5 or greater to use the “f”), the counter variable is defined outside of the function. Defining it as global in the function signifies that the “global” version outside of the function should be made available to the function. So each time the function runs, it modifies the value outside the function, preserving it beyond the function.

counter = 0

def foo():
    global counter
    counter += 1
    print("counter is {}".format(counter))

foo() #output: "counter is 1"
foo() #output: "counter is 2"
foo() #output: "counter is 3"

回答 20

Python方法内的静态变量

class Count:
    def foo(self):
        try: 
            self.foo.__func__.counter += 1
        except AttributeError: 
            self.foo.__func__.counter = 1

        print self.foo.__func__.counter

m = Count()
m.foo()       # 1
m.foo()       # 2
m.foo()       # 3

A static variable inside a Python method

class Count:
    def foo(self):
        try: 
            self.foo.__func__.counter += 1
        except AttributeError: 
            self.foo.__func__.counter = 1

        print self.foo.__func__.counter

m = Count()
m.foo()       # 1
m.foo()       # 2
m.foo()       # 3

回答 21

我个人更喜欢以下装饰器。给每个人自己。

def staticize(name, factory):
    """Makes a pseudo-static variable in calling function.

    If name `name` exists in calling function, return it. 
    Otherwise, saves return value of `factory()` in 
    name `name` of calling function and return it.

    :param name: name to use to store static object 
    in calling function
    :type name: String
    :param factory: used to initialize name `name` 
    in calling function
    :type factory: function
    :rtype: `type(factory())`

    >>> def steveholt(z):
    ...     a = staticize('a', list)
    ...     a.append(z)
    >>> steveholt.a
    Traceback (most recent call last):
    ...
    AttributeError: 'function' object has no attribute 'a'
    >>> steveholt(1)
    >>> steveholt.a
    [1]
    >>> steveholt('a')
    >>> steveholt.a
    [1, 'a']
    >>> steveholt.a = []
    >>> steveholt.a
    []
    >>> steveholt('zzz')
    >>> steveholt.a
    ['zzz']

    """
    from inspect import stack
    # get scope enclosing calling function
    calling_fn_scope = stack()[2][0]
    # get calling function
    calling_fn_name = stack()[1][3]
    calling_fn = calling_fn_scope.f_locals[calling_fn_name]
    if not hasattr(calling_fn, name):
        setattr(calling_fn, name, factory())
    return getattr(calling_fn, name)

I personally prefer the following to decorators. To each their own.

def staticize(name, factory):
    """Makes a pseudo-static variable in calling function.

    If name `name` exists in calling function, return it. 
    Otherwise, saves return value of `factory()` in 
    name `name` of calling function and return it.

    :param name: name to use to store static object 
    in calling function
    :type name: String
    :param factory: used to initialize name `name` 
    in calling function
    :type factory: function
    :rtype: `type(factory())`

    >>> def steveholt(z):
    ...     a = staticize('a', list)
    ...     a.append(z)
    >>> steveholt.a
    Traceback (most recent call last):
    ...
    AttributeError: 'function' object has no attribute 'a'
    >>> steveholt(1)
    >>> steveholt.a
    [1]
    >>> steveholt('a')
    >>> steveholt.a
    [1, 'a']
    >>> steveholt.a = []
    >>> steveholt.a
    []
    >>> steveholt('zzz')
    >>> steveholt.a
    ['zzz']

    """
    from inspect import stack
    # get scope enclosing calling function
    calling_fn_scope = stack()[2][0]
    # get calling function
    calling_fn_name = stack()[1][3]
    calling_fn = calling_fn_scope.f_locals[calling_fn_name]
    if not hasattr(calling_fn, name):
        setattr(calling_fn, name, factory())
    return getattr(calling_fn, name)

回答 22

此答案基于@claudiu的答案。

我发现,每当我打算访问静态变量时,总是必须在函数名称前加上前缀,我的代码变得不清楚。

即,在我的函数代码中,我更喜欢写:

print(statics.foo)

代替

print(my_function_name.foo)

因此,我的解决方案是:

  1. 添加一个 statics向函数属性
  2. 在功能范围中,将局部变量添加statics为别名my_function.statics
from bunch import *

def static_vars(**kwargs):
    def decorate(func):
        statics = Bunch(**kwargs)
        setattr(func, "statics", statics)
        return func
    return decorate

@static_vars(name = "Martin")
def my_function():
    statics = my_function.statics
    print("Hello, {0}".format(statics.name))

备注

我的方法使用一个名为的类Bunch,它是一个支持属性样式访问的字典,即JavaScript(请参见原始文章)。 2000年前后)

可以通过安装 pip install bunch

也可以这样手写:

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self

This answer builds on @claudiu ‘s answer.

I found that my code was getting less clear when I always had to prepend the function name, whenever I intend to access a static variable.

Namely, in my function code I would prefer to write:

print(statics.foo)

instead of

print(my_function_name.foo)

So, my solution is to :

  1. add a statics attribute to the function
  2. in the function scope, add a local variable statics as an alias to my_function.statics
from bunch import *

def static_vars(**kwargs):
    def decorate(func):
        statics = Bunch(**kwargs)
        setattr(func, "statics", statics)
        return func
    return decorate

@static_vars(name = "Martin")
def my_function():
    statics = my_function.statics
    print("Hello, {0}".format(statics.name))

Remark

My method uses a class named Bunch, which is a dictionary that supports attribute-style access, a la JavaScript (see the original article about it, around 2000)

It can be installed via pip install bunch

It can also be hand-written like so:

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self

回答 23

基于丹尼尔的答案(补充):

class Foo(object): 
    counter = 0  

def __call__(self, inc_value=0):
    Foo.counter += inc_value
    return Foo.counter

foo = Foo()

def use_foo(x,y):
    if(x==5):
        foo(2)
    elif(y==7):
        foo(3)
    if(foo() == 10):
        print("yello")


use_foo(5,1)
use_foo(5,1)
use_foo(1,7)
use_foo(1,7)
use_foo(1,1)

我想添加此部分的原因是,作为一个实际示例,静态变量不仅用于增加某个值,而且还检查静态var是否等于某个值。

静态变量仍受保护,仅在函数use_foo()的范围内使用

在此示例中,对foo()的调用功能完全相同(相对于相应的c ++等效项):

stat_c +=9; // in c++
foo(9)  #python equiv

if(stat_c==10){ //do something}  // c++

if(foo() == 10):      # python equiv
  #add code here      # python equiv       

Output :
yello
yello

如果将Foo类限制性地定义为单例类,那将是理想的。这将使它更具Pythonic性。

Building on Daniel’s answer (additions):

class Foo(object): 
    counter = 0  

def __call__(self, inc_value=0):
    Foo.counter += inc_value
    return Foo.counter

foo = Foo()

def use_foo(x,y):
    if(x==5):
        foo(2)
    elif(y==7):
        foo(3)
    if(foo() == 10):
        print("yello")


use_foo(5,1)
use_foo(5,1)
use_foo(1,7)
use_foo(1,7)
use_foo(1,1)

The reason why I wanted to add this part is , static variables are used not only for incrementing by some value, but also check if the static var is equal to some value, as a real life example.

The static variable is still protected and used only within the scope of the function use_foo()

In this example, call to foo() functions exactly as(with respect to the corresponding c++ equivalent) :

stat_c +=9; // in c++
foo(9)  #python equiv

if(stat_c==10){ //do something}  // c++

if(foo() == 10):      # python equiv
  #add code here      # python equiv       

Output :
yello
yello

if class Foo is defined restrictively as a singleton class, that would be ideal. This would make it more pythonic.


回答 24

当然,这是一个老问题,但我想我可能会提供一些更新。

似乎性能参数已过时。相同的测试套件似乎为siInt_try和isInt_re2提供了相似的结果。当然结果会有所不同,但这是在我的计算机上使用Xeon W3550在内核4.3.01上使用python 3.4.4的会话。我已经运行了几次,结果似乎是相似的。我将全局正则表达式移到了函数静态中,但是性能差异可以忽略不计。

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

随着性能问题的解决,try / catch似乎会生成最适合未来和极端情况的代码,因此也许将其包装在函数中

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


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。