函数中静态变量的python等价物是什么?

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

这个C/C++代码的惯用的Python等价物是什么?

1
2
3
4
5
6
7
void foo()
{
    static int counter = 0;
    counter++;
    printf("counter is %d
"
, counter);
}

具体来说,与类级别不同,如何在函数级别实现静态成员?把函数放到类中会改变什么吗?


有点颠倒了,但这应该管用:

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

如果希望计数器初始化代码位于顶部而不是底部,可以创建一个装饰器:

1
2
3
4
5
def static_var(varname, value):
    def decorate(func):
        setattr(func, varname, value)
        return func
    return decorate

然后使用如下代码:

1
2
3
4
@static_var("counter", 0)
def foo():
    foo.counter += 1
    print"Counter is %d" % foo.counter

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

编辑(多亏了Ony):这看起来更好:

1
2
3
4
5
6
7
8
9
10
11
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


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

1
2
3
4
5
6
def myfunc():
  myfunc.counter += 1
  print myfunc.counter

# attribute must be initialized
myfunc.counter = 0

或者,如果不想在函数外部设置变量,可以使用hasattr()避免AttributeError异常:

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

无论如何,静态变量是相当罕见的,您应该为这个变量找到一个更好的位置,最有可能是在类中。


也可以考虑:

1
2
3
4
5
def foo():
    try:
        foo.counter += 1
    except AttributeError:
        foo.counter = 1

推理:

  • 多脓毒气(ask for forgiveness not permission)
  • 使用exception(只抛出一次)而不是if分支(think stopIteration exception)


其他答案也证明了你应该这样做。你不应该这样做:

1
2
3
4
5
6
7
8
9
>>> def foo(counter=[0]):
...   counter[0] += 1
...   print("Counter is %i." % counter[0]);
...
>>> foo()
Counter is 1.
>>> foo()
Counter is 2.
>>>

只有在首次计算函数时才会初始化默认值,而不是每次执行函数时都初始化默认值,因此可以使用列表或任何其他可变对象来存储静态值。


许多人已经建议测试"hasattr",但有一个简单的答案:

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

没有Try/Except,没有测试hasattr,只有带有默认值的getattr。


python没有静态变量,但是可以通过定义一个可调用的类对象,然后将其用作函数来伪造它。也可以看到这个答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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__方法的原因。从文档中:

Instances of arbitrary classes can be made callable by defining a __call__() method in their class.


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

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

在python中,函数是对象,我们可以通过特殊的属性__dict__向它们简单地添加成员变量,或猴补丁。内置的vars()返回特殊属性__dict__

编辑:注意,与可选的try:except AttributeError答案不同,使用这种方法,变量在初始化之后总是可以用于代码逻辑。我认为,除以下各项外,try:except AttributeError的替代方案将不太干燥和/或流动不畅:

1
2
3
4
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

1
2
3
4
5
6
7
8
9
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))
    ...


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

1
2
3
4
5
def foo_gen():
    n = 0
    while True:
        n+=1
        yield n

然后像这样使用

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

如果需要上限:

1
2
3
4
5
def foo_gen(limit=100000):
    n = 0
    while n < limit:
       n+=1
       yield n

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

1
2
for i in foo_gen(20):
    print i

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

这是收益表上的文件。


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

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


1
2
3
4
5
6
7
8
9
10
11
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)

就像上面文森特的代码一样,它将被用作函数修饰器,静态变量必须以函数名作为前缀进行访问。这段代码的优点(尽管可以承认,任何人都可能足够聪明地理解它)是您可以拥有多个静态变量,并以更传统的方式初始化它们。


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

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

第二个问题的惯用python可能会用一个前导下划线来命名变量,以表示不打算访问它,同时在事实发生后保持它的可访问性。

另一种选择是使用词汇闭包的模式,这在Python3中由nonlocal关键字支持。

1
2
3
4
5
6
7
8
def make_counter():
    i = 0
    def counter():
        nonlocal i
        i = i + 1
        return i
    return counter
counter = make_counter()

遗憾的是,我不知道如何将这个解决方案封装到装饰器中。


可读性稍高,但更详细:

1
2
3
4
5
6
7
8
9
>>> def func(_static={'counter': 0}):
...     _static['counter'] += 1
...     print _static['counter']
...
>>> func()
1
>>> func()
2
>>>


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

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

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

