什么是一个函数内的静态变量的Python等价物?
什么是这个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。
这是一个完全封装的版本,不需要外部初始化调用:
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',{}) return Fibonacci.memo.setdefault(n,Fibonacci(n-1)+Fibonacci(n-2))
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 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语句的文档。
_counter = 0 def foo(): 全球_counter _counter + = 1 打印'计数器是',_counter
Python通常使用下划线来表示私有变量。 C中声明静态变量的唯一原因就是将它隐藏在函数之外,这不是真正的惯用Python。
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可能会用变长的下划线来命名变量,以表明它不是被访问的,而是在事实之后保持访问。
另外一种方法是使用词法关闭的模式,在Python 3中使用nonlocal
关键字来支持。
def make_counter(): i = 0 def counter(): nonlocal i i = i + 1 return i return counter counter = make_counter()
可悲的是我知道没有办法将这个解决方案封装到装饰器中。
在这个问题的提示下,我可以提出另一个可能更好用的替代方法,对于这两种方法和功能看起来都是一样的:
@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
更可读,但更详细:
>>> def func(_static={'counter': 0}): ... _static['counter'] += 1 ... print _static['counter'] ... >>> func() 1 >>> func() 2 >>>
惯用的方法是使用一个可以具有属性的类。 如果您需要实例不分离,请使用单例。
有很多方法可以将“静态”变量伪装成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()` 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)
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
另一个(不推荐!)扭曲的可调用对象,如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
当然这是一个老问题,但我想我可以提供一些更新。
表演的观点似乎已经过时了。 相同的测试套件似乎给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似乎会产生最具未来性和防止角落代码的代码,所以也许只是将其包装在函数中
你可以不用创建一个具有静态局部变量的函数,而是一直创建一个叫做“函数对象”的对象,并给它一个标准的(非静态的)成员变量。
既然你给了一个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