首页 文章

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

提问于
浏览
493

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

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

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

26 回答

  • 1

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

    • 每次要访问变量时,都必须写出函数的全名 .

    • 外部代码可以轻松访问变量并弄乱值 .

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

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

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

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

  • 559

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

    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的文档 .

  • 22

    洗脱n = 1

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

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

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

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

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

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

  • 1
    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代码非常相似,它将用作函数装饰器,并且必须使用函数名作为前缀来访问静态变量 . 这段代码的优点(虽然不可否认任何人都可能很聪明地弄明白)是你可以有多个静态变量并以更传统的方式初始化它们 .

  • 4

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

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

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

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

  • 37

    更具可读性,但更详细:

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

    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
    
  • 160

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

    似乎性能论证已经过时了 . 相同的测试套件似乎给出了siInt_try和isInt_re2的类似结果 . 当然结果各不相同,但这是我的计算机上的一个会话,内核4.3.01上使用Xeon W3550的python 3.4.4 . 我已经运行了几次,结果似乎相似 . 我将全局正则表达式转换为静态函数,但性能差异可以忽略不计 .

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

    由于性能问题已经解决,似乎try / catch会生成最具未来和防止角落的代码,所以可能只是将它包装在函数中

  • 0

    这个答案 Build 在@claudiu的回答之上 .

    我发现当我打算访问一个静态变量时,我总是不得不在函数名前加上我的代码变得越来越清晰 .

    也就是说,在我的功能代码中,我更愿意写:

    print(statics.foo)
    

    代替

    print(my_function_name.foo)
    

    所以,我的解决方案是:

    • 为函数添加 statics 属性
      在函数范围
    • 中,将一个局部变量 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
    
  • 6

    我个人更喜欢以下装饰器 . 给每个人自己 .

    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)
    
  • 21
    _counter = 0
    def foo():
       global _counter
       _counter += 1
       print 'counter is', _counter
    

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

  • 3

    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 __()方法,可以使任意类的实例可调用 .

  • 6

    以丹尼尔的答案(补充)为基础:

    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)
    

    我想添加这个部分的原因是,静态变量不仅用于递增某个值,还检查静态var是否等于某个值,作为现实生活中的例子 .

    静态变量仍受保护,仅在函数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 .

  • 10

    全局声明提供此功能 . 在下面的示例中(使用"f"的python 3.5或更高版本),计数器变量在函数外部定义 . 在函数中将其定义为全局表示函数外部的"global"版本应该可用于该函数 . 因此,每次函数运行时,它都会修改函数外部的值,并将其保留在函数之外 .

    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"
    
  • 6

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

    def foo():
        foo.counter += 1
        print "Counter is %d" % foo.counter
    foo.counter = 0
    

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

    def static_var(varname, value):
        def decorate(func):
            setattr(func, varname, value)
            return func
        return decorate
    

    然后使用这样的代码:

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

    不幸的是,它仍然需要你使用 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
    
  • 185

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

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

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

  • 28

    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
    
  • 1

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

    既然你给出了一个写C的例子,我将首先解释"function object"在C中的含义 . "function object"只是具有重载 operator() 的任何类 . 该类的实例将表现得像函数一样 . 例如,即使 square 是一个对象(具有重载 operator() )而且技术上不是"function.",您也可以编写 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
    
  • 1

    另一个(不推荐!)扭曲可调用对象,如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
    
  • 1

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

    有很多方法可以伪造或者将"static"变量伪装成Python(到目前为止没有提到的是有一个可变的默认参数),但这不是 Pythonic, idiomatic 方法 . 只需使用一堂课 .

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

  • 1

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

    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
    

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

  • 1

    人们还可以考虑:

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

    推理:

    • 多pythonic( ask for forgiveness not permission

    • 使用异常(仅抛出一次)而不是 if 分支(想想StopIteration异常)

  • 1

    这个答案表明setdefault并不能真正满足OPs如何创建静态 local 变量的问题 .

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

    它的工作时间和fn一样长 . 以每个变量名称为前缀 . 如果你这样删除它们:

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

    没有错误,但计数器总是0,这告诉我vars(fn)不是访问局部变量,而是一个全局,可能是装饰器或属性存储 .

    如果这个工作,它将是我的首选解决方案 . 但是,由于它没有,我倾向于使用完全封装的类定义来创建这样的静态变量 .

    恕我直言,这是最直截了当的 . 当然,这取决于您是否更熟悉功能与OOP编码样式 .

  • 0

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

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

相关问题