题 静态类变量可能吗?


是否可以在python中使用静态类变量或方法?这样做需要什么语法?


1505
2017-09-16 01:46


起源




答案:


在类定义中声明但在方法内部未声明的变量是类或静态变量:

>>> class MyClass:
...     i = 3
...
>>> MyClass.i
3 

作为@millerdev 指出,这创造了一个阶级 i 变量,但这与任何实例级别不同 i 变量,所以你可以

>>> m = MyClass()
>>> m.i = 4
>>> MyClass.i, m.i
>>> (3, 4)

这与C ++和Java不同,但与C#没有什么不同,在C#中,使用对实例的引用无法访问静态成员。

看到 关于类和类对象的主题,Python教程必须说些什么

@Steve Johnson已经回答过 静态方法,也记录在下 Python库参考中的“内置函数”

class C:
    @staticmethod
    def f(arg1, arg2, ...): ...

@beidy建议 类方法在静态方法上,因为该方法接收类类型作为第一个参数,但我对这种方法相对于静态方法的优势仍然有点模糊。如果你也是,那可能没关系。


1479
2017-09-16 01:51



我只是学习Python,但优点 @classmethod 过度 @staticmethod AFAIK是你总是得到调用该方法的类的名称,即使它是一个子类。静态方法缺少此信息,因此它不能调用重写方法。 - Seb
@theJollySin常量的pythonic方法是不为常量增长一个类。只是有一些 const.py 同 PI = 3.14 你可以在任何地方导入它。 from const import PI - Giszmo
这个答案可能会混淆静态变量问题。首先, i = 3 是 不 一个静态变量,它是一个类属性,因为它与实例级属性不同 i 它确实 不 在其他语言中表现得像一个静态变量。看到 米勒德夫的回答, Yann的回答,和 我的答案 下面。 - Rick Teachey
所以只有一份 i(静态变量)即使我创建了这个类的数百个实例,它也会在内存中? - sdream
对于任何感兴趣的人,丹尼尔在@Dubslow评论中提到过,是的 millerdev (回力机) - HeyJude


@Blair Conrad说在类定义中声明的静态变量,但不在方法内部是类或“静态”变量:

>>> class Test(object):
...     i = 3
...
>>> Test.i
3

这里有一些问题。继续上面的例子:

>>> t = Test()
>>> t.i     # static variable accessed via instance
3
>>> t.i = 5 # but if we assign to the instance ...
>>> Test.i  # we have not changed the static variable
3
>>> t.i     # we have overwritten Test.i on t by creating a new attribute t.i
5
>>> Test.i = 6 # to change the static variable we do it by assigning to the class
>>> t.i
5
>>> Test.i
6
>>> u = Test()
>>> u.i
6           # changes to t do not affect new instances of Test

# Namespaces are one honking great idea -- let's do more of those!
>>> Test.__dict__
{'i': 6, ...}
>>> t.__dict__
{'i': 5}
>>> u.__dict__
{}

注意实例变量如何 t.i 在属性时与“static”类变量不同步 i 直接设置 t。这是因为 i 被重新绑定了 t 命名空间,与...不同 Test 命名空间。如果要更改“静态”变量的值,则必须在最初定义它的范围(或对象)中更改它。我把“静态”放在引号中,因为Python在C ++和Java的意义上并没有真正的静态变量。

虽然它没有说明有关静态变量或方法的具体内容,但是 Python教程 有一些相关的信息 类和类对象

@Steve Johnson也回答了有关静态方法的问题,也在Python Library Reference的“内置函数”中进行了介绍。

class Test(object):
    @staticmethod
    def f(arg1, arg2, ...):
        ...

@beid还提到了classmethod,类似于staticmethod。 classmethod的第一个参数是类对象。例:

class Test(object):
    i = 3 # class (or static) variable
    @classmethod
    def g(cls, arg):
        # here we can use 'cls' instead of the class name (Test)
        if arg > cls.i:
            cls.i = arg # would the the same as  Test.i = arg1

