Вопрос-Ответ

Class (static) variables and methods

Переменные и методы класса (статические)

Как мне создать переменные или методы класса (т.е. статические) в Python?

Переведено автоматически
Ответ 1

Переменные, объявленные внутри определения класса, но не внутри метода, являются переменными класса или статическими переменными:

>>> 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 рекомендует использовать classmethod вместо staticmethod, поскольку тогда метод получает тип класса в качестве первого аргумента.

Ответ 2

@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 рассинхронизировалась со "статической" переменной класса, когда атрибут i был установлен непосредственно для t. Это связано с тем, что i было повторно привязано к t пространству имен, которое отличается от Test пространства имен. Если вы хотите изменить значение "статической" переменной, вы должны изменить ее в пределах области видимости (или объекта), где она была первоначально определена. Я заключил "static" в кавычки, потому что в Python на самом деле нет статических переменных в том смысле, в каком это делают C ++ и Java.

Хотя в руководстве по Python не говорится ничего конкретного о статических переменных или методах, в нем есть некоторая соответствующая информация о классах и объектах класса.

@Стив Джонсон также ответил относительно статических методов, также описанных в разделе "Встроенные функции" в справочнике по библиотеке 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 be the same as Test.i = arg1

Наглядное представление приведенного выше примера

Ответ 3

Static and Class Methods

As the other answers have noted, static and class methods are easily accomplished using the built-in decorators:

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

As usual, the first argument to my_method() is bound to the class instance object. In contrast, the first argument to my_class_method() is bound to the class object itself (e.g., in this case, Test). For my_static_method(), none of the arguments are bound, and having arguments at all is optional.

"Static Variables"

However, implementing "static variables" (well, mutable static variables, anyway, if that's not a contradiction in terms...) is not as straight forward. As millerdev pointed out in his answer, the problem is that Python's class attributes are not truly "static variables". Consider:

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

This is because the line x.i = 12 has added a new instance attribute i to x instead of changing the value of the Test class i attribute.

Partial expected static variable behavior, i.e., syncing of the attribute between multiple instances (but not with the class itself; see "gotcha" below), can be achieved by turning the class attribute into a property:

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)

Now you can do:

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

The static variable will now remain in sync between all class instances.

(NOTE: That is, unless a class instance decides to define its own version of _i! But if someone decides to do THAT, they deserve what they get, don't they???)

Note that technically speaking, i is still not a 'static variable' at all; it is a property, which is a special type of descriptor. However, the property behavior is now equivalent to a (mutable) static variable synced across all class instances.

Immutable "Static Variables"

For immutable static variable behavior, simply omit the property 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)

Now attempting to set the instance i attribute will return an AttributeError:

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

One Gotcha to be Aware of

Note that the above methods only work with instances of your class - they will not work when using the class itself. So for example:

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'

The line assert Test.i == x.i produces an error, because the i attribute of Test and x are two different objects.

Many people will find this surprising. However, it should not be. If we go back and inspect our Test class definition (the second version), we take note of this line:

    i = property(get_i) 

Clearly, the member i of Test must be a property object, which is the type of object returned from the property function.

If you find the above confusing, you are most likely still thinking about it from the perspective of other languages (e.g. Java or c++). You should go study the property object, about the order in which Python attributes are returned, the descriptor protocol, and the method resolution order (MRO).

Ниже я представляю решение вышеупомянутой проблемы; однако я бы настоятельно рекомендовал вам не пытаться делать что-то вроде следующего, пока - как минимум - вы полностью не поймете, почему assert Test.i = x.i возникает ошибка.

РЕАЛЬНЫЕ статические переменные - 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", класс вернется к поведению получения / установки / удаления атрибута по умолчанию (т.Е. "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
Ответ 4

Вы также можете добавлять переменные класса в классы " на лету"

>>> 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]
2023-07-03 14:23 python class