Python类方法中的C语言风格静态变量

3

我有20年的C++经验,现在学习Python还有些困难。

现在我想要一个拥有自己“静态”变量(不是静态变量)的方法(一个类内的函数)。

可能一个伪代码示例可以更好地说明我想要什么。

最初的回答:

class dummy:
    @staticmethod
    def foo():
        foo.counter += 1
        print "You have called me {} times.".format(foo.counter)
    foo.counter = 0

注意1:我只是为了简单起见使用了@staticmethod,但这不相关。

注意2:这会崩溃并出现AttributeError: 'staticmethod' object has no attribute 'counter',但正如我上面所说,这只是伪代码,以阐明我的目标。

我已经学会了这个在类外部可以运行:

Original Answer翻译成"最初的回答"

def foo():
    foo.counter += 1
    print "You have called me {} times.".format(foo.counter)
foo.counter = 0

但对于成员函数,同样的技巧似乎不起作用。
最后一分钟的信息,我只能使用Python 2.7(不是我的选择)。
是否有任何合法可靠的方法可以拥有一个作用域仅限于成员函数作用域的持续变量(或常量)?
一些相关链接:
"What is the Python equivalent of static variables inside a function?": https://dev59.com/oHVC5IYBdhLWcg3wfhGL#279586 "There are no function-level static variables in Python": https://code-maven.com/slides/python-programming/static-variable 先行致谢。

当你想要为该类预定义值或计算时,可以使用静态(static)关键字。在第一个链接中,foo被用作装饰器。如果你想在类中使用foo.counter,那么在类中创建一个counter变量(而不是在__init__中),并在函数中每次调用时递增它。 - undefined
@prashantrana 抱歉,我无法理解你的评论。你能否详细说明一下? - undefined
没有理由需要一个类的方法拥有自己的变量。只需将变量分配给类即可。如果不将其设为staticmethod,方法将能够自由地修改该变量。 - undefined
你试图模拟什么,以至于类变量不足以满足需求? - undefined
感谢 @EdwardMinnix 的回复。正如我所提到的,我来自 C++ 的背景,因此,拥有一个在类级别上无用的东西会污染和膨胀类的命名空间,并且还会为误用留下漏洞。通过将其放在函数范围内,我将百分之百地安全。 - undefined
1
@j4x 你希望方法内的静态变量能够在多个对象之间共享吗?(此外:为了避免命名空间污染,习惯上会在名称中添加 _ 表示这些是实现细节,不建议进行修改。) - undefined
5个回答

2

正如@Prune已经提到的,没有真正的方法可以这样做。

然而,如果你希望方法内的静态变量仅对其所属的对象可用(就像我记得的C++一样),你应该在构造函数中定义它或将其定义为一个非静态方法的类变量:

from __future__ import print_function

class dummy:
    def __init__(self, counter=0):
        self._foo_counter = 0

    def foo(self):
        self._foo_counter += 1
        print("You have called me {} times.".format(self._foo_counter))

或者:

class dummy:
    def foo(self):
        self._foo_counter += 1
        print("You have called me {} times.".format(self._foo_counter))

    _foo_counter = 0

这样,运行以下代码:
x = dummy()
for _ in range(4):
    x.foo()

y = dummy()
for _ in range(4):
    y.foo()

结果为:

You have called me 1 times.
You have called me 2 times.
You have called me 3 times.
You have called me 4 times.
You have called me 1 times.
You have called me 2 times.
You have called me 3 times.
You have called me 4 times.

请注意,这两个版本的行为并不完全相同。 当您直接在类中定义_foo_counter时,您将可以访问_foo_counter变量,包括对象(self._foo_counter)和类本身(dummy._foo_counter)。 dummy._foo_counter将对类的每次使用保持静态,并且将跨多个类实例持久存在,因此跨多个对象。 这也是您可以访问的唯一变量,如果您在dummy.foo()上使用@staticmethod装饰器的话。
class dummy:
    @staticmethod
    def foo():
        dummy._foo_counter += 1
        print("You have called me {} times.".format(dummy._foo_counter))

    _foo_counter = 0

这里,self或者_foo_counter将无法访问,你唯一的选择是使用dummy._foo_counter这个类范围的变量(正如前面提到的,你也可以在没有被@staticmethod修饰的方法中使用它)。所以再次运行:
x = dummy()
for _ in range(4):
    x.foo()

y = dummy()
for _ in range(4):
    y.foo()

结果为:
You have called me 1 times.
You have called me 2 times.
You have called me 3 times.
You have called me 4 times.
You have called me 5 times.
You have called me 6 times.
You have called me 7 times.
You have called me 8 times.

2