Pictorial Representation Of Above Example


525
2017-09-16 03:04



我建议你稍微扩展一下这个例子:如果在设置Test.i = 6后,然后实例化一个新对象(例如,u = Test()),新对象将“继承”新的类值(例如, UI == 6) - Mark
保持静态变量同步的一种方法是使它们成为属性: class Test(object):, _i = 3, @property, def i(self),return type(self)._i, @i.setter, def i(self,val):, type(self)._i = val。现在你可以做到 x = Test(), x.i = 12, assert x.i == Test.i。 - Rick Teachey
所以我可以说所有变量最初都是静态的,然后访问实例会在运行时生成实例变量? - Ali
也许这很有趣:如果在Test中定义一个更改Test.i的方法,那将影响BOTH Test.i和t.i值。 - Pablo


静态和类方法

正如其他答案所指出的那样,使用内置装饰器可以轻松完成静态和类方法:

class Test(object):

    # regular instance method:
    def MyMethod(self):
        pass

    # class method:
    @classmethod
    def MyClassMethod(klass):
        pass

    # static method:
    @staticmethod
    def MyStaticMethod():
        pass

像往常一样,第一个参数 MyMethod() 绑定到类实例对象。相比之下,第一个参数 MyClassMethod() 是 绑定到类对象本身 (例如,在这种情况下, Test)。对于 MyStaticMethod(),没有任何参数被绑定,并且拥有参数是可选的。

“静态变量”

但是,实现“静态变量”(嗯, 易变的 静态变量,无论如何,如果这不是一个矛盾......)并不是那么直截了当。作为米勒德夫 在他的回答中指出问题是Python的类属性不是真正的“静态变量”。考虑:

class Test(object):
    i = 3  # This is a class attribute

x = Test()
x.i = 12   # Attempt to change the value of the class attribute using x instance
assert x.i == Test.i  # ERROR
assert Test.i == 3    # Test.i was not affected
assert x.i == 12      # x.i is a different object than Test.i

这是因为这条线 x.i = 12 添加了一个新的实例属性 i 至 x 而不是改变的价值 Test 类 i 属性。

局部 预期的静态变量行为,即在多个实例之间同步属性(但是  与班级本身;看下面的“gotcha”),可以通过将class属性转换为属性来实现:

class Test(object):

    _i = 3

    @property
    def i(self):
        return type(self)._i

    @i.setter
    def i(self,val):
        type(self)._i = val

## ALTERNATIVE IMPLEMENTATION - FUNCTIONALLY EQUIVALENT TO ABOVE ##
## (except with separate methods for getting and setting i) ##

class Test(object):

    _i = 3

    def get_i(self):
        return type(self)._i

    def set_i(self,val):
        type(self)._i = val

    i = property(get_i, set_i)

现在你可以这样做:

x1 = Test()
x2 = Test()
x1.i = 50
assert x2.i == x1.i  # no error
assert x2.i == 50    # the property is synced

静态变量现在将保持同步 在所有类实例之间

(注意:也就是说,除非类实例决定定义自己的版本 _i!但如果有人决定这样做,他们应该得到他们得到的,不是吗?)

请注意,从技术上讲, i 仍然不是一个“静态变量”;它是一个 property,这是一种特殊类型的描述符。但是,那 property 行为现在等同于在所有类实例中同步的(可变)静态变量。

不可变的“静态变量”

对于不可变的静态变量行为,只需省略 property 二传手:

class Test(object):

    _i = 3

    @property
    def i(self):
        return type(self)._i

## ALTERNATIVE IMPLEMENTATION - FUNCTIONALLY EQUIVALENT TO ABOVE ##
## (except with separate methods for getting i) ##

class Test(object):

    _i = 3

    def get_i(self):
        return type(self)._i

    i = property(get_i)

现在尝试设置实例 i 属性将返回一个 AttributeError

x = Test()
assert x.i == 3  # success
x.i = 12         # ERROR

一个要意识到的

请注意,上述方法仅适用于 实例 你的班级 - 他们会  工作 在使用类本身时。例如:

x = Test()
assert x.i == Test.i  # ERROR

# x.i and Test.i are two different objects:
type(Test.i)  # class 'property'
type(x.i)     # class 'int'

这条线 assert Test.i == x.i 产生错误,因为 i 的属性 Test 和 x 是两个不同的对象。

很多人会发现这令人惊讶。但是,它不应该。如果我们回去检查我们的 Test 类定义(第二个版本),我们注意到这一行:

    i = property(get_i) 

显然,该成员 i 的 Test 必须是一个 property object,是从中返回的对象的类型 property 功能。

如果您发现上述内容令人困惑,您很可能仍会从其他语言(例如Java或c ++)的角度考虑它。你应该去学习 property object,关于返回Python属性的顺序,描述符协议和方法解析顺序(MRO)。

我提出了以下'gotcha'的解决方案;但是我会建议 - 极力 - 你不要尝试做类似下面的事情,直到 - 至少 - 你彻底明白为什么 assert Test.i = x.i 导致错误。

真实的,实际的 静态变量 - Test.i == x.i

我在下面提供(Python 3)解决方案仅供参考。我并不赞同它是一个“好的解决方案”。我怀疑是否真的需要在Python中模拟其他语言的静态变量行为。但是,无论它是否真的有用,下面的内容都应该有助于进一步了解Python的工作原理。

更新:这次尝试 真是太可怕了;如果你坚持做这样的事情(提示:请不要; Python是一种非常优雅的语言,而且它只是没有必要表现得像另一种语言),使用代码 Ethan Furman的回答 代替。

使用元类模拟其他语言的静态变量行为

元类是类的类。 Python中所有类的默认元类(即我认为的Python 2.3之后的“新风格”类) type。例如:

type(int)  # class 'type'
type(str)  # class 'type'
class Test(): pass
type(Test) # class 'type'

但是,您可以像这样定义自己的元类:

class MyMeta(type): pass

并将其应用到您自己的类中(仅限Python 3):

class MyClass(metaclass = MyMeta):
    pass

type(MyClass)  # class MyMeta

下面是我创建的元类,它试图模仿其他语言的“静态变量”行为。它基本上可以通过用版本替换默认的getter,setter和deleter来工作,这些版本检查所请求的属性是否是“静态变量”。

“静态变量”的目录存储在 StaticVarMeta.statics 属性。最初尝试使用替代分辨率顺序来解析所有属性请求。我把它称为“静态分辨率顺序”或“SRO”。这是通过在给定类(或其父类)的“静态变量”集中查找所请求的属性来完成的。如果该属性未出现在“SRO”中,则该类将回退到默认属性get / set / delete行为(即“MRO”)。

from functools import wraps

