这个C/ c++代码在Python中的等效代码是什么?

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

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


当前回答

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

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

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

EDIT:注意,与另一种try不同:除了AttributeError答案外,使用这种方法,变量将始终为初始化后的代码逻辑做好准备。我认为try:except AttributeError替代以下将不那么干和/或有尴尬的流程:

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))
    ...

其他回答

使用generator函数生成迭代器。

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 statement的文档。

使用装饰器和闭包

下面的装饰器可用于创建静态函数变量。它将声明的函数替换为函数本身的返回值。这意味着被修饰的函数必须返回一个函数。

def static_inner_self(func):
    return func()

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

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

nonlocal是必需的,否则Python认为计数器变量是一个局部变量而不是一个捕获变量。Python之所以如此,是因为变量赋值counter += 1。函数中的任何赋值都会使Python认为该变量是局部变量。

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

@static_inner_self
def indent_lines():
    import re
    re_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 Static():
    ### get the func object by which Static() is called.
    from inspect import currentframe, getframeinfo
    caller = currentframe().f_back
    func_name = getframeinfo(caller)[2]
    # print(func_name)
    caller = caller.f_back
    func = 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.staticVars
    else:
        # add an attribute to func
        func.staticVars = StaticVars()
        return func.staticVars

使用方法:

def myfunc(arg):
    if Static().has('test1'):
        Static().test += 1
    else:
        Static().test = 1
    print(Static().test)

    # declare() only takes effect in the first time for each static variable.
    Static().declare('test2', 1)
    print(Static().test2)
    Static().test2 += 1

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

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()的作用域内使用。

在这个例子中,调用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被严格定义为一个单例类,那将是理想的。这将使它更加python化。

有点相反,但这应该是有效的:

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)