Возможны ли статические переменные класса в Python?

Возможно ли иметь статические переменные или методы класса в Python? Какой синтаксис требуется для этого?

Да. Отсутствие ключевого слова static может вводить в заблуждение, но любой объект, инициализированный внутри класса (только один отступ внутри класса, а не в конструкторе), является статическим. Он не зависит от создания экземпляра (потому что он не является частью конструктора). Что касается методов, вы можете сделать это с помощью декоратора @staticmethod.

shady shamus 30.10.2020 12:16

использование термина статический для чего-то, что существует для всех экземпляров класса, всегда кажется мне странным

Tony Suffolk 66 31.10.2020 23:10

@ TonySuffolk66 Виновен (я думаю) C++, который просто присвоил существующее ключевое слово «static» из C (где он указывал, что время жизни переменной сохраняется за пределами области, в которой она была объявлена). C++ расширил это понятие, чтобы обозначить переменную, значение которой выходит за пределы «области действия» одного экземпляра класса. Python (более логично) просто называет их атрибутами класса, поскольку они являются атрибутами, связанными с самим классом, а не экземпляром класса.

chepner 23.12.2020 16:25
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2 092
3
1 301 004
23
Перейти к ответу Данный вопрос помечен как решенный

Ответы 23

Ответ принят как подходящий

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

>>> 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 рекомендует classmethods вместо staticmethod, поскольку метод затем получает тип класса в качестве первого аргумента, но я все еще немного не уверен в преимуществах этого подхода перед staticmethod. Если вы тоже, то это, вероятно, не имеет значения.

Я только изучаю Python, но преимущества @classmethod перед @staticmethod AFAIK в том, что вы всегда получаете имя класса, для которого был вызван метод, даже если это подкласс. Статическому методу не хватает этой информации, поэтому он не может, например, вызвать переопределенный метод.

Seb 02.10.2012 19:58

@Blair Я хочу этим заняться. Например, если вы хотите иметь возможность многократно обращаться к PI = 3.14 в классе (игнорируя математические библиотеки ради аргумента), действительно ли вы используете MyClass.PI во всех этих местах? Что такое питонический подход? Разве это не выглядит загроможденным и некрасивым? Или я не прав?

theJollySin 16.03.2013 08:07

@theJollyS в питоническом смысле для констант - не выращивать класс для констант. Просто возьмите const.py с PI = 3.14, и вы сможете импортировать его куда угодно. from const import PI

Giszmo 21.06.2013 21:54

Этот ответ может запутать проблему статической переменной. Начнем с того, что i = 3 - это статическая переменная нет, это атрибут класса, и, поскольку он отличается от атрибута i на уровне экземпляра, нет ведет себя как статическая переменная в других языках. См. ответ Миллердева, Ответ Янна и мой ответ ниже.

Rick supports Monica 19.12.2014 18:40

Кажется, такая переменная класса недоступна во время __del__? По моему ограниченному опыту, переменные класса исчезают до запуска __del__. Ожидается ли это?

yoyo 01.07.2015 08:24

Превосходство @classmethod над @staticmethod заключается в том, что IMO легко реорганизовать методы класса и разбить их на более мелкие, тогда как с @staticmethod вы в основном застряли на том, что у вас есть. Рефакторинг статического метода C.m() потребует добавления ссылки на класс C к каждому вызову, не говоря уже о выборке полей класса. В некоторых случаях это, вероятно, имеет негативные побочные эффекты на полиморфизм.

Błażej Michalik 24.09.2016 00:16

Это привело бы к совершенно другому поведению, если бы вы использовали изменяемую переменную, такую ​​как список: i = [], тогда m.i.append (1) также изменит содержимое переменной в классе.

gauteh 09.12.2016 14:06

так что только одна копия i (статическая переменная) будет в памяти, даже если я создам сотни экземпляров этого класса?

sdream 21.07.2017 15:53