nonlocal声明的规格见PEP 3104。


在这个问题的提示下,我可以提出另一个更好的选择吗?对于方法和函数,这两个选择看起来都一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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&nbsp; &nbsp;&nbsp;

如果您喜欢这个用法,下面是实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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


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

由于您给出了一个C++编写的例子,我将首先解释C++中的"函数对象"是什么。"函数对象"只是带有重载operator()的任何类。类的实例的行为将类似于函数。例如,即使square是一个对象(带有重载的operator()),而在技术上不是一个"函数",也可以编写int x = square(5);,您可以给一个函数对象任何可以给类对象的特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# C++ function object
class Foo_class {
    private:
        int counter;    
    public:
        Foo_class() {
             counter = 0;
        }
        void operator() () {  
            counter++;
            printf("counter is %d
"
, counter);
        }    
   };
   Foo_class foo;

在python中,我们也可以重载operator(),除了方法名为__call__之外:

以下是类定义:

1
2
3
4
5
6
7
8
9
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

下面是正在使用的类的示例:

1
2
3
4
from foo import foo

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

打印到控制台的输出是:

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

如果希望函数接受输入参数,也可以将这些参数添加到__call__中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 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

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

有很多方法可以将"静态"变量伪装成python(到目前为止还没有提到的方法是使用可变的默认参数),但这不是一种Python式的惯用方法。只要上课就行了。

或者可能是一个生成器,如果您的使用模式适合的话。


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

1
2
3
4
5
import types

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

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

1
2
3
4
5
6
class foo(object):
    counter = 0;
    @staticmethod
    def __call__():
        foo.counter += 1
        print"counter is %i" % foo.counter
1
2
3
4
>>> foo()()
counter is 1
>>> foo()()
counter is 2

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

1
2
3
4
5
6
7
8
9
10
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"


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

性能参数似乎已过时。对于siint-try和isint-re2,相同的测试套件似乎给出了相似的结果。当然,结果会有所不同,但这是我的计算机上的一个会话,在内核4.3.01上使用Python3.4.4,在XeonW3550上使用。我已经运行了几次,结果似乎是相似的。我将全局regex移入了函数static,但性能差异可以忽略不计。

1
2
3
4
isInt_try: 0.3690
isInt_str: 0.3981
isInt_re: 0.5870
isInt_re2: 0.3632

随着性能问题的解决,Try/Catch似乎会生成最适合未来和基本情况的代码,因此可能只需将其包装在函数中即可。


我个人更喜欢下面的装饰。他们各自的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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)


这个答案表明setdefault并不能真正满足如何创建静态局部变量的ops问题。

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

它的工作时间与fn相同。在每个变量名前面加上前缀。如果您这样删除它们:

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

没有错误,但计数器始终为0,这说明vars(fn)不访问局部变量,而是一个全局变量,可能是一个修饰器或属性stash。

如果这能奏效的话,这将是我首选的解决方案。然而,由于它没有,我倾向于使用完全封装的类定义来创建这样的静态变量。

嗯,那是最直接的。当然,这取决于您是否更熟悉功能性和OOP编码风格。


浸液n+=1

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

python方法中的静态变量

1
2
3
4
5
6
7
8
9
10
11
12
13
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


这个答案建立在@claudiu的答案之上。

我发现我的代码越来越不清晰每当我想要访问一个静态变量时,都要预先准备函数名。

也就是说,在我的函数代码中,我更喜欢写:

1
print(statics.foo)

而不是

1
print(my_function_name.foo)

因此,我的解决方案是:

  • 向函数添加statics属性
  • 在函数范围内,将局部变量statics作为别名添加到my_function.statics中。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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的类,它是一个支持属性样式访问,一个la javascript(参见关于它的原始文章,大约2000年)

    可通过pip install bunch安装

    也可以这样手写:

    1
    2
    3
    4
    class Bunch(dict):
        def __init__(self, **kw):
            dict.__init__(self,kw)
            self.__dict__ = self

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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)

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

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

    在这个例子中,调用For()函数完全是(相对于相应的C++等效的):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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被限制地定义为一个单例类,那将是理想的。这会使它更像Python。