函数内部静态变量的Python等价物是什么?

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

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

具体来说,如何在函数级别实现静态成员,而不是类级别?将函数放入类会改变什么吗?

440745 次浏览

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

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

那就像

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

如果你想要一个上限:

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

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

for i in foo_gen(20):print i

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

这是收益表的留档。

有点颠倒,但这应该工作:

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

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

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

然后像这样使用代码:

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

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

(图片来源:@ony

其他答案已经证明了你应该这样做的方式。这是一个你不应该的方法:

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

默认值仅在函数首次评估时初始化,而不是每次执行时初始化,因此您可以使用列表或任何其他可变对象来存储静态值。

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

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

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

def myfunc():myfunc.counter += 1print myfunc.counter
# attribute must be initializedmyfunc.counter = 0

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

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

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

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

class Foo(object):# Class variable, shared by all instances of this classcounter = 0
def __call__(self):Foo.counter += 1print Foo.counter
# Create an object instance of class "Foo," called "foo"foo = Foo()
# Make calls to the "__call__" method, via the object's name itselffoo() #prints 1foo() #prints 2foo() #prints 3

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

通过在类中定义__call__()方法,可以使任意类的实例可调用。

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

有很多方法可以在Python中伪造或修改“静态”变量(到目前为止还没有提到的一种是有一个可变的默认参数),但这不是Pythonic,惯用的的方法。只需使用一个类。

或者可能是发电机,如果您的使用模式适合。

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

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()` inname `name` of calling function and return it.
:param name: name to use to store static objectin 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.aTraceback (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 functioncalling_fn_scope = stack()[2][0]# get calling functioncalling_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)

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

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

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

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

def Fibonacci(n):if n<2: return nFibonacci.memo=vars(Fibonacci).setdefault('memo',{}) # use static variable to hold a results cachereturn 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 memoif n<2: return nreturn memo.setdefault(n,Fibonacci(n-1)+Fibonacci(n-2))...print (Fibonacci(200))...

还可以考虑:

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

推理:

  • 多pythonic(“请求宽恕而不是许可”)
  • 使用异常(只抛出一次)而不是if分支(想想停止迭代异常)

这个问题的提示下,我可以提出另一种可能更好用的替代方案,并且对于方法和函数看起来都一样:

@static_var2('seed',0)def funccounter(statics, add=1):statics.seed += addreturn statics.seed
print funccounter()       #1print funccounter(add=2)  #3print funccounter()       #4
class ACircle(object):@static_var2('seed',0)def counter(statics, self, add=1):statics.seed += addreturn statics.seed
c = ACircle()print c.counter()      #1print c.counter(add=2) #3print c.counter()      #4d = ACircle()print d.counter()      #5print d.counter(add=2) #7print 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] = valdef __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 = wrappedelse:f = original #already wrapped
getattr(f, ':staticman')[name] = valreturn freturn decorator

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

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

没有try/除了,没有测试hasattr,只是默认的getattr。

Python方法中的静态变量

class Count:def foo(self):try:self.foo.__func__.counter += 1except AttributeError:self.foo.__func__.counter = 1
print self.foo.__func__.counter
m = Count()m.foo()       # 1m.foo()       # 2m.foo()       # 3
def staticvariables(**variables):def decorate(function):for variable in variables:setattr(function, variable, variables[variable])return functionreturn decorate
@staticvariables(counter=0, bar=1)def foo():print(foo.counter)print(foo.bar)

就像文森特上面的代码一样,这将被用作函数装饰器,并且必须以函数名作为前缀访问静态变量。这段代码的优点(尽管不可否认任何人都可能足够聪明地弄清楚)是您可以有多个静态变量并以更常规的方式初始化它们。

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

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

第二个问题的惯用python可能会用前导下划线命名变量,以表明它不应该被访问,同时在事后保持它的可访问性。

使用闭包

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

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

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

使用内部状态参数

另一个选项可能是作为可变值容器的未记录参数。

def counter(*, _i=[0]):_i[0] += 1return _i[0]

这是有效的,因为默认参数是在定义函数时评估的,而不是在调用函数时评估的。

清洁器可能是有一个容器类型而不是列表,例如。

def counter(*, _i = Mutable(0)):_i.value += 1return _i.value

但我不知道一个内置类型,这清楚地传达了目的。

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

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

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

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

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

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

随着性能问题的解决,try/catch似乎会产生最未来和最可靠的代码,所以也许只需将其包装在函数中

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

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

请参阅这里了解它是如何工作的。

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

既然你给出了一个写C++的例子,我将首先解释C++中的“函数对象”是什么。“函数对象”只是任何重载operator()的类。类的实例将表现得像函数。例如,你可以写int x = square(5);,即使square是一个对象(重载operator()),而不是技术上不是“函数”。你可以给函数对象任何可以给类对象的特征。

# C++ function objectclass 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定义:

class Foo_class:def __init__(self): # __init__ is similair to a C++ class constructorself.counter = 0# self.counter is like a static member# variable of a function named "foo"def __call__(self): # overload operator()self.counter += 1print("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 1counter is 2counter is 3counter is 4counter is 5

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

# FILE: foo.py - - - - - - - - - - - - - - - - - - - - - - - - -
class Foo_class:def __init__(self):self.counter = 0def __call__(self, x, y, z): # overload operator()self.counter += 1print("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 1x, y, z, are 7, 8, 9counter is 2x, y, z, are 7, 8, 9counter is 3x, y, z, are 7, 8, 9counter is 4x, y, z, are 7, 8, 9counter is 5x, y, z, are 7, 8, 9

这个回答是以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 funcreturn decorate
@static_vars(name = "Martin")def my_function():statics = my_function.staticsprint("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

结果n+=1

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

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

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

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

有关nonlocal语句的规范,请参阅PEP 3104

如果计数器对模块是私有的,则应将其命名为_counter

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

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

根据丹尼尔的回答(补充):

class Foo(object):counter = 0
def __call__(self, inc_value=0):Foo.counter += inc_valuereturn 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 :yelloyello

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

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

counter = 0
def foo():global countercounter += 1print("counter is {}".format(counter))
foo() #output: "counter is 1"foo() #output: "counter is 2"foo() #output: "counter is 3"

我写了一个简单的函数来使用静态变量:

def Static():### get the func object by which Static() is called.from inspect import currentframe, getframeinfocaller = currentframe().f_backfunc_name = getframeinfo(caller)[2]# print(func_name)caller = caller.f_backfunc = caller.f_locals.get(func_name, caller.f_globals.get(func_name))    
class StaticVars:def has(self, varName):return hasattr(self, varName)def declare(self, varName, value):if not self.has(varName):setattr(self, varName, value)
if hasattr(func, "staticVars"):return func.staticVarselse:# add an attribute to funcfunc.staticVars = StaticVars()return func.staticVars

如何使用:

def myfunc(arg):if Static().has('test1'):Static().test += 1else:Static().test = 1print(Static().test)
# declare() only takes effect in the first time for each static variable.Static().declare('test2', 1)print(Static().test2)Static().test2 += 1

使用装饰器和闭包

以下装饰器可用于创建静态函数变量。它用自身的返回替换声明的函数。这意味着装饰的函数必须返回一个函数。

def static_inner_self(func):return func()

然后在返回另一个带有捕获变量的函数的函数上使用装饰器:

@static_inner_selfdef foo():counter = 0def foo():nonlocal countercounter += 1print(f"counter is {counter}")return foo

nonlocal是必需的,否则Python认为counter变量是局部变量而不是捕获变量。Python的行为是因为变量赋值counter += 1。函数中的任何赋值都会让Python认为该变量是局部的。

如果你没有在内部函数中赋值给变量,那么你可以忽略nonlocal语句,例如,在这个函数中,我用来缩进字符串的行,Python可以在其中推断变量是nonlocal

@static_inner_selfdef indent_lines():import rere_start_line = re.compile(r'^', flags=re.MULTILINE)def indent_lines(text, indent=2):return re_start_line.sub(" "*indent, text)return indent_lines

附:有一个删除的答案提出了同样的建议。我不知道作者为什么删除它。https://stackoverflow.com/a/23366737/195417

米格尔·安吉洛的自我重新定义解决方案甚至可以在没有任何装饰器的情况下实现:

def fun(increment=1):global funcounter = 0def fun(increment=1):nonlocal countercounter += incrementprint(counter)fun(increment)
fun()    #=> 1fun()    #=> 2fun(10)  #=> 12

第二行必须适应以获得有限的范围:

def outerfun():def innerfun(increment=1):nonlocal innerfuncounter = 0def innerfun(increment=1):nonlocal countercounter += incrementprint(counter)innerfun(increment)
innerfun()    #=> 1innerfun()    #=> 2innerfun(10)  #=> 12
outerfun()

装饰师的好处是你不必额外注意你的建筑范围。