Для всех, кто интересуется, кого Дэниел упомянул в комментарии @Dubslow, это Millerdev (обратная машина)

OfirD 27.02.2018 00:22

@sdream да, поскольку единственная ссылка на i - это его присвоение классу. Чтобы получить доступ к этому из экземпляра через instance.__class__.i

cowbert 25.04.2018 22:07

Я смутно помню, как читал некоторые комментарии Гвидо ван Россума относительно введения классов и статических методов. IIRC, статические методы - это то, что больше соответствует методам классов на других языках. Python classmethod был более или менее «ошибкой», хотя он стал естественным способом определения альтернативного конструктора класса для использования вместо __new__.

chepner 23.12.2020 16:32

Это немного пугает, если исходить из других языков программирования и ожидать, что это будет просто частное / защищенное поле.

Alex 03.02.2021 13:49

Лично я бы использовал метод класса всякий раз, когда мне нужен статический метод. В основном потому, что я получаю класс в качестве аргумента.

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

или используйте декоратор

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

Для статических свойств ... Пора вам поискать определение Python ... переменная всегда может измениться. Есть два их типа: изменяемые и неизменяемые. Также есть атрибуты класса и атрибуты экземпляра. Ничего подобного статическим атрибутам в смысле java и C++.

Зачем использовать статический метод в питоническом смысле, если он не имеет никакого отношения к классу! На вашем месте я бы либо использовал метод класса, либо определил метод независимо от класса.

Переменные не являются изменяемыми или неизменяемыми; объекты есть. (Однако объект может с разной степенью успеха попытаться предотвратить присвоение определенных его атрибутов.)

Davis Herring 21.09.2017 06:55

Java и C++ используют статический (неправильное использование слова, imho) точно так же, как вы используете экземпляр по сравнению с атрибутом класса. Атрибут / метод класса статичен в Java и C++, никакой разницы, за исключением того, что в Python первым параметром при вызове метода класса является класс.

Angel O'Sphere 01.11.2019 11:35

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

Просто для шуток и хихиканья мы могли бы вызвать myInstanceMethod в классе, передав экземпляр класса, например:

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

Эмм ... статические методы сделаны с @staticmethod; @classmethod (очевидно) предназначен для методов класса (которые в первую очередь предназначены для использования в качестве альтернативных конструкторов, но в крайнем случае могут служить статическими методами, которые получают ссылку на класс, через который они были вызваны).

ShadowRanger 14.06.2019 06:20

@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. Если вы хотите изменить значение «статической» переменной, вы должны изменить ее в пределах области (или объекта), в которой она была изначально определена. Я заключил «статический» в кавычки, потому что 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

Pictorial Representation Of Above Example

Я предлагаю вам немного расширить пример: если после установки Test.i = 6 вы затем создаете экземпляр нового объекта (например, u = Test ()), новый объект «унаследует» новое значение класса (например, ui == 6)

Mark 20.08.2014 17:48

Один из способов синхронизировать статические переменные - сделать их свойствами: class Test(object):, _i = 3, @property, def i(self), return type(self)._i, @i.setter, def i(self,val):, type(self)._i = val. Теперь вы можете делать x = Test(), x.i = 12, assert x.i == Test.i.

Rick supports Monica 19.12.2014 18:05

Итак, я мог бы сказать, что все переменные изначально статические, а затем доступ к экземплярам делает переменные экземпляра во время выполнения?

Ali 04.11.2015 10:17

Возможно, это интересно: если вы определите метод в Test, который изменяет Test.i, это повлияет на ОБЕИХ значения Test.i и t.i.

Pablo 07.03.2018 16:41

@millerdev, как и вы упомянули, Python не имеет статических переменных, как у C++ или JAVA ... Так что можно будет сказать, что Test.i - это скорее переменная класса, чем статическая переменная?

NightOwl19 21.09.2018 13:12

Чтобы избежать возможной путаницы, я хотел бы противопоставить статические переменные и неизменяемые объекты.

