如何在Python中创建类(即静态)变量或方法?


当前回答

关于Python的属性查找,一个非常有趣的点是它可以用来创建“虚拟变量”:

class A(object):

  label="Amazing"

  def __init__(self,d): 
      self.data=d

  def say(self): 
      print("%s %s!"%(self.label,self.data))

class B(A):
  label="Bold"  # overrides A.label

A(5).say()      # Amazing 5!
B(3).say()      # Bold 3!

通常情况下,在创建它们之后,不会有任何分配给它们。请注意,查找使用self,因为尽管标签在不与特定实例关联的意义上是静态的,但值仍然取决于(的类)实例。

其他回答

python中的静态方法称为classmethods。查看以下代码

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使用decorator@classmethod设置为类方法。

为了好玩,我们可以通过传入类的实例来调用类上的myInstanceMethod,如下所示:

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

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

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。

静态和类方法

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

class Test(object):

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

    # class method:
    @classmethod
    def my_class_method(cls):
        pass

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

通常,my_method()的第一个参数绑定到类实例对象。相反,my_class_method()的第一个参数绑定到类对象本身(例如,在本例中为Test)。对于my_static_method(),没有任何参数是绑定的,并且有任何参数都是可选的。

“静态变量”

然而,实现“静态变量”(好吧,可变静态变量,无论如何,如果这在术语上不是矛盾的话…)并不是那么直接。正如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

这是因为行x.i=12向x添加了一个新的实例属性i,而不是更改测试类i属性的值。

部分预期的静态变量行为,即在多个实例之间同步属性(但不与类本身同步;请参见下面的“gotcha”),可以通过将类属性转换为属性来实现:

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仍然不是一个“静态变量”;它是一种属性,是一种特殊类型的描述符。然而,属性行为现在相当于跨所有类实例同步的(可变)静态变量。

不可变的“静态变量”

对于不可变的静态变量行为,只需省略属性setter:

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行产生错误,因为Test和x的i属性是两个不同的对象。

许多人会觉得这令人惊讶。然而,它不应该是。如果我们回去检查我们的测试类定义(第二个版本),我们会注意到这一行:

    i = property(get_i) 

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

如果您发现上述问题令人困惑,那么您很可能仍然从其他语言(例如Java或c++)的角度来考虑它。您应该研究属性对象、Python属性的返回顺序、描述符协议和方法解析顺序(MRO)。

我提出了一个解决上述问题的方法;然而,我强烈建议,至少在你彻底理解为什么断言Test.I=x.I会导致错误之前,不要尝试执行以下操作。

实际静态变量-测试.i==x.i

我在下面介绍(Python3)解决方案,仅供参考。我并不赞同这是一个“好的解决方案”。我怀疑是否真的有必要在Python中模拟其他语言的静态变量行为。然而,不管它是否实际有用,下面的内容应该有助于进一步了解Python的工作原理。

更新:这种尝试真的很糟糕;如果你坚持这样做(提示:请不要这样做;Python是一种非常优雅的语言,不需要强迫它表现得像另一种语言),请使用Ethan Furman答案中的代码。

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

元类是类的类。Python中所有类的默认元类(即,我认为Python 2.3之后的“新样式”类)是类型。例如:

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

您可以使用列表或字典来获取实例之间的“静态行为”。

class Fud:

     class_vars = {'origin_open':False}

     def __init__(self, origin = True):
         self.origin = origin
         self.opened = True
         if origin:
             self.class_vars['origin_open'] = True


     def make_another_fud(self):
         ''' Generating another Fud() from the origin instance '''

         return Fud(False)


     def close(self):
         self.opened = False
         if self.origin:
             self.class_vars['origin_open'] = False


fud1 = Fud()
fud2 = fud1.make_another_fud()

print (f"is this the original fud: {fud2.origin}")
print (f"is the original fud open: {fud2.class_vars['origin_open']}")
# is this the original fud: False
# is the original fud open: True

fud1.close()

print (f"is the original fud open: {fud2.class_vars['origin_open']}")
# is the original fud open: False

我发现最好的方法是使用另一个类。您可以创建一个对象,然后将其用于其他对象。

class staticFlag:
    def __init__(self):
        self.__success = False
    def isSuccess(self):
        return self.__success
    def succeed(self):
        self.__success = True

class tryIt:
    def __init__(self, staticFlag):
        self.isSuccess = staticFlag.isSuccess
        self.succeed = staticFlag.succeed

tryArr = []
flag = staticFlag()
for i in range(10):
    tryArr.append(tryIt(flag))
    if i == 5:
        tryArr[i].succeed()
    print tryArr[i].isSuccess()

通过上面的示例,我创建了一个名为staticFlag的类。

此类应显示静态变量__success(私有静态变量)。

tryIt类表示我们需要使用的常规类。

现在我为一个标志(staticFlag)创建了一个对象。此标志将作为对所有常规对象的引用发送。

所有这些对象都被添加到列表tryArr中。


此脚本结果:

False
False
False
False
False
True
True
True
True
True