【问题标题】:What is the Python equivalent of static variables inside a function?函数中静态变量的 Python 等价物是什么?
【发布时间】:2016-08-26 14:45:55
【问题描述】:

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

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

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

【问题讨论】:

  • 恐怕有 NO 等价。即使您使用函数属性进行装饰器破解,您也可以访问外部变量,遗憾的是,这有点不合时宜。此外,您必须在函数中硬编码函数名称,这非常烦人。我建议使用类或模块全局变量,而不是传统的 _ 前缀。
  • 对于非 C 程序员,[stackoverflow.com/questions/5033627/… 函数内的静态变量仅在该函数的范围内可见,但其生命周期是程序的整个生命周期,并且仅初始化一次)。基本上,存在于函数调用之间的持久计数器或存储变量。
  • @lpapp:有一种是class member。您说得对,我们无法阻止其他代码查看或更改它。
  • 我发现 Claudiu 提供的 answer 很有用。

标签: python static


【解决方案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变量是局部变量而不是捕获变量。由于变量赋值counter += 1,Python 的行为就是这样。函数中的任何赋值都会使 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

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

【讨论】:

    【解决方案2】:

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

    import types
    
    def func(_static=types.SimpleNamespace(counter=0)):
        _static.counter += 1
        print(_static.counter)
    

    【讨论】:

      【解决方案3】:

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

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

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

      使用闭包

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

      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
      

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

      【讨论】:

      • 这种内部状态参数的边缘状态让我想起了C++的隐藏朋友习语。
      【解决方案4】:

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

      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
      

      【讨论】:

        【解决方案5】:

        也可以考虑:

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

        推理:

        • 很多pythonic(“请求原谅而不是许可”)
        • 使用异常(只抛出一次)而不是if 分支(想想StopIteration 异常)

        【讨论】:

        • 我做 Python 的时间不长,但这符合该语言的隐含原则之一:如果它不是(相当)容易,那你就错了 .
        • 没有立即使用类方法,“self.foo.counter = 1”再次引发 AttributeError。
        • 这是正确的解决方案,它应该是公认的答案,因为初始化代码将在调用函数时运行,而不是在执行模块或导入其中的某些内容时运行,这是如果您使用当前接受的答案中的装饰器方法,则为这种情况。见Python decorator function execution。如果你有一个巨大的库模块,那么每个装饰器都会运行,包括那些你不导入的函数。
        • 更简单的方法:def fn(): if not hasattr(fn, 'c'): fn.c = 0fn.c += 1 return fn.c
        • @MANU 为此使用hasattr() 并不简单,效率也较低。
        【解决方案6】:

        有点颠倒,但这应该可以:

        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

        【讨论】:

        • 只有一个 foo 实例 - 这一个函数。所有调用都访问同一个变量。
        • 抱歉挖掘了这个,但我宁愿把if "counter" not in foo.__dict__: foo.counter = 0作为foo()的第一行。这将有助于避免函数之外的代码。不过,不确定这在 2008 年是否可行。附言在寻找创建静态函数变量的可能性时找到了这个答案,所以这个线程仍然“活着”:)
        • @binaryLV:我可能更喜欢第一种方法。第一种方法的问题是foofoo.counter = 密切相关并不是很明显。但是,我最终更喜欢装饰器方法,因为不可能不调用装饰器,而且它在语义上的作用更明显(@static_var("counter", 0)if "counter" not in foo.__dict__: foo.counter = 0 更容易理解,对我来说更有意义,尤其是在后者你必须使用可能会改变的函数名(两次)。
        • @lpapp:这取决于静态变量的意义。我一直认为它在多个函数调用中将是相同的值,这确实满足。我从来没有把它当作变量隐藏,正如你所说的那样。
        • def foo(): if not hasattr(foo,"counter"): foo.counter=0 foo.counter += 1
        【解决方案7】:

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

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

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

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

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

        【讨论】:

        • 甚至在 Python 3 之前,你总是可以使用 global counter 语句而不是 nonlocal counter 来做到这一点(nonlocal 只是让你在嵌套函数中写入闭包状态)。人们将属性附加到函数的原因是为了避免污染全局命名空间以获得特定于函数的状态,因此当两个函数需要独立的counters 时,您不必做更麻烦的事情。此解决方案无法扩展;函数做的属性。 kdb's answernonlocal 可以提供帮助的方式,但它确实增加了复杂性。
        • 嗯,我认为工厂函数或装饰器的复杂性是多余的,除非你经常这样做,在这种情况下,设计已经有点臭了。对于一次性,只需添加非本地计数器并完成它。我在有关命名约定的答案中添加了一些内容。另外,我推荐nonlocal 而不是global 的原因正如您所指出的那样——它适用于更多的情况。
        【解决方案8】:

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

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

        请参阅 here 了解其工作原理。

        【讨论】:

        • 你能详细说明为什么这段代码有效吗?第二个foo() 应该将字典重新初始化为函数定义中指定的值(因此计数器键的值为0)。为什么没有?
        • @raffamaiden:默认参数仅在定义函数时评估一次,而不是每次调用函数时。
        【解决方案9】:

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

        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"
        

        【讨论】:

        • 如果使用正确,它的工作方式相同。与 c 代码的不同之处在于,在 OP 的 c 示例中,计数器变量只能由函数触及。 python中的全局变量可以在脚本中的任何地方使用或更改
        【解决方案10】:

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

        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。

        【讨论】:

          【解决方案11】:

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

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

          【讨论】:

          • 唯一的问题是它真的一点也不整洁,而且每当你想使用这种模式时,你必须剪切和粘贴代码......因此我使用了装饰器
          • 可能应该使用类似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 = 0fn.c += 1 return fn.c
          • 不理想,因为 if 子句添加了不必要的嵌套,在这种情况下我更喜欢 setdefault
          【解决方案12】:
          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 的代码,这将用作函数装饰器,并且静态变量必须以函数名作为前缀访问。这段代码的优点(尽管公认的任何人都可能足够聪明地理解它)是您可以拥有多个静态变量并以更传统的方式初始化它们。

          【讨论】:

            【解决方案13】:

            解决方案 n +=1

            def foo():
              foo.__dict__.setdefault('count', 0)
              foo.count += 1
              return foo.count
            

            【讨论】:

              【解决方案14】:

              这个答案建立在@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(参见 original article 关于它,大约 2000 年)

              可以通过pip install bunch安装

              也可以这样手写:

              class Bunch(dict):
                  def __init__(self, **kw):
                      dict.__init__(self,kw)
                      self.__dict__ = self
              

              【讨论】:

              • 注意:types.SimpleNamespace(自 3.3 起可用)开箱即用地支持此行为(并在 CPython 上以 C 语言实现,因此速度尽可能快)。
              【解决方案15】:

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

              由于您给出了一个编写 C++ 的示例,我将首先解释 C++ 中的“函数对象”是什么。 “函数对象”就是任何具有重载operator() 的类。类的实例将表现得像函数。例如,即使square 是一个对象(带有重载的operator())并且在技术上不是“函数”,您也可以编写int x = square(5);。您可以赋予函数对象任何可以赋予类对象的特性。

              # 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
              

              【讨论】:

                【解决方案16】:

                Python 没有静态变量,但您可以通过定义一个可调用的类对象然后将其用作函数来伪造它。 Also see this answer.

                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__ 方法。 From the documentation:

                可以通过在其类中定义__call__() 方法来调用任意类的实例。

                【讨论】:

                • 函数已经是对象,所以这只是添加了一个不必要的层。
                • 请参阅此 SO 答案以获得长期意见,认为这实际上是一个好主意。 stackoverflow.com/questions/460586。我同意让任何这样的类成为单身人士,也许像这样stackoverflow.com/questions/6760685,也是一个好主意。我不知道@S.Lott 所说的“......将计数器移动到类定义......”是什么意思,因为它看起来已经在我的类变量位置。
                • 根据我的研究,此类技术似乎是本页介绍的方法中最“Pythonic”的,并且使用的技巧最少。因此,作为一名新的 Python 开发人员,我计划将其用作函数中类 C 静态变量的首选替代品。
                • 如果我想要 foo1 = Foo() 和 foo2 = Foo() 会发生什么?
                • @MarkLawrence 然后你有两个不同的可调用类实例,每个实例都有自己的计数器。如果您不使用作为单例提供的实例 foo,那么您应该期待什么。
                【解决方案17】:

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

                看来性能论点已经过时了。 对于 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 似乎会产生最适合未来和极端情况的代码,所以也许只需将其包装在函数中

                【讨论】:

                • 你在这里比较什么?这似乎是对其他答案的评论,但不清楚是哪些答案,也没有回答问题本身。
                【解决方案18】:

                另一个(不推荐!)像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
                

                【讨论】:

                  【解决方案19】:

                  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
                  

                  【讨论】:

                    【解决方案20】:

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

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

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

                    【讨论】:

                    • 在放 func 时注意 getattr 的第三个参数 例如: def func(): def foo(): return 1112 func.counter = getattr(func, 'counter', foo ()) + 1 当你调用 func 时,foo 将永远被调用!
                    • 每次调用 func 时只需调用 getattr。如果性能不是问题,那很好,如果它是 try/except 将赢得胜利。
                    • @MarkLawrence:实际上,至少在我的 Windows x64 3.8.0 安装中,这个答案和 ravwojdyla's equivalent try/except based approach 之间的性能差异是毫无意义的。一个简单的ipython%%timeit 微基准测试给出了每次调用try/except 的成本为255 ns,而基于getattr 的解决方案为263 ns。是的,try/except 更快,但并不完全是“获胜”;这是一个微小的微优化。编写任何看起来更清晰的代码,不要担心像这样微不足道的性能差异。
                    • @ShadowRanger 感谢您进行基准测试。两年来我一直想知道 MarkLawrence 的声明,我很高兴你做了这项研究。我绝对同意你的最后一句话——“写任何看起来更清晰的代码”——这正是我写这个答案的原因。
                    【解决方案21】:

                    this question 的提示下,我能否提供另一种替代方案,它可能更好用,并且在方法和功能上看起来都一样:

                    @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
                    

                    【讨论】:

                      【解决方案22】:

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

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

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

                      【讨论】:

                      • 我试过了,但由于某种原因,函数参数将自身初始化为 140,而不是 0。为什么会这样?
                      • @bouvard 对于需要静态变量的递归函数,这是唯一一个真正可读性好的。
                      • 我尝试了几种方法,我希望这个方法被接受为 pythonic。有一些有意义的名字,比如def foo(arg1, arg2, _localstorage=DataClass(counter=0)),我觉得它可读性很好。另一个好处是简单的函数重命名。
                      • 你为什么说你不应该那样做?在我看来完全合理!
                      • @VPfB:对于一般存储,您可以使用types.SimpleNamespace,使其成为def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)):,无需定义特殊类。
                      【解决方案23】:

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

                      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)
                      

                      【讨论】:

                      • 请不要生气,但这个解决方案让我想起了“大公司风格”:-) willa.me/2013/11/the-six-most-common-species-of-code.html
                      • 是的,使用不可移植(堆栈操作通常是 CPython 实现细节,在 PyPy、Jython、IronPython、what-have-you 中不是可以依赖的),脆弱的堆栈操作,与每次使用时调用六个函数方式比一个简单的装饰器要好...
                      【解决方案24】:

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

                      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
                      

                      不管怎样,静态变量相当少见,你应该为这个变量找到一个更好的地方,很可能是在一个类中。

                      【讨论】:

                      • 为什么不试试 if 语句呢?
                      • try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1 也应该这样做,而是使用异常。
                      • Exceptions 应该用于 Exceptional 情况,即程序员期望不会发生的情况,例如它成功打开的输入文件突然不可用。这是预期的情况,if 语句更有意义。
                      • @Hack_Saw:嗯,这是 Pythonic(请求宽恕比请求许可更好)。这实际上是在 Python 优化技术中推荐的,因为它节省了 if 的成本(尽管我不推荐过早优化)。您关于例外情况的规则: 1. 在某种意义上,失败在这里是例外情况。它只发生一次。 2. 我认为该规则是关于使用(即引发)异常的。这是您希望工作但有备份计划的例外情况,这在大多数语言中很常见。
                      • @leewangzhong:在try 中包含一个不会引发异常的块会增加任何成本吗?只是好奇。
                      【解决方案25】:

                      惯用的方式是使用一个class,它可以有属性。如果您需要实例不分开,请使用单例。

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

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

                      【讨论】:

                      • 对于独立的递归函数,default 参数是最优雅的。
                      【解决方案26】:

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

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

                      【讨论】:

                        【解决方案27】:
                        _counter = 0 定义 foo(): 全局计数器 _counter += 1 print 'counter is', _counter

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

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2021-02-14
                          • 1970-01-01
                          • 2017-03-14
                          • 2011-03-17
                          • 1970-01-01
                          相关资源
                          最近更新 更多