Некоторые примитивные типы объектов, такие как целые числа, числа с плавающей запятой, строки и пары, неизменяемы в Python. Это означает, что объект, на который ссылается данное имя, не может измениться, если он относится к одному из вышеупомянутых типов объектов. Имя можно переназначить другому объекту, но сам объект не может быть изменен.

Сделав переменную статической, мы сделаем еще один шаг вперед, запретив имя переменной указывать на любой объект, кроме того, на который оно указывает в данный момент. (Примечание: это общая концепция программного обеспечения, не относящаяся к Python; информацию о реализации статики в Python см. В других сообщениях).

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

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

Сохранятся ли новые переменные класса, даже если класс будет импортирован в другой модуль?

zakdances 01.03.2013 16:20

Да. Классы фактически являются одиночными, независимо от того, из какого пространства имен вы их вызываете.

Pedro 25.07.2018 17:11

@Gregory, вы сказали: «И экземпляры класса могут изменять переменные класса» На самом деле этот пример называется доступом, а не модификацией. Модификация была произведена самим объектом с помощью его собственной функции append ().

Amr ALHOSSARY 26.11.2019 15:18

Вы также можете заставить класс быть статическим, используя метакласс.

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

Тогда всякий раз, когда вы случайно попытаетесь инициализировать Мой класс, вы получите StaticClassError.

Почему это вообще класс, если вы не собираетесь его создавать? Это похоже на поворот Python, чтобы превратить его в Java ...

Ned Batchelder 24.08.2014 21:44

Идиома борга - лучший способ справиться с этим.

Rick supports Monica 26.01.2015 18:03

@NedBatchelder Это абстрактный класс, предназначенный только для создания подклассов (и создания экземпляров подклассов)

stevepastelan 24.09.2018 19:53

Я надеюсь, что подклассы не используют super () для вызова __new__ своих родителей ...

Ned Batchelder 24.09.2018 23:29

Особо следует отметить статические свойства и свойства экземпляра, показанные в примере ниже:

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, всегда имеет статический слот в памяти..

Лучший способ, который я нашел, - использовать другой класс. Вы можете создать объект, а затем использовать его на других объектах.

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

При определении некоторой переменной-члена вне любого метода-члена переменная может быть статической или нестатической в ​​зависимости от того, как выражается переменная.

  • 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

Отступ нарушен. Это не будет выполнено

Thomas Weller 16.07.2019 11:56

Что касается этого отвечать, для статической переменной постоянный вы можете использовать дескриптор. Вот пример:

class ConstantAttribute(object):
    '''You can initialize my value but not change it.'''
    def __init__(self, value):
        self.value = value

    def __get__(self, obj, type=None):
        return self.value

    def __set__(self, obj, val):
        pass


class Demo(object):
    x = ConstantAttribute(10)


class SubDemo(Demo):
    x = 10


demo = Demo()
subdemo = SubDemo()
# should not change
demo.x = 100
# should change
subdemo.x = 100
print "small demo", demo.x
print "small subdemo", subdemo.x
print "big demo", Demo.x
print "big subdemo", SubDemo.x

в результате чего ...

small demo 10
small subdemo 100
big demo 10
big subdemo 10

Вы всегда можете вызвать исключение, если незаметное игнорирование значения параметра (pass выше) не для вас. Если вы ищете переменную статического класса в стиле C++, Java:

class StaticAttribute(object):
    def __init__(self, value):
        self.value = value

    def __get__(self, obj, type=None):
        return self.value

    def __set__(self, obj, val):
        self.value = val

Взгляните на этот ответ и официальную документацию КАК для получения дополнительной информации о дескрипторах.

Вы также можете просто использовать @property, что аналогично использованию дескриптора, но это намного меньше кода.

Rick supports Monica 19.12.2014 18:34

Статические и классовые методы

Как отмечали другие ответы, статические методы и методы класса легко выполняются с использованием встроенных декораторов:

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

