静态类变量可能吗?
是否有可能在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#没有太大区别,静态成员无法通过对实例的引用进行访问。
看看Python教程对于类和类对象的主题要说些什么。
@Steve Johnson已经回答了有关静态方法的问题,这些方法也记录在Python库参考的“内置函数”中。
class C:
@staticmethod
def f(arg1, arg2, ...): ...
@beidy推荐使用静态方法的类方法,因为该方法接收类类型作为第一个参数,但我仍然对这种方法相对于静态方法的优点有些模糊。 如果你也是,那么它可能并不重要。
@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
与“static”类变量不同步。 这是因为i
在t
名称空间内重新绑定,这与Test
名称空间不同。 如果要更改“静态”变量的值,则必须在最初定义的范围(或对象)内对其进行更改。 我把“static”放在引号中,因为在C ++和Java的意义上,Python并没有真正的静态变量。
尽管它没有提及任何有关静态变量或方法的具体信息,但Python教程还提供了有关类和类对象的一些相关信息。
@Steve Johnson也回答了有关静态方法的问题,这些方法也记录在Python库参考中的“内置函数”下。
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
静态和类方法
正如其他答案所指出的那样,使用内置装饰器可以轻松完成静态和类方法:
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
已将新的实例属性i
添加到x
而不是更改Test
class 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 == xi
会产生一个错误,因为Test
和x
的i
属性是两个不同的对象。
许多人会发现这令人惊讶。 但是,它不应该。 如果我们回顾并检查我们的Test
类定义(第二个版本),我们注意到这一行:
i = property(get_i)
显然, Test
的成员i
必须是一个property
对象,它是property
函数返回的对象的类型。
如果您发现上述混淆,您很可能仍然从其他语言(例如Java或c ++)的角度思考它。 您应该研究property
对象,关于返回Python属性的顺序,描述符协议和方法解析顺序(MRO)。
我为下面的'gotcha'提出了一个解决方案; 但是我会建议 - 非常努力 - 你不会尝试做类似于以下的事情,直到 - 至少 - 你彻底明白为什么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”中,则该类将回退到缺省属性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
链接地址: http://www.djcxy.com/p/2221.html
上一篇: Are static class variables possible?
下一篇: What is the difference between 'typedef' and 'using' in C++11?