Python 中可以使用静态类变量吗?

Python 中是否可以有静态类变量或方法?为此需要什么语法?

答案

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

>>> 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 Library Reference 中的 “内置函数” 下

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

@beidy 建议使用classmethod 而不是 staticmethod,因为该方法随后将类类型作为第一个参数,但是对于这种方法相对于 staticmethod 的优点,我仍然有些模糊。如果您也是,那可能没关系。

@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__
{}

请注意,当将属性i直接设置在t上时,实例变量ti如何与 “静态” 类变量不同步。这是因为i被重新绑定在t名称空间中,该名称空间与Test名称空间不同。如果要更改 “静态” 变量的值,则必须在其最初定义的范围(或对象)内进行更改。我将 “static” 用引号引起来,因为 Python 实际上没有 C ++ 和 Java 所具有的静态变量。

尽管它没有对静态变量或方法进行任何具体说明,但是Python 教程提供了有关类和类对象的一些相关信息。

@Steve Johnson 还回答了有关静态方法的问题,该方法也记录在 Python 库参考的 “内置函数” 下。

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

@beid 还提到了 classmethod,它与 staticmethod 相似。类方法的第一个参数是类对象。例:

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 be the same as Test.i = arg1

以上示例的图形表示

静态和类方法

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

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() ,没有参数绑定,并且完全没有参数是可选的。

“静态变量”

但是,实现 “静态变量”(无论如何, 可变的静态变量,如果这不是一个矛盾的话……)并不是那么简单。正如 millerdev 在回答中指出的那样 ,问题在于 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

这是因为线xi = 12增加了一个新的实例属性ix ,而不是改变的值Testi属性。

可以通过将 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 == xi会产生错误,因为Testi属性和x是两个不同的对象。

许多人会发现这令人惊讶。但是,事实并非如此。如果我们返回并检查我们的Test类定义(第二个版本),请注意以下这一行:

i = property(get_i)

显然, Test的成员i必须是property对象,这是从property函数返回的对象的类型。

如果您发现上述混淆,您很可能仍会从其他语言(例如 Java 或 c ++)的角度考虑它。您应该研究property对象,有关返回 Python 属性的顺序,描述符协议和方法解析顺序(MRO)。

我在下面提出了上述 “陷阱” 的解决方案;但是,我建议 - 极力地 - 您不要尝试执行以下操作,直到 - 至少 - 您要完全理解为什么assert Test.i = xi会导致错误。

REAL,ACTUAL静态变量Test.i == xi