Это связано с тем, что строка x.i = 12 добавила новый атрибут экземпляра i в x вместо изменения значения атрибута Test класса 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 вызывает ошибку.

НАСТОЯЩЕЕ, НАСТОЯЩЕЕ Статические переменные - 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», класс вернется к поведению при получении / установке / удалении атрибута по умолчанию (то есть «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

Я попытался использовать ваш способ, но у меня возникла проблема, пожалуйста, посмотрите мой вопрос здесь stackoverflow.com/questions/29329850/get-static-variable-val‌ ue

Muhammed Refaat 29.03.2015 18:22

@RickTeachey: Я думаю, вам следует обычно рассматривать все, что вы делаете в классе Instance Test (перед его использованием для создания экземпляров), как относящееся к области метапрограммирования? Например, вы изменяете поведение класса, выполняя Test.i = 0 (здесь вы просто полностью уничтожаете объект свойства). Я предполагаю, что «механизм свойств» срабатывает только при доступе к свойствам экземпляров класса (если, возможно, вы не измените базовое поведение, используя мета-класс в качестве промежуточного звена). Кстати, закончите, пожалуйста, этот ответ :-)

Ole Thomsen Buus 11.05.2015 18:20

@OleThomsenBuus Эм, я бы не сказал так. Правильнее было бы назвать это «программированием объекта класса» и «программированием объекта экземпляра», потому что классы ЯВЛЯЮТСЯ объектами. Мета-программирование необходимо (в данном конкретном случае) только в том случае, если вы хотите стереть грань между экземпляром и классом, где находится «статическая переменная». Поведение property работает, потому что объекты экземпляра делегируют доступ к атрибутам своему классу в определенных случаях (например, когда атрибут не существует). Также: я обещаю, что дойду до того, что задумал для этого ответа. Просто был занят.

Rick supports Monica 12.05.2015 04:48

@RickTeachey Спасибо :-) В конце концов, ваш метакласс интересен, но, на мой взгляд, слишком сложен. Это может быть полезно в большом фреймворке / приложении, где этот механизм абсолютно необходим. Во всяком случае, это показывает, что если действительно необходимо новое (сложное) нестандартное мета-поведение, Python делает это возможным :)

Ole Thomsen Buus 15.05.2015 00:54

@OleThomsenBuus: проверьте мой ответ на наличие более простого метакласса, который выполняет эту работу.

Ethan Furman 22.02.2017 19:40

Я попробовал и обнаружил, что @property не синхронизируется между экземплярами классов в python 3. Это особенность старого языка Python?

taper 08.12.2017 17:30

@taper, как объяснено в ответе выше, @property синхронизируется между экземплярами класса (для всех версий python, возвращающихся на долгое время). член класса с тем же именем, что и свойство, прикрепленное к самому объекту класса, является нет синхронно с экземплярами.

Rick supports Monica 08.12.2017 18:04

Уважаемый @RickTeachey, я не могу понять вашу точку зрения из-за некоторых терминов, с которыми я не знаком. Но то, что я имею в виду, может быть продемонстрировано этим суть. Если вы запустите его в python 3.6.3 (что я и сделал), он даст другой результат, заявленный в этом ответе.

taper 08.12.2017 18:52

@taper Вы правы; Я отредактировал ответ, чтобы исправить проблему (не могу поверить, что он так долго сидел там неправильно!). Извините за путаницу.

Rick supports Monica 08.12.2017 19:14

Очень нравится писать. Один придирчивый выбор: неизменная версия на самом деле не является неизменной, потому что вы можете назначить лямбду для get_i, которая возвращает другое значение.

Howard Lovatt 11.04.2019 22:16

@HowardLovatt, на самом деле это не так. Если вы попытаетесь установить патч get_i после того, как свойство было создано, оно не перезапишет свойство. попытайся.

Rick supports Monica 12.04.2019 00:26

Следующие изменения t.i для меня: t.get_i = lambda s: 7 ; assert t.i == 7 # ERROR i changed.

