什么是这个C/C++代码的惯用Python等价物?
void foo()
{
static int counter = 0;
counter++;
printf("counter is %d\n", counter);
}
具体来说,如何在功能级别实现静态成员,而不是类级别?将函数放入类中会改变什么吗?
有点逆转,但这应该工作:
def foo(): foo.counter += 1 print "Counter is %d" % foo.counter foo.counter = 0
如果您希望计数器初始化代码位于顶部而不是底部,则可以创建装饰器:
def static_var(varname, value): def decorate(func): setattr(func, varname, value) return func return decorate
然后使用这样的代码:
@static_var("counter", 0) def foo(): foo.counter += 1 print "Counter is %d" % foo.counter
foo.
不幸的是,它仍然需要你使用前缀.
编辑(感谢ony):这看起来更好:
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
您可以向函数添加属性,并将其用作静态变量.
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
无论如何,静态变量相当罕见,你应该为这个变量找到一个更好的位置,很可能是在一个类中.
人们还可以考虑:
def foo(): try: foo.counter += 1 except AttributeError: foo.counter = 1
推理:
多pythonic(ask for forgiveness not permission
)
使用异常(仅抛出一次)而不是if
分支(想想StopIteration异常)
其他答案已经证明了你应该这样做的方式.这是你不应该这样做的方式:
>>> def foo(counter=[0]): ... counter[0] += 1 ... print("Counter is %i." % counter[0]); ... >>> foo() Counter is 1. >>> foo() Counter is 2. >>>
默认值仅在首次计算函数时初始化,而不是每次执行时初始化,因此您可以使用列表或任何其他可变对象来存储静态值.
很多人已经建议测试'hasattr',但有一个更简单的答案:
def func(): func.counter = getattr(func, 'counter', 0) + 1
没有尝试/除,没有测试hasattr,只有getattr默认.
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__()
在类中定义方法,可以使任意类的实例可调用.
这是一个完全封装的版本,不需要外部初始化调用:
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
以下的替代方案将减少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)) ...
使用生成器函数生成迭代器.
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语句的文档.
其他解决方案通常使用复杂的逻辑来将计数器属性添加到函数,以处理初始化。这不适用于新代码。
在Python 3中,正确的方法是使用以下nonlocal
语句:
counter = 0 def foo(): nonlocal counter counter += 1 print(f'counter is {counter}')
有关声明的说明,请参见PEP 3104nonlocal
。
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代码非常相似,它将用作函数装饰器,并且必须使用函数名作为前缀来访问静态变量.这段代码的优点(尽管可以说任何人都可能足够聪明地弄明白)是你可以拥有多个静态变量并以更传统的方式初始化它们.
使用函数的属性作为静态变量有一些潜在的缺点:
每次要访问变量时,都必须写出函数的全名.
外部代码可以轻松访问变量并弄乱值.
第二个问题的惯用python可能是用一个前导下划线命名变量,表示它不是要访问的,同时在事后保持可访问性.
另一种方法是使用词法闭包的模式nonlocal
,python 3中的关键字支持这种模式.
def make_counter(): i = 0 def counter(): nonlocal i i = i + 1 return i return counter counter = make_counter()
可悲的是,我知道无法将此解决方案封装到装饰器中.
更具可读性,但更冗长(Python的Zen:显式优于隐式):
>>> def func(_static={'counter': 0}): ... _static['counter'] += 1 ... print _static['counter'] ... >>> func() 1 >>> func() 2 >>>
请参阅此处以了解其工作原理。
_counter = 0 def foo(): global _counter _counter += 1 print 'counter is', _counter
Python通常使用下划线来表示私有变量.C在函数内声明静态变量的唯一原因是将它隐藏在函数外部,这不是真正的惯用Python.