我仅在下面提供(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” 中,则该类将回退到默认属性的 “获取 / 设置 / 删除” 行为(即 “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

您还可以随时将类变量添加到类中

>>> 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]

就个人而言,每当我需要静态方法时,我都会使用类方法。主要是因为我将类作为参数。

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

或使用装饰器

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

对于静态属性.. 它时候您查找一些 python 定义.. 变量可以随时更改。有两种类型,它们是可变的和不可变的。此外,还有类属性和实例属性。从 Java 和 C ++ 的意义上说,没有什么比静态属性更像

如果与类没有任何关系,为什么要使用 pythonic 意义上的静态方法!如果您是我,则可以使用 classmethod 或独立于类定义方法。

python 中的静态方法称为classmethod 。看下面的代码

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 时 ,我们得到一个错误。这是因为它要求在此类的实例上调用该方法。使用装饰器 @classmethod将方法myStaticMethod设置为类方法。

只是为了一笑而过,我们可以通过传入类的实例来调用类的myInstanceMethod ,如下所示:

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

关于静态属性和实例属性的一件事要特别注意,如下面的示例所示:

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

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

当在任何成员方法之外定义某个成员变量时,该变量可以是静态的也可以是非静态的,具体取决于变量的表示方式。

  • CLASSNAME.var 是静态变量
  • INSTANCENAME.var 不是静态变量。
  • 类中的 self.var 不是静态变量。
  • 类成员函数内部的 var 未定义。

例如:

#!/usr/bin/python

class A:
    var=1

    def printvar(self):
        print "self.var is %d" % self.var
        print "A.var is %d" % A.var


    a = A()
    a.var = 2
    a.printvar()

    A.var = 3
    a.printvar()

结果是

self.var is 2
A.var is 1
self.var is 2
A.var is 3

您还可以使用元类将类强制为静态。

class StaticClassError(Exception):
    pass


class StaticClass:
    __metaclass__ = abc.ABCMeta

    def __new__(cls, *args, **kw):
        raise StaticClassError("%s is a static class and cannot be initiated."
                                % cls)

class MyClass(StaticClass):
    a = 1
    b = 3

    @staticmethod
    def add(x, y):
        return x+y

然后,每当您偶然尝试初始化MyClass 时 ,都会收到一个 StaticClassError。

可以有static类变量,但可能不值得。

这是用 Python 3 编写的概念证明 - 如果任何确切的细节有误,则可以对代码进行调整以使其与static variable含义完全匹配:


class Static:
    def __init__(self, value, doc=None):
        self.deleted = False
        self.value = value
        self.__doc__ = doc
    def __get__(self, inst, cls=None):
        if self.deleted:
            raise AttributeError('Attribute not set')
        return self.value
    def __set__(self, inst, value):
        self.deleted = False
        self.value = value
    def __delete__(self, inst):
        self.deleted = True

class StaticType(type):
    def __delattr__(cls, name):
        obj = cls.__dict__.get(name)
        if isinstance(obj, Static):
            obj.__delete__(name)
        else:
            super(StaticType, cls).__delattr__(name)
    def __getattribute__(cls, *args):
        obj = super(StaticType, cls).__getattribute__(*args)
        if isinstance(obj, Static):
            obj = obj.__get__(cls, cls.__class__)
        return obj
    def __setattr__(cls, name, val):
        # check if object already exists
        obj = cls.__dict__.get(name)
        if isinstance(obj, Static):
            obj.__set__(name, val)
        else:
            super(StaticType, cls).__setattr__(name, val)

并在使用中:

class MyStatic(metaclass=StaticType):
    """
    Testing static vars
    """
    a = Static(9)
    b = Static(12)
    c = 3

class YourStatic(MyStatic):
    d = Static('woo hoo')
    e = Static('doo wop')

和一些测试:

ms1 = MyStatic()
ms2 = MyStatic()
ms3 = MyStatic()
assert ms1.a == ms2.a == ms3.a == MyStatic.a
assert ms1.b == ms2.b == ms3.b == MyStatic.b
assert ms1.c == ms2.c == ms3.c == MyStatic.c
ms1.a = 77
assert ms1.a == ms2.a == ms3.a == MyStatic.a
ms2.b = 99
assert ms1.b == ms2.b == ms3.b == MyStatic.b
MyStatic.a = 101
assert ms1.a == ms2.a == ms3.a == MyStatic.a
MyStatic.b = 139
assert ms1.b == ms2.b == ms3.b == MyStatic.b
del MyStatic.b
for inst in (ms1, ms2, ms3):
    try:
        getattr(inst, 'b')
    except AttributeError:
        pass
    else:
        print('AttributeError not raised on %r' % attr)
ms1.c = 13
ms2.c = 17
ms3.c = 19
assert ms1.c == 13
assert ms2.c == 17
assert ms3.c == 19
MyStatic.c = 43
assert ms1.c == 13
assert ms2.c == 17
assert ms3.c == 19

ys1 = YourStatic()
ys2 = YourStatic()
ys3 = YourStatic()
MyStatic.b = 'burgler'
assert ys1.a == ys2.a == ys3.a == YourStatic.a == MyStatic.a
assert ys1.b == ys2.b == ys3.b == YourStatic.b == MyStatic.b
assert ys1.d == ys2.d == ys3.d == YourStatic.d
assert ys1.e == ys2.e == ys3.e == YourStatic.e
ys1.a = 'blah'
assert ys1.a == ys2.a == ys3.a == YourStatic.a == MyStatic.a
ys2.b = 'kelp'
assert ys1.b == ys2.b == ys3.b == YourStatic.b == MyStatic.b
ys1.d = 'fee'
assert ys1.d == ys2.d == ys3.d == YourStatic.d
ys2.e = 'fie'
assert ys1.e == ys2.e == ys3.e == YourStatic.e
MyStatic.a = 'aargh'
assert ys1.a == ys2.a == ys3.a == YourStatic.a == MyStatic.a