Howard Lovatt 13.04.2019 15:39

@HowardLovatt, ваш оператор assert должен быть assert t.i == 3, и ошибки нет.

Rick supports Monica 13.04.2019 21:44

@RickTeachey Нет, для меня он проходит assert, т.е. t.i равен 7 - он изменился. Это верно для Pythonista, который использует 3.6 (я думаю) и 3.7 (через PyCharm).

Howard Lovatt 14.04.2019 23:11

@HowardLovatt, это странно. Это определенно не изменит значение ни в одной из версий python, которые я когда-либо использовал, если вы сначала установите свойство, а затем замените геттер (или сеттер) без перезаписи свойства. Это просто не так.

Rick supports Monica 15.04.2019 00:16

Можно иметь переменные класса 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

Статические переменные в фабрике классов python3.6

Для всех, кто использует фабрику классов с python3.6 и выше, используйте ключевое слово nonlocal, чтобы добавить его в область / контекст создаваемого класса следующим образом:

>>> def SomeFactory(some_var=None):
...     class SomeClass(object):
...         nonlocal some_var
...         def print():
...             print(some_var)
...     return SomeClass
... 
>>> SomeFactory(some_var = "hello world").print()
hello world

да, но в данном случае hasattr(SomeClass, 'x') - это False. Я сомневаюсь, что это вообще то, что кто-то имеет в виду под статической переменной.

Rick supports Monica 25.07.2017 05:55

@RickTeachey lol, видел ваш код статической переменной, stackoverflow.com/a/27568860/2026508 +1 интернет, сэр, и я подумал, что hasattr так не работает? Итак, some_var неизменен и статически определен, или нет? Какое отношение имеет внешний доступ к геттеру к статической переменной или нет? у меня сейчас так много вопросов. хотелось бы услышать ответы, когда у вас будет время.

jmunsch 25.07.2017 22:09

Да, этот метакласс довольно нелепый. Я не уверен, что понимаю вопросы, но, на мой взгляд, some_var, приведенный выше, вообще не является членом класса. В Python ко всем членам класса можно получить доступ извне.

Rick supports Monica 25.07.2017 22:55

Клавиатура nonlocal «увеличивает» область действия переменной. Область определения тела класса не зависит от области, в которой он находится - когда вы говорите nonlocal some_var, это просто создает нелокальную (читай: НЕ в области определения класса) ссылку на имя другого именованного объекта. Поэтому он не привязывается к определению класса, потому что он не входит в область видимости тела класса.

Rick supports Monica 25.07.2017 22:59

Абсолютно да, Сам по себе Python не имеет явного статического члена данных, но мы можем сделать это

class A:
    counter =0
    def callme (self):
        A.counter +=1
    def getcount (self):
        return self.counter  
>>> x=A()
>>> y=A()
>>> print(x.getcount())
>>> print(y.getcount())
>>> x.callme() 
>>> print(x.getcount())
>>> print(y.getcount())

выход

0
0
1
1

объяснение

here object (x) alone increment the counter variable
from 0 to 1 by not object y. But result it as "static counter"

Один очень интересный момент в поиске атрибутов 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, потому что, хотя label является статическим в том смысле, что он не связан с экземпляром частности, значение по-прежнему зависит от (класса) экземпляра.

Да, безусловно, можно писать статические переменные и методы на Python.

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

    >>> class A:
        ...my_var = "shagun"

    >>> print(A.my_var)
        shagun

Переменные экземпляра: Переменные, связанные и доступные экземпляру класса, являются переменными экземпляра.

   >>> a = A()
   >>> a.my_var = "pruthi"
   >>> print(A.my_var,a.my_var)
       shagun pruthi

Статические методы: Подобно переменным, к статическим методам можно обращаться напрямую, используя имя класса. Нет необходимости создавать экземпляр.

Но имейте в виду, что статический метод не может вызывать нестатический метод в python.

    >>> class A:
   ...     @staticmethod
   ...     def my_static_method():
   ...             print("Yippey!!")
   ... 
   >>> A.my_static_method()
   Yippey!!