实现这一点的一种方法是将变量藏在闭包中,这样它对于您的目的来说就是静态的。不幸的是,Python 2不支持nonlocal关键字,因此我们必须将变量的值包装在对象中(除非您只是想在方法中引用而不是改变变量(即将变量赋值):

最初的回答:

要实现这一点,可以将变量放入闭包中,使其对于您的目的而言实际上是静态的。但不幸的是,Python 2不支持nonlocal关键字,因此我们必须将变量的值包装在对象中(除非您只是想在方法中引用变量而不修改它)。

In [7]: class _Nonlocal:
   ...:     def __init__(self, value):
   ...:         self.counter = value
   ...:
   ...: def foo_maker():
   ...:     nonlocal = _Nonlocal(0)
   ...:     def foo(self):
   ...:         nonlocal.counter += 1
   ...:         print "You have called me {} times.".format(nonlocal.counter)
   ...:     return foo
   ...:

In [8]: class Dummy(object): #you should always inherit from object explicitely in python 2
   ...:     foo = foo_maker()
   ...:

In [9]: dummy = Dummy()

In [10]: dummy.foo()
You have called me 1 times.

In [11]: dummy.foo()
You have called me 2 times.

当然,这样做纯粹是为了避免使用实例变量而导致繁琐复杂。也许最好的解决方案是将您的方法变成自定义对象,并实现描述符协议使其可以被调用作为一个方法,如果需要,它也可以作为实例方法来使用:


In [35]: import types
    ...:
    ...: class Foo(object):
    ...:     def __init__(this):
    ...:         this.counter = 0
    ...:     def __call__(this, self):
    ...:         this.counter += 1
    ...:         print "You have called me {} times.".format(this.counter)
    ...:         print "here is some instance state, self.bar: {}".format(self.bar)
    ...:     def __get__(this, obj, objtype=None):
    ...:         "Simulate func_descr_get() in Objects/funcobject.c"
    ...:         if obj is None:
    ...:             return this
    ...:         return types.MethodType(this, obj)
    ...:

In [36]: class Dummy(object): #you should always inherit from object explicitely in python 2
    ...:     foo = Foo()
    ...:     def __init__(self):
    ...:         self.bar = 42
    ...:

In [37]: dummy = Dummy()

In [38]: dummy.foo()
You have called me 1 times.
here is some instance state, self.bar: 42

In [39]: dummy.bar = 99

In [40]: dummy.foo()
You have called me 2 times.
here is some instance state, self.bar: 99

所有这些对于习惯于Python约定的其他人来说都是非常不规则和令人困惑的,尽管我希望您能看到,Python数据模型提供了许多自定义功能。
请注意,我使用“this”作为第一个参数的名称,以避免与实际来自对象的“self”混淆,该对象将绑定到“Foo”作为方法。
再次强调,我永远不会这样做。我只会使用实例变量,或者如果函数需要保持状态并且可以用作迭代器,则可能使用生成器。
"最初的回答"

很好。你解释了语言没有支持,但仍然提供了一个优雅的解决方案,以避免破坏封装性。谢谢@juanpa.arrivillaga - undefined

2
不,没有。你已经找到了Python版本:一个类变量,你作为class dummy开发的至高无上的统治者,只能在函数foo内访问。
如果了解其背景会有所帮助,你可以从这里开始探索:为什么Python没有静态变量? 我相信你已经阅读了很多相关内容;然而,这个答案提供了Python特定的更Pythonic的实现方式来满足你的需求。

1

使用可变类型作为函数关键字参数的默认值可能是最简单的方法:

class Dummy:

    @staticmethod
    def foo(_counter=[0]):   # here using a list, but you could use a dictionary, or a deque
        _counter[0] += 1
        print "You have called me {} times.".format(_counter[0])

这个变量只被初始化一次,并且它的最新值会保留在形成的闭包中。

0

我已经在一个旧帖子中发布过这个问题,但是没有人注意到。

由于静态变量在我这里有不同的习惯性用途,我想要阐述以下内容: 在一个函数中,我想要只初始化一次一个可能会比较昂贵的计算值。 作为一个喜欢优雅编写代码的老式C程序员,我尝试了定义一个类似宏的写法:

def  Foo () :
   StaticVar( Foo, ‘Var’, CalculateStatic())
   StaticVar( Foo, ‘Step’, CalculateStep())
   Foo.Var += Foo.Step
   print(‘Value of Var : ‘, Foo.Var)

然后,我像这样编写了 ‘StaticVar’:
  def StaticVar(Cls, Var, StaticVal) :
     if not hasattr(Cls, Var) :
        setattr(Cls, Var, StaticVal)

我甚至可以用Python写出更好的代码:

def StaticVars(Cls, **Vars) :
    for Var, StaticVal in Vars.items() :
        if not hasattr(Cls, Var) :
            setattr(Cls, Var, StaticVal)

  def  Foo () :
      StaticVars( Foo, Var = CalculateStatic(),Step= CalculateStep()))
      Foo.Var += Foo. Step
      print(‘Value of Var : ‘, Foo.Var)

当然,这是一种很好的编码方式,但我的目标(只调用一次初始化函数)没有实现(只需在初始化函数中添加一个打印语句即可看到它被频繁调用的情况)!事实上,在函数调用中,参数值在函数被调用之前就已经被评估了。
def CalculateStatic() :
    print("Costly Initialization")
    return 0

def CalculateStep() :
    return 2

def Test() :
    Foo()
    Foo()
    Foo()

>>> Test()
Costly Initialization
Value of Var : 2
Costly Initialization
Value of Var : 4
Costly Initialization
Value of Var : 6

为了达到我的目标,我更愿意写出这样的代码:
def  Foo () :
    if not hasattr(Foo, ‘Var’) :
        setattr ( Foo, ‘Var’, CalculateStatic())
        setattr ( Foo, ‘Step’, CalculateStep())

    Foo.Var += Foo. Step
    print(‘Value of Var : ‘, Foo.Var)

>>> Test()
Costly Initialization
Value of Var : 2
Value of Var : 4
Value of Var : 6

而且它可以像这样“很好地编写”(我使用下划线表示“private == static”):

def StaticVars(Cls, **Vars) :
    for Var, StaticVal in Vars.items() :
        setattr(Cls, Var, StaticVal)

def  Foo () :
    _ = Foo
    try :
        __ = _.Var
    except AttributeError : # The above code could only generate AttributeError Exception
                    # the following code is executed only once
        StaticDefVars(_, Var= CalculateStatic(), Step = CalculateStep())

    _.Var += _. Step
    print(‘Value of Var : ‘, Foo.Var)

注意不要将“计算代码”放在“try”语句中,这可能会生成额外的“AttributeError”异常。

当然,如果Python有“宏预处理”功能,会更好。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接