class StaticVarsMeta(type):
    '''A metaclass for creating classes that emulate the "static variable" behavior
    of other languages. I do not advise actually using this for anything!!!

    Behavior is intended to be similar to classes that use __slots__. However, "normal"
    attributes and __statics___ can coexist (unlike with __slots__). 

    Example usage: 

        class MyBaseClass(metaclass = StaticVarsMeta):
            __statics__ = {'a','b','c'}
            i = 0  # regular attribute
            a = 1  # static var defined (optional)

        class MyParentClass(MyBaseClass):
            __statics__ = {'d','e','f'}
            j = 2              # regular attribute
            d, e, f = 3, 4, 5  # Static vars
            a, b, c = 6, 7, 8  # Static vars (inherited from MyBaseClass, defined/re-defined here)

        class MyChildClass(MyParentClass):
            __statics__ = {'a','b','c'}
            j = 2  # regular attribute (redefines j from MyParentClass)
            d, e, f = 9, 10, 11   # Static vars (inherited from MyParentClass, redefined here)
            a, b, c = 12, 13, 14  # Static vars (overriding previous definition in MyParentClass here)'''
    statics = {}
    def __new__(mcls, name, bases, namespace):
        # Get the class object
        cls = super().__new__(mcls, name, bases, namespace)
        # Establish the "statics resolution order"
        cls.__sro__ = tuple(c for c in cls.__mro__ if isinstance(c,mcls))

        # Replace class getter, setter, and deleter for instance attributes
        cls.__getattribute__ = StaticVarsMeta.__inst_getattribute__(cls, cls.__getattribute__)
        cls.__setattr__ = StaticVarsMeta.__inst_setattr__(cls, cls.__setattr__)
        cls.__delattr__ = StaticVarsMeta.__inst_delattr__(cls, cls.__delattr__)
        # Store the list of static variables for the class object
        # This list is permanent and cannot be changed, similar to __slots__
        try:
            mcls.statics[cls] = getattr(cls,'__statics__')
        except AttributeError:
            mcls.statics[cls] = namespace['__statics__'] = set() # No static vars provided
        # Check and make sure the statics var names are strings
        if any(not isinstance(static,str) for static in mcls.statics[cls]):
            typ = dict(zip((not isinstance(static,str) for static in mcls.statics[cls]), map(type,mcls.statics[cls])))[True].__name__
            raise TypeError('__statics__ items must be strings, not {0}'.format(typ))
        # Move any previously existing, not overridden statics to the static var parent class(es)
        if len(cls.__sro__) > 1:
            for attr,value in namespace.items():
                if attr not in StaticVarsMeta.statics[cls] and attr != ['__statics__']:
                    for c in cls.__sro__[1:]:
                        if attr in StaticVarsMeta.statics[c]:
                            setattr(c,attr,value)
                            delattr(cls,attr)
        return cls
    def __inst_getattribute__(self, orig_getattribute):
        '''Replaces the class __getattribute__'''
        @wraps(orig_getattribute)
        def wrapper(self, attr):
            if StaticVarsMeta.is_static(type(self),attr):
                return StaticVarsMeta.__getstatic__(type(self),attr)
            else:
                return orig_getattribute(self, attr)
        return wrapper
    def __inst_setattr__(self, orig_setattribute):
        '''Replaces the class __setattr__'''
        @wraps(orig_setattribute)
        def wrapper(self, attr, value):
            if StaticVarsMeta.is_static(type(self),attr):
                StaticVarsMeta.__setstatic__(type(self),attr, value)
            else:
                orig_setattribute(self, attr, value)
        return wrapper
    def __inst_delattr__(self, orig_delattribute):
        '''Replaces the class __delattr__'''
        @wraps(orig_delattribute)
        def wrapper(self, attr):
            if StaticVarsMeta.is_static(type(self),attr):
                StaticVarsMeta.__delstatic__(type(self),attr)
            else:
                orig_delattribute(self, attr)
        return wrapper
    def __getstatic__(cls,attr):
        '''Static variable getter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                try:
                    return getattr(c,attr)
                except AttributeError:
                    pass
        raise AttributeError(cls.__name__ + " object has no attribute '{0}'".format(attr))
    def __setstatic__(cls,attr,value):
        '''Static variable setter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                setattr(c,attr,value)
                break
    def __delstatic__(cls,attr):
        '''Static variable deleter'''
        for c in cls.__sro__:
            if attr in StaticVarsMeta.statics[c]:
                try:
                    delattr(c,attr)
                    break
                except AttributeError:
                    pass
        raise AttributeError(cls.__name__ + " object has no attribute '{0}'".format(attr))
    def __delattr__(cls,attr):
        '''Prevent __sro__ attribute from deletion'''
        if attr == '__sro__':
            raise AttributeError('readonly attribute')
        super().__delattr__(attr)
    def is_static(cls,attr):
        '''Returns True if an attribute is a static variable of any class in the __sro__'''
        if any(attr in StaticVarsMeta.statics[c] for c in cls.__sro__):
            return True
        return False

141
2017-12-19 15:16



我试着用你的方式,但我遇到了一个问题,请在这里查看我的问题 stackoverflow.com/questions/29329850/get-static-variable-value - Muhammed Refaat
@RickTeachey:我猜你通常应该查看你在Instance类上做的任何事情 Test (在用于实例化实例之前)在元编程领域?例如,您可以通过执行来改变类行为 Test.i = 0 (这里你完全破坏了属性对象)。我猜“属性机制”仅在类的实例上对属性访问进行启动(除非您使用元类作为中间类型来改变基础行为)。顺便说一下,请完成这个答案:-) - Ole Thomsen Buus
@RickTeachey谢谢:-)你的元类到底有趣但实际上有点太复杂了我的喜好。它可能在绝对需要此机制的大型框架/应用程序中很有用。无论如何,这表明如果真的需要新的(复杂的)非默认元行为,Python就有可能:) - Ole Thomsen Buus
@OleThomsenBuus:检查 我的答案 对于完成这项工作的更简单的元类。 - Ethan Furman
@taper你是对的;我已经编辑了解决问题的答案(不能相信它已经存在错误这么久了!)。对困惑感到抱歉。 - Rick Teachey


您还可以动态地将类变量添加到类中

>>> class X:
...     pass
... 
>>> X.bar = 0
>>> x = X()
>>> x.bar
0
>>> x.foo
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
AttributeError: X instance has no attribute 'foo'
>>> X.foo = 1
>>> x.foo
1

类实例可以更改类变量

class X:
  l = []
  def __init__(self):
    self.l.append(1)

print X().l
print X().l

>python test.py
[1]
[1, 1]

23
2017-09-17 08:06



即使将类导入另一个模块,新的类变量是否仍然存在? - zakdances
是。无论您调用它们的命名空间,类都是有效的单例。 - Pedro


我个人在需要静态方法时会使用classmethod。主要是因为我把班级作为一个论点。

class myObj(object):
   def myMethod(cls)
     ...
   myMethod = classmethod(myMethod) 

或使用装饰

class myObj(object):
   @classmethod
   def myMethod(cls)

对于静态属性..它查找一些python定义的时间..变量总是可以改变。它们有两种类型,它们是可变的和不可变的。此外,还有类属性和实例属性。在java和c ++意义上,没有什么比静态属性更像

为什么在pythonic意义上使用静态方法,如果它对类没有任何关系!如果我是你,我要么使用classmethod,要么定义独立于类的方法。


12
2017-09-16 02:02



变量不可变或不可变;对象是。 (但是,一个对象可以在不同程度上成功地尝试阻止对其某些属性的赋值。) - Davis Herring


调用python中的静态方法 类方法秒。看看下面的代码

class MyClass:

    def myInstanceMethod(self):
        print 'output from an instance method'

    @classmethod
    def myStaticMethod(cls):
        print 'output from a static method'

>>> MyClass.myInstanceMethod()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method myInstanceMethod() must be called [...]

>>> MyClass.myStaticMethod()
output from a static method

请注意,当我们调用该方法时 myInstanceMethod,我们得到一个错误。这是因为它要求在此类的实例上调用该方法。方法 myStaticMethod 使用the设置为classmethod 装饰  @classmethod

只是为了踢和咯咯笑,我们可以打电话 myInstanceMethod 通过传入类的实例在类上,如下所示:

>>> MyClass.myInstanceMethod(MyClass())
output from an instance method

11
2017-09-16 02:05





有关静态属性和实例属性的一个特别注意事项,如下例所示:

class my_cls:
  my_prop = 0

#static property
print my_cls.my_prop  #--> 0

#assign value to static property
my_cls.my_prop = 1 
print my_cls.my_prop  #--> 1

#access static property thru' instance
my_inst = my_cls()
print my_inst.my_prop #--> 1

#instance property is different from static property 
#after being assigned a value
my_inst.my_prop = 2
print my_cls.my_prop  #--> 1
print my_inst.my_prop #--> 2

这意味着在将值赋给instance属性之前,如果我们尝试通过'instance访问属性,则使用静态值。 在python类中声明的每个属性在内存中始终都有一个静态插槽


10
2018-03-08 06:06