Я думаю, что то, что вы называете «статическими» переменными, - это переменные класса. Визуализация: class A (): inner_var = 0 class B (A): pass A.inner_var = 15 B.inner_var = 30 print ("A: static =" + str (A.inner_var)) print ("B: static = "+ str (B.inner_var)) # Вывод: # A: static = 15 # B: static = 30

Andrew 16.01.2021 16:13

Вы можете использовать список или словарь, чтобы получить «статическое поведение» между экземплярами.

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

Так что это, вероятно, взлом, но я использовал eval(str) для получения статического объекта, что-то вроде противоречия, в python 3.

Существует файл Records.py, в котором нет ничего, кроме объектов class, определенных с помощью статических методов и конструкторов, которые сохраняют некоторые аргументы. Затем из другого файла .py я import Records, но мне нужно динамически выбирать каждый объект, а затем создавать его экземпляр по запросу в соответствии с типом считываемых данных.

Итак, где object_name = 'RecordOne' или имя класса, я вызываю cur_type = eval(object_name), а затем для его создания вы делаете cur_inst = cur_type(args) Однако перед созданием экземпляра вы можете вызвать статические методы из cur_type.getName(), например, что-то вроде реализации абстрактного базового класса или любой другой цели. Однако в бэкэнде он, вероятно, создается в python и не является по-настоящему статическим, потому что eval возвращает объект ... который должен быть создан ... который дает статическое поведение.

Если вы пытаетесь поделиться статической переменной, например, для ее увеличения в других экземплярах, что-то вроде этого скрипта отлично работает:

# -*- coding: utf-8 -*-
class Worker:
    id = 1

    def __init__(self):
        self.name = ''
        self.document = ''
        self.id = Worker.id
        Worker.id += 1

    def __str__(self):
        return u"{}.- {} {}".format(self.id, self.name, self.document).encode('utf8')


class Workers:
    def __init__(self):
        self.list = []

    def add(self, name, doc):
        worker = Worker()
        worker.name = name
        worker.document = doc
        self.list.append(worker)


if __name__ == "__main__":
    workers = Workers()
    for item in (('Fiona', '0009898'), ('Maria', '66328191'), ("Sandra", '2342184'), ('Elvira', '425872')):
        workers.add(item[0], item[1])
    for worker in workers.list:
        print(worker)
    print("next id: %i" % Worker.id)

Скажем так, статическая переменная создается, когда создается пользовательский класс, и определяется статическая переменная, которая должна следовать за ключевым словом self,

class Student:

    the correct way of static declaration
    i = 10

    incorrect
    self.i = 10

Определения @dataclass предоставляют имена уровня класса, которые используются для определения переменных экземпляра и метода инициализации __init__(). Если вам нужна переменная уровня класса в @dataclass, вы должны использовать подсказку типа typing.ClassVar. Параметры типа ClassVar определяют тип переменной уровня класса.

from typing import ClassVar
from dataclasses import dataclass

@dataclass
class Test:
    i: ClassVar[int] = 10
    x: int
    y: int
    
    def __repr__(self):
        return f"Test({self.x=}, {self.y=}, {Test.i=})"

Примеры использования:

> test1 = Test(5, 6)
> test2 = Test(10, 11)

> test1
Test(self.x=5, self.y=6, Test.i=10)
> test2
Test(self.x=10, self.y=11, Test.i=10)

В отличие от @staticmethod, переменные класса являются статическим методом класса и используются всеми экземплярами.

Теперь вы можете получить к нему доступ, как

instance = MyClass()
print(instance.i)

или же

print(MyClass.i)

вы должны присвоить значение этим переменным

я пытался

class MyClass:
  i: str

и присвоение значения в одном вызове метода, в этом случае он не будет работать и выдаст ошибку

i is not attribute of MyClass

Другие вопросы по теме