此C / C ++代码的惯用Python等效项是什么?
void foo()
{
static int counter = 0;
counter++;
printf("counter is %d\n", counter);
}
具体来说,如何在函数级别而非类级别实现静态成员?并将函数放入类中是否会发生任何变化?
此C / C ++代码的惯用Python等效项是什么?
void foo()
{
static int counter = 0;
counter++;
printf("counter is %d\n", counter);
}
具体来说,如何在函数级别而非类级别实现静态成员?并将函数放入类中是否会发生任何变化?
Answers:
有点相反,但这应该起作用:
def foo():
foo.counter += 1
print "Counter is %d" % foo.counter
foo.counter = 0
如果要将计数器初始化代码放在顶部而不是底部,则可以创建一个装饰器:
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
foo.
不幸的是,它仍然需要您使用前缀。
(信用:@ony)
if "counter" not in foo.__dict__: foo.counter = 0
作为的第一行foo()
。这将有助于避免函数外的代码。不确定在2008年是否有可能。PS在寻找创建静态函数变量的可能性时找到了这个答案,因此该线程仍然“活跃” :)
foo
并foo.counter =
没有密切的联系。但是,我最终还是喜欢装饰器方法,因为装饰器是不会被调用的,并且它的作用在语义上更加明显(@static_var("counter", 0)
比起if "counter" not in foo.__dict__: foo.counter = 0
,在我看来更容易,对我更有意义),尤其是在后者中,您必须使用函数名称(可能会更改两次)。
def foo():
if not hasattr(foo,"counter"): foo.counter=0
foo.counter += 1
您可以向函数添加属性,并将其用作静态变量。
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
无论如何,静态变量很少见,您应该为该变量找到一个更好的位置,很可能在类中。
try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1
应该使用异常来做同样的事情。
try
增加成本吗?只是好奇。
还可以考虑:
def foo():
try:
foo.counter += 1
except AttributeError:
foo.counter = 1
推理:
if
分支(请考虑StopIteration异常)def fn(): if not hasattr(fn, 'c'): fn.c = 0
fn.c += 1 return fn.c
hasattr()
这样做并不简单,效率也较低。
其他答案已经说明了您应该执行此操作的方式。这是您不应该使用的方法:
>>> def foo(counter=[0]):
... counter[0] += 1
... print("Counter is %i." % counter[0]);
...
>>> foo()
Counter is 1.
>>> foo()
Counter is 2.
>>>
仅在第一次评估该函数时才初始化缺省值,而不是在每次执行该函数时才初始化缺省值,因此可以使用列表或任何其他可变对象存储静态值。
def foo(arg1, arg2, _localstorage=DataClass(counter=0))
我这样的有意义的名字,我觉得它很好读。另一个好处是易于重命名功能。
types.SimpleNamespace
,def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)):
而无需定义特殊的类。
很多人已经建议测试“ hasattr”,但是答案很简单:
def func():
func.counter = getattr(func, 'counter', 0) + 1
没有try / except,没有测试hasattr,只有默认的getattr。
try
/ except
基于方法之间的性能差异是毫无意义的。一个简单的ipython
%%timeit
微基准给成本try
/ except
每调用255纳秒,与263纳秒为getattr
基础的解决方案。是的,try
/ except
速度更快,但这并不是完全“放手”。这是一个微小的微优化。编写看起来更清晰的代码,不必担心这样的琐碎性能差异。
这是一个完全封装的版本,不需要外部初始化调用:
def fn():
fn.counter=vars(fn).setdefault('counter',-1)
fn.counter+=1
print (fn.counter)
在Python中,函数是对象,我们可以通过special属性将成员变量简单地添加或猴子补丁__dict__
。内置vars()
函数返回special属性__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))
...
try: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0
X not in Y
而不是not X in Y
(或建议使用,如果您只是在使用它,以便与进行更相似的比较hasattr
)
def fn(): if not hasattr(fn, 'c'): fn.c = 0
fn.c += 1 return fn.c
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__()
方法来使其实例化。
foo
作为单例提供的实例,那该期待什么呢?
使用生成器函数生成迭代器。
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
。
如果计数器是模块专用的,则应_counter
改为命名。
global counter
语句代替nonlocal counter
(nonlocal
只是让您在嵌套函数中写入闭包状态)来执行此操作。人们向该函数附加属性的原因是避免为该函数特定的状态污染全局命名空间,因此,当两个函数需要独立的counter
s 时,您甚至不必做更棘手的事情。此解决方案无法扩展;功能上的属性。kdb的答案是如何提供nonlocal
帮助,但这确实增加了复杂性。
nonlocal
在global
是完全按照你所指出的-它工作在严格的更多情况。
将函数的属性用作静态变量有一些潜在的缺点:
第二个问题的惯用python可能是用一个前导下划线将变量命名,以表示该变量不是可被访问的,而在事发后仍可访问。
另一种选择是使用词法闭包的模式,这nonlocal
在python 3 中受关键字支持。
def make_counter():
i = 0
def counter():
nonlocal i
i = i + 1
return i
return counter
counter = make_counter()
可悲的是,我不知道将这种解决方案封装到装饰器中的方法。
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的Zen:显式优于隐式):
>>> def func(_static={'counter': 0}):
... _static['counter'] += 1
... print _static['counter']
...
>>> func()
1
>>> func()
2
>>>
请参阅此处以了解其工作原理。
foo()
应该将字典重新初始化为函数定义中指定的值(因此计数器键的值为0)。为什么不呢?
在这个问题的提示下,我可以提出另一种选择,它可能会更好用,并且对于方法和函数来说都一样:
@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
另一个(不建议!)在可调用对象(如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
除了创建具有静态局部变量的函数外,您始终可以创建所谓的“函数对象”并为其提供标准(非静态)成员变量。
由于您提供了用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
全局声明提供了此功能。在下面的示例(使用“ f”的python 3.5或更高版本)中,计数器变量在函数外部定义。在功能中将其定义为全局表示表示该功能之外的“全局”版本应可用于该功能。因此,每次函数运行时,它都会修改函数外部的值,并将其保留在函数之外。
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"
我个人更喜欢以下装饰器。给每个人自己。
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)
此答案基于@claudiu的答案。
我发现,每当我打算访问静态变量时,总是必须在函数名称前加上前缀,我的代码变得不清楚。
即,在我的函数代码中,我更喜欢写:
print(statics.foo)
代替
print(my_function_name.foo)
因此,我的解决方案是:
statics
向函数属性statics
为别名my_function.statics
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
,它是一个支持属性样式访问的字典,即JavaScript(请参见原始文章)。 2000年前后)
可以通过安装 pip install bunch
也可以这样手写:
class Bunch(dict):
def __init__(self, **kw):
dict.__init__(self,kw)
self.__dict__ = self
types.SimpleNamespace
自3.3起可用)开箱即用地支持此行为(并且在CPython上的C中实现,因此它的速度尽可能快)。
基于丹尼尔的答案(补充):
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)
我想添加此部分的原因是,作为一个实际示例,静态变量不仅用于增加某个值,而且还检查静态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 :
yello
yello
如果将Foo类限制性地定义为单例类,那将是理想的。这将使它更具Pythonic性。
当然,这是一个老问题,但我想我可能会提供一些更新。
似乎性能参数已过时。相同的测试套件似乎为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似乎会生成最适合未来和极端情况的代码,因此也许将其包装在函数中
_
前缀。