747

这个 C/C++ 代码的惯用 Python 等价物是什么?

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

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

4

27 回答 27

796

有点颠倒,但这应该有效:

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

于 2008-11-10T23:46:00.920 回答
260

您可以向函数添加属性,并将其用作静态变量。

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

无论如何,静态变量相当少见,你应该为这个变量找到一个更好的地方,很可能是在一个类中。

于 2008-11-10T23:53:00.797 回答
243

还可以考虑:

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

推理:

  • 很多pythonic(“请求原谅而不是许可”)
  • 使用异常(只抛出一次)而不是if分支(想想StopIteration异常)
于 2013-04-25T12:16:31.450 回答
67

许多人已经建议测试“hasattr”,但有一个更简单的答案:

def func():
    func.counter = getattr(func, 'counter', 0) + 1

没有 try/except,没有测试 hasattr,只是 getattr 具有默认值。

于 2015-01-05T16:24:14.347 回答
59

其他答案已经展示了您应该这样做的方式。这是一种你不应该的方式:

>>> def foo(counter=[0]):
...   counter[0] += 1
...   print("Counter is %i." % counter[0]);
... 
>>> foo()
Counter is 1.
>>> foo()
Counter is 2.
>>> 

默认值仅在第一次评估函数时初始化,而不是每次执行时初始化,因此您可以使用列表或任何其他可变对象来存储静态值。

于 2008-11-10T23:47:53.770 回答
32

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

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替代方案将不那么干燥和/或有尴尬的流动:

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))
    ...
于 2012-09-04T19:51:35.113 回答
31

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__()在其类中定义方法来调用。

于 2008-11-10T23:53:12.480 回答
16

使用生成器函数生成迭代器。

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

于 2008-11-10T23:37:00.440 回答
15

其他解决方案将计数器属性附加到函数,通常使用复杂的逻辑来处理初始化。这不适合新代码。

在 Python 3 中,正确的方法是使用nonlocal语句:

counter = 0
def foo():
    nonlocal counter
    counter += 1
    print(f'counter is {counter}')

有关语句的规范,请参见PEP 3104 。nonlocal

如果计数器打算对模块私有,则应_counter改为命名。

于 2018-04-08T15:36:57.270 回答
11

使用函数的属性作为静态变量有一些潜在的缺点:

  • 每次要访问变量时,都必须写出函数的全名。
  • 外部代码可以轻松访问变量并弄乱该值。

第二个问题的惯用 python 可能会使用前导下划线来命名变量,以表明它不应该被访问,同时在事后保持它可访问。

使用闭包

另一种方法是使用词法闭包的模式,nonlocalpython 3 中的关键字支持这种模式。

def make_counter():
    i = 0
    def counter():
        nonlocal i
        i = i + 1
        return i
    return counter
counter = make_counter()

可悲的是,我不知道如何将此解决方案封装到装饰器中。

使用内部状态参数

另一种选择可能是用作可变值容器的未记录参数。

def counter(*, _i=[0]):
    _i[0] += 1
    return _i[0]

这是可行的,因为默认参数是在定义函数时评估的,而不是在调用它时。

清洁工可能有一个容器类型而不是列表,例如

def counter(*, _i = Mutable(0)):
    _i.value += 1
    return _i.value

但我不知道明确传达目的的内置类型。

于 2015-08-03T09:50:37.460 回答
10

更易读,但更冗长(Python 之禅:显式优于隐式):

>>> def func(_static={'counter': 0}):
...     _static['counter'] += 1
...     print _static['counter']
...
>>> func()
1
>>> func()
2
>>>

有关其工作原理的说明,请参见此处。

于 2016-08-02T06:02:26.520 回答
9
_counter = 0
定义 foo():
   全局计数器
   _counter += 1
   print 'counter is', _counter

Python 通常使用下划线来表示私有变量。在 C 中,在函数内部声明静态变量的唯一原因是将其隐藏在函数外部,这并不是真正的 Python 惯用语。

于 2008-11-10T23:50:14.670 回答
7
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 的代码,这将用作函数装饰器,并且必须使用函数名称作为前缀来访问静态变量。这段代码的优点(尽管无可否认,任何人都可能足够聪明地弄清楚它)是您可以拥有多个静态变量并以更传统的方式初始化它们。

于 2015-02-09T02:27:39.613 回答
6

在尝试了几种方法后,我最终使用了@warvariuc 答案的改进版本:

import types

def func(_static=types.SimpleNamespace(counter=0)):
    _static.counter += 1
    print(_static.counter)
于 2018-07-20T08:02:10.763 回答
4

惯用的方法是使用可以具有属性的类。如果您需要实例不分开,请使用单例。

有多种方法可以将“静态”变量伪造或混入 Python 中(目前尚未提及的一种方法是具有可变的默认参数),但这不是Pythonic 的惯用方法。只需使用一个类。

如果您的使用模式适合,也可能是生成器。

于 2011-05-16T07:36:30.363 回答
4

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
于 2015-01-13T03:58:38.627 回答
4

另一个(不推荐!)像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
于 2015-12-01T14:47:24.163 回答
4

解决方案 n +=1

def foo():
  foo.__dict__.setdefault('count', 0)
  foo.count += 1
  return foo.count
于 2018-03-26T22:34:36.530 回答
4

全局声明提供了此功能。在下面的示例中(python 3.5 或更高版本使用“f”),计数器变量在函数外部定义。在函数中将其定义为全局意味着函数外部的“全局”版本应该对函数可用。所以每次函数运行时,它都会修改函数外部的值,将其保留在函数之外。

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"
于 2018-09-20T17:46:15.247 回答
3

在这个问题的提示下,我可以提出另一种替代方案,它可能使用起来更好一些,并且对于方法和函数来说看起来都一样:

@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
于 2013-10-01T21:09:43.700 回答
3

您始终可以创建所谓的“函数对象”并为其提供标准(非静态)成员变量,而不是创建具有静态局部变量的函数。

既然您给出了一个用 C++ 编写的示例,我将首先解释 C++ 中的“函数对象”是什么。“函数对象”只是任何具有重载的类operator()。类的实例将表现得像函数。例如,您可以编写int x = square(5);ifsquare是一个对象(带有重载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
于 2017-10-25T22:22:25.003 回答
2

这个答案建立在@claudiu 的答案之上。

我发现每当我打算访问静态变量时,我总是必须在函数名前加上函数名,我的代码变得不那么清晰了。

即,在我的函数代码中,我更愿意编写:

print(statics.foo)

代替

print(my_function_name.foo)

所以,我的解决方案是:

  1. 为函数添加statics属性
  2. 在函数范围内,添加一个局部变量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
于 2018-03-18T11:06:41.603 回答
1

我个人更喜欢以下装饰器。各有各的。

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)
于 2012-06-08T01:15:58.377 回答
1

使用装饰器和闭包

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

def static_inner_self(func):
    return func()

然后在一个函数上使用装饰器,该函数返回另一个带有捕获变量的函数:

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

nonlocal是必需的,否则 Python 认为该counter变量是局部变量而不是捕获的变量。由于变量 assignment ,Python 的行为如此counter += 1。函数中的任何赋值都会使 Python 认为该变量是局部变量。

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

@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

PS有一个已删除的答案提出了相同的建议。不知道作者为什么删了。 https://stackoverflow.com/a/23366737/195417

于 2021-07-08T18:36:48.127 回答
0

基于丹尼尔的回答(补充):

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 被限制性地定义为单例类,那将是理想的。这将使它更加pythonic。

于 2018-08-20T13:27:47.957 回答
0

我编写了一个简单的函数来使用静态变量:

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
于 2020-07-28T09:38:44.480 回答
-2

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

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

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

解决了性能问题,try/catch 似乎会产生最面向未来和最极端的代码,所以也许只是将它包装在函数中

于 2016-01-26T17:40:11.607 回答