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

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

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


当前回答

使用装饰器和闭包

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

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

其他回答

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

你也可以考虑:

def foo():
    try:
        foo.counter += 1
    except AttributeError:
        foo.counter = 1

推理:

非常python化(“请求原谅而不是允许”) 使用异常(只抛出一次)而不是if分支(考虑StopIteration异常)

你可以创建一个所谓的“函数对象”,并给它一个标准的(非静态的)成员变量,而不是创建一个具有静态局部变量的函数。

既然你给出了一个c++编写的例子,我将首先解释什么是c++中的“函数对象”。“函数对象”就是任何带有重载操作符()的类。类实例的行为类似于函数。例如,你可以写int x = square(5);即使square是一个对象(带有重载操作符()),从技术上讲也不是一个“函数”。你可以给一个函数对象任何你可以给一个类对象的特性。

# 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

当然,这是一个老问题,但我想我可以提供一些更新。

看来性能论点已经过时了。 对于siInt_try和isInt_re2,相同的测试套件似乎给出了类似的结果。 当然,结果会有所不同,但这是在我的计算机上使用python 3.4.4的一次会话,使用Xeon W3550的内核4.3.01。 我已经运行了几次,结果似乎相似。 我将全局正则表达式移动到函数静态,但性能差异可以忽略不计。

isInt_try: 0.3690
isInt_str: 0.3981
isInt_re: 0.5870
isInt_re2: 0.3632

考虑到性能问题,try/catch似乎可以生成最适合未来和墙角情况的代码,所以可能只是将其包装在函数中

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

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