函数内部的Python等价的静态变量是什么?

and*_*ich 570 python

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

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

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

Cla*_*diu 634

有点逆转,但这应该工作:

def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter
foo.counter = 0
Run Code Online (Sandbox Code Playgroud)

如果您希望计数器初始化代码位于顶部而不是底部,则可以创建装饰器:

def static_var(varname, value):
    def decorate(func):
        setattr(func, varname, value)
        return func
    return decorate
Run Code Online (Sandbox Code Playgroud)

然后使用这样的代码:

@static_var("counter", 0)
def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter
Run Code Online (Sandbox Code Playgroud)

foo.不幸的是,它仍然需要你使用前缀.


编辑(感谢ony):这看起来更好:

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
Run Code Online (Sandbox Code Playgroud)

  • 很抱歉挖掘它,但我宁愿把'if'反击"不在foo .__ dict__:foo.counter = 0`作为`foo()`的第一行.这有助于避免函数外的代码.不确定这是否有可能在2008年.PS在寻找创建静态函数变量的可能性时找到了这个答案,所以这个线程仍然"活着":) (113认同)
  • 只有一个foo实例 - 这个功能.所有调用都访问同一个变量. (20认同)
  • @binaryLV:我可能更喜欢第一种方法.第一种方法的问题是`foo`和`foo.counter =`密切相关并不是很明显.但是,我最终更喜欢装饰器方法,因为没有办法不会调用装饰器,它在语义上更明显它的作用(`@static_var("counter",0)`更容易开启并且对我的眼睛更有意义比'if'反击"不在foo .__ dict__:foo.counter = 0`,特别是在后者你必须使用可能改变的函数名称(两次). (7认同)
  • `def foo():` `如果不是 hasattr(foo,"counter"): foo.counter=0` `foo.counter += 1` (5认同)
  • @lpapp:这取决于静态变量的作用点.我一直认为它是多个函数调用的相同值,这确实满足.我从来没有把它当作变量隐藏,这不是,正如你所说的那样. (4认同)
  • 我在早些时候匆忙推翻了这个答案,但我认为这是误导,因为python不提供类似C/C++函数的静态变量.即使有了这个"解决方案",你也可以轻松访问函数外的"函数静态"变量,这有点难以理解.是的,我知道你也可以通过原始内存访问C/C++中的这些变量,但是,这个答案只是低声说它是一个真正的替代品,而它不是,因此它可能没有太多实际用途恕我直言. (2认同)
  • 还应该说:不要这样做 (2认同)
  • @binaryLV 您的建议是否会在每次调用函数时产生额外的开销,因为解释器现在必须在每次调用函数时处理额外的“if”子句? (2认同)

vin*_*ent 210

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

def myfunc():
  myfunc.counter += 1
  print myfunc.counter

# attribute must be initialized
myfunc.counter = 0
Run Code Online (Sandbox Code Playgroud)

或者,如果您不想在函数外部设置变量,则可以使用hasattr()以避免AttributeError异常:

def myfunc():
  if not hasattr(myfunc, "counter"):
     myfunc.counter = 0  # it doesn't exist yet, so initialize it
  myfunc.counter += 1
Run Code Online (Sandbox Code Playgroud)

无论如何,静态变量相当罕见,你应该为这个变量找到一个更好的位置,很可能是在一个类中.

  • `try:myfunc.counter + = 1; 除了AttributeError:myfunc.counter = 1`应该做同样的事情,而不是使用异常. (12认同)
  • @Hack_Saw:嗯,这是Pythonic(更好地要求宽恕而不是许可).这实际上是在Python优化技术中推荐的,因为它节省了if的成本(尽管我不建议过早优化).关于特殊情况的规则:1.在某种意义上,失败是一种例外情况.它只发生过一次.我认为该规则是关于使用(即提高)例外.对于您期望工作但有备份计划的事情,这是一个例外,这在大多数语言中都很常见. (11认同)
  • 为什么不尝试而不是if语句? (6认同)
  • Exceptions 应该用于 Exceptional 情况,即程序员期望不会发生的情况,例如它成功打开的输入文件突然不可用。这是预期的情况,if 语句更有意义。 (2认同)
  • @leewangzhong:在 `try` 中封闭一个不会引发异常的块会增加任何成本吗?只是好奇。 (2认同)

rav*_*yla 180

人们还可以考虑:

def foo():
    try:
        foo.counter += 1
    except AttributeError:
        foo.counter = 1
Run Code Online (Sandbox Code Playgroud)

推理:

  • 多pythonic(ask for forgiveness not permission)
  • 使用异常(仅抛出一次)而不是if分支(想想StopIteration异常)

  • 这是正确的解决方案,它应该是可接受的答案,因为初始化代码将在调用函数时运行,而不是在执行模块或导入某些内容时运行,如果您使用装饰器方法,则会出现这种情况.目前接受的答案.请参阅[Python装饰器功能执行](http://stackoverflow.com/questions/36435236/python-decorator-function-execution).如果你有一个巨大的库模块,那么每个装饰器都会被运行,包括那些你没有导入的函数. (13认同)
  • 我没有长时间使用Python,但这满足了该语言的隐含权限之一:**如果它不(非常)容易,那么你做错了**. (8认同)
  • @MANU为此使用“ hasattr()”并不简单,而且效率也较低。 (3认同)
  • 不能立即使用类方法,“self.foo.counter = 1”再次引发 AttributeError。 (2认同)
  • 一个更简单的方法:`def fn():如果不是hasattr(fn,'c'):fn.c = 0``fn.c + = 1返回fn.c` (2认同)

小智 43

其他答案已经证明了你应该这样做的方式.这是你不应该这样做的方式:

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

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

  • 为什么说您不应该那样做?对我来说看起来很合理! (2认同)
  • @VPfB:对于一般存储,您可以使用 [`types.SimpleNamespace`](https://docs.python.org/3/library/types.html#types.SimpleNamespace),使其成为 `def foo(arg1, arg2) , _staticstorage=types.SimpleNamespace(counter=0)):` 无需定义特殊类。 (2认同)

Jon*_*han 33

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

def func():
    func.counter = getattr(func, 'counter', 0) + 1
Run Code Online (Sandbox Code Playgroud)

没有尝试/除,没有测试hasattr,只有getattr默认.

  • @MarkLawrence:实际上,至少在我的 Windows x64 3.8.0 安装上,这个答案与 [ravwojdyla 等效的基于 `try`/` except` 的方法之间的性能差异](/sf/answers/1135015731/ )是毫无意义的。一个简单的“ipython”“%%timeit”微基准测试显示每次调用“try”/“ except”的成本为 255 纳秒,而基于“getattr”的解决方案为 263 纳秒。是的,“try”/“ except”更快,但它并不完全是“轻松获胜”;这是一个微小的优化。编写看起来更清晰的代码,不要担心像这样的微不足道的性能差异。 (4认同)
  • 每次调用 func 时只调用 getattr 。如果性能不是问题,那很好,如果它是 try/except 将赢得胜利。 (3认同)
  • 当你在那里放一个 func 时注意 getattr 的第三个参数,例如: def func(): def foo(): return 1112 func.counter = getattr(func, 'counter', foo()) + 1 when you call func,将始终调用 foo! (2认同)
  • @ShadowRanger 感谢您对此进行基准测试。两年来我一直对马克劳伦斯的说法感到好奇,我很高兴你做了这项研究。我绝对同意你的最后一句话 - “编写看起来更清晰的代码” - 这正是我写这个答案的原因。 (2认同)

dan*_*els 25

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
Run Code Online (Sandbox Code Playgroud)

请注意,__call__使类(对象)的实例可以通过其自己的名称进行调用.这就是为什么调用foo()上面调用类的__call__方法.从文档:

通过__call__()在类中定义方法,可以使任意类的实例可调用.

  • 函数已经是对象,所以这只是添加了一个不必要的层. (15认同)

Ria*_*zvi 25

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

def fn():
    fn.counter=vars(fn).setdefault('counter',-1)
    fn.counter+=1
    print (fn.counter)
Run Code Online (Sandbox Code Playgroud)

在Python中,函数是对象,我们可以通过特殊属性简单地向其添加或修补成员变量__dict__.内置vars()返回特殊属性__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
Run Code Online (Sandbox Code Playgroud)

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))
    ...
Run Code Online (Sandbox Code Playgroud)

  • 唯一的问题是它真的一点也不整洁,每当你想使用这种模式时,你必须剪切和粘贴代码......因此我使用了装饰器 (2认同)
  • 可能应该使用类似`try:mystaticfun.counter + = 10的东西,除了AttributeError:mystaticfun.counter = 0` (2认同)
  • 请使用“不在Y中的X”而不是“不在Y中的X”(或建议使用,如果您只是为了与“ hasattr”之间看起来更相似的比较而使用它) (2认同)

gnu*_*nud 14

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

def foo_gen():
    n = 0
    while True:
        n+=1
        yield n
Run Code Online (Sandbox Code Playgroud)

然后就像使用它一样

foo = foo_gen().next
for i in range(0,10):
    print foo()
Run Code Online (Sandbox Code Playgroud)

如果你想要一个上限:

def foo_gen(limit=100000):
    n = 0
    while n < limit:
       n+=1
       yield n
Run Code Online (Sandbox Code Playgroud)

如果迭代器终止(如上例所示),你也可以直接循环它,比如

for i in foo_gen(20):
    print i
Run Code Online (Sandbox Code Playgroud)

当然,在这些简单的情况下,最好使用xrange :)

以下是yield语句的文档.


cba*_*ick 8

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

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

counter = 0
def foo():
    nonlocal counter
    counter += 1
    print(f'counter is {counter}')
Run Code Online (Sandbox Code Playgroud)

有关声明的说明,请参见PEP 3104nonlocal

  • 即使在 Python 3 之前,您始终可以使用“全局计数器”语句而不是“非本地计数器”来执行此操作(“非本地”仅允许您在嵌套函数中写入闭包状态)。人们将属性附加到函数的原因是为了避免污染函数特定状态的全局命名空间,因此当两个函数需要独立的“计数器”时,您不必做更黑客的事情。该解决方案无法扩展;函数上的属性可以。[kdb 的答案](/sf/answers/2224901311/) 是 `nonlocal` 可以提供帮助的方式,但它确实增加了复杂性。 (3认同)

Gio*_*iuc 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)
Run Code Online (Sandbox Code Playgroud)

与上面的vincent代码非常相似,它将用作函数装饰器,并且必须使用函数名作为前缀来访问静态变量.这段代码的优点(尽管可以说任何人都可能足够聪明地弄明白)是你可以拥有多个静态变量并以更传统的方式初始化它们.


kdb*_*kdb 7

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

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

第二个问题的惯用python可能是用一个前导下划线命名变量,表示它不是要访问的,同时在事后保持可访问性.

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

def make_counter():
    i = 0
    def counter():
        nonlocal i
        i = i + 1
        return i
    return counter
counter = make_counter()
Run Code Online (Sandbox Code Playgroud)

可悲的是,我知道无法将此解决方案封装到装饰器中.


war*_*iuc 7

更具可读性,但更冗长(Python的Zen:显式优于隐式):

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

请参阅此处以了解其工作原理。

  • @raffamaiden:默认参数仅在定义函数时计算一次,而不是每次调用函数时计算。 (4认同)

Dav*_*ave 6

_counter = 0
def foo():
   global _counter
   _counter += 1
   print 'counter is', _counter

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


Ted*_*ddy 5

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

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

或者可能是发电机,如果您的使用模式适合的话。


wan*_*nik 5

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
Run Code Online (Sandbox Code Playgroud)


VPf*_*PfB 5

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

import types

def func(_static=types.SimpleNamespace(counter=0)):
    _static.counter += 1
    print(_static.counter)
Run Code Online (Sandbox Code Playgroud)


小智 5

全局声明提供了此功能。在下面的示例中(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"
Run Code Online (Sandbox Code Playgroud)


Mig*_*elo 5

使用装饰器和闭包

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

def static_inner_self(func):
    return func()
Run Code Online (Sandbox Code Playgroud)

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

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

nonlocal是必需的,否则Python认为该counter变量是局部变量而不是捕获的变量。由于变量赋值,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
Run Code Online (Sandbox Code Playgroud)

PS 有一个已删除的答案提出了相同的建议。不知道作者为什么删了。 /sf/answers/1635671621/