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 |
不幸的是,它仍然需要您使用
编辑(多亏了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 |
或者,如果不想在函数外部设置变量,可以使用
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 |
注意,
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中,函数是对象,我们可以通过特殊的属性
编辑:注意,与可选的
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:我只建议在从多个位置调用函数时使用上述方法。如果只在一个地方调用函数,最好使用
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中由
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中,正确的方法是使用
1 2 3 4 5 | counter = 0 def foo(): nonlocal counter counter += 1 print(f'counter is {counter}') |
在这个问题的提示下,我可以提出另一个更好的选择吗?对于方法和函数,这两个选择看起来都一样:
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 |
如果您喜欢这个用法,下面是实现:
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++中的"函数对象"是什么。"函数对象"只是带有重载
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中,我们也可以重载
以下是类定义:
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 |
如果希望函数接受输入参数,也可以将这些参数添加到
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) |
因此,我的解决方案是:
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)) |
备注
我的方法使用一个名为
可通过
也可以这样手写:
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。