Статические и классовые методы
Как уже отмечалось в других ответах, статические и классовые методы легко выполняются с помощью встроенных декораторов:
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
class i
.
Частичное ожидаемое поведение статической переменной, т. Е. Синхронизация атрибута между несколькими экземплярами (но , а не с самим классом; см. «Гоча» ниже), может быть достигнута поворотом атрибут класса в свойство:
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
, который является типом объекта, возвращаемого функцией property
.
Если вы находите вышеприведенное замешательство, скорее всего, вы все еще думаете об этом с точки зрения других языков (например, Java или c ++). Вам следует изучить объект property
, порядок, в котором возвращаются атрибуты Python, протокол дескриптора и порядок разрешения методов (MRO).
Я представляю решение вышеупомянутой «ошибки» ниже; Тем не менее, я бы настоятельно рекомендовал вам - не пытаться делать что-то вроде следующего, пока - как минимум - вы не поймете, почему assert Test.i = x.i
вызывает ошибку.
REAL, ACTUAL Статические переменные - Test.i == x.i
Я представляю решение (Python 3) ниже только в ознакомительных целях. Я не одобряю это как «хорошее решение». У меня есть сомнения относительно того, действительно ли когда-либо необходима эмуляция поведения статических переменных в других языках Python. Тем не менее, независимо от того, насколько он полезен, нижеприведенное должно помочь понять, как работает Python.
ОБНОВЛЕНИЕ: эта попытка действительно ужасна ; если вы настаиваете на том, чтобы делать что-то вроде этого (подсказка: пожалуйста, не делайте; Python - очень элегантный язык, и вы должны заставить его вести себя, как будто другой язык просто не нужен), используйте код в ответ Этана Фурмана вместо.
Эмуляция поведения статических переменных других языков с использованием метакласса
Метакласс - это класс класса. Метакласс по умолчанию для всех классов в 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
Ниже приведен метакласс, созданный мной, который пытается эмулировать поведение "статических переменных" других языков. Он в основном работает, заменяя стандартные методы получения, установки и удаления версиями, которые проверяют, является ли запрашиваемый атрибут «статической переменной».
Каталог «статических переменных» хранится в атрибуте 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