class variable в Python

Содержание
Введение
Простейший пример
Пример с __init__()
Объявление переменной
Похожие статьи

Введение

В объектно-ориентированном программировании с классами переменная класса - это любая переменная, объявленная со статическим модификатором, для которой существует единственная копия, независимо от того, сколько экземпляров класса существует.

Переменная класса не является переменной экземпляра. Это особый тип атрибута класса (или свойства класса, поля или элемента данных).

Та же дихотомия между экземпляром и членами класса применима и к методам ("функциям-членам"); класс может иметь как методы экземпляра, так и методы класса.

Пример

class Site(): protocol = "https" pass hh = Site() hh.domain = "www.heihei.ru" hh.name = "HeiHei.ru" tb = Site() tb.domain = "www.heihei.ru" tb.name = "TopBicycle.ur" print(hh.protocol, hh.domain, tb.protocol, tb.domain)

python class_variable.py

https www.heihei.ru https www.heihei.ru

Пример с __init__()

Перед изучением следующего примера рекомендую ознакомится со статьями про __init__() и self

class Cosmo: cosmo_count = 0 raise_amount = 1.04 def __init__(self, first, last, pay): self.first = first self.last = last self.pay = pay self.fullname = first + " " + last self.email = first + '.' + last + '@vostok.su' Cosmo.cosmo_count += 1 def apply_raise(self): self.pay = int(self.pay * self.raise_amount) # also possible # self.pay = int(self.pay * Cosmo.raise_amount) # but not # self.pay = int(self.pay * raise_amount) // NameError: name 'raise_amount' is not defined # Class print(Cosmo.__dict__) # Create two instances cosm_2 = Cosmo('German', 'Titov', 2000) cosm_3 = Cosmo('Andriyan', 'Nikolaev', 3000) # Check cosm_2 instance print(cosm_2.__dict__) # Check how raise() works print(f"Initial {cosm_2.last} pay: {cosm_2.pay}") cosm_2.apply_raise() print(f"New {cosm_2.last} pay: {cosm_2.pay}") print(cosm_2.__dict__) # Check that raise amount changed for one instance # does not affect other instances print(f"Initial {cosm_2.last} raise_amount: {cosm_2.raise_amount}") cosm_2.raise_amount = 1.1 print(cosm_2.__dict__) print(f"New {cosm_2.last} raise_amount: {cosm_2.raise_amount}") print(f"{cosm_3.last} raise_amount remains: {cosm_3.raise_amount}") # Check that instance count works print(f"Created {Cosmo.cosmo_count} cosmonaut profiles")

python class_variable.py

{'__module__': '__main__', 'cosmo_count': 2, 'raise_amount': 1.04, '__init__': <function Cosmo.__init__ at 0x7fa3f5e5ea60>, 'apply_raise': <function Cosmo.apply_raise at 0x7fa3f5e5eaf0>, '__dict__': <attribute '__dict__' of 'Cosmo' objects>, '__weakref__': <attribute '__weakref__' of 'Cosmo' objects>, '__doc__': None} {'first': 'German', 'last': 'Titov', 'pay': 2000, 'fullname': 'German Titov', 'email': 'German.Titov@vostok.su'} Initial Titov pay: 2000 New Titov pay: 2080 {'first': 'German', 'last': 'Titov', 'pay': 2080, 'fullname': 'German Titov', 'email': 'German.Titov@vostok.su'} Initial Titov raise_amount: 1.04 {'first': 'German', 'last': 'Titov', 'pay': 2080, 'fullname': 'German Titov', 'email': 'German.Titov@vostok.su', 'raise_amount': 1.1} New Titov raise_amount: 1.1 Nikolaev raise_amount remains: 1.04 Created 2 cosmonaut profiles

Объявление переменной

Рассмотрим следующую попытку использовать class attribute

class ShippingContainer: next_serial = 1337 def __init__(self, owner_code, contents): self.owner_code = owner_code self.contents = contents self.serial = next_serial next_serial +=1 c3 = ShippingContainer("MAE", ["tools"])

python class_attr.py

Traceback (most recent call last): File "class_attr.py", line 12, in <module> c3 = ShippingContainer("MAE", ["tools"]) File "class_attr.py", line 8, in __init__ self.serial = next_serial UnboundLocalError: local variable 'next_serial' referenced before assignment

Получили UnboundLocalError потому что Python не может разобраться с переменной next_serial

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

Local scope это self, owner_code и contents.

То, что идёт после Class это не enclosing function, в этом примере Enclosing Scop пуст, а в Global у нас уже сам класс ShippingContainer

Воспользуемся тем фактом, что ShippingContainer доступен в Global Scope

class ShippingContainer: next_serial = 1337 def __init__(self, owner_code, contents): self.owner_code = owner_code self.contents = contents self.serial = ShippingContainer.next_serial ShippingContainer.next_serial +=1 c3 = ShippingContainer("MAE", ["tools"]) c4 = ShippingContainer("ESC", ["electronics"]) c5 = ShippingContainer("ESC", ["pharmaceuticals"]) c6 = ShippingContainer("ESC", ["noodles"]) print(c3.serial) # 1337 print(c4.serial) # 1338 print("c5.serial", c5.serial) # 1339 print("c5.next_serial", c5.next_serial) # 1340 print("c6.serial", c6.serial) # 1340 print("c6.next_serial", c6.next_serial) # 1341 print(ShippingContainer.next_serial) # 1341 print("c5.serial", c5.serial) # 1339 print("c5.next_serial", c5.next_serial) # 1341 print("c6.serial", c6.serial) # 1340 print("c6.next_serial", c6.next_serial) # 1341

python class_attr.py

1337 1338 c5.serial 1339 c5.next_serial 1341 c6.serial 1340 c6.next_serial 1341 1341 c5.serial 1339 c5.next_serial 1341 c6.serial 1340 c6.next_serial 1341

Можно было использовать вместо ShippingContainer.next_serial self.next_serial но концептуально это неправильно так как не передаёт сути - next_serial это class attribute а не instance attribute и лучше чтобы это было видно сразу.

Также использвание self для class attributes чревато проблемой с присваиванием значений

Рассмотрим пример, в котором неуместное использование self даёт не тот результат, который ждём

class ShippingContainer: next_serial = 1337 def __init__(self, owner_code, contents): self.owner_code = owner_code self.contents = contents # Не рекомендую так делать, но # может работать self.serial = self.next_serial # += 1 читает и изменяет существующий объект # Поэтому можно написать # self.next_serial +=1 # А вот если сделать self.next_serial = self.next_serial + 1 # Instance атрибут будет иметь приоритет # над class attribute c3 = ShippingContainer("MAE", ["tools"]) c4 = ShippingContainer("ESC", ["electronics"]) c5 = ShippingContainer("ESC", ["pharmaceuticals"]) c6 = ShippingContainer("ESC", ["noodles"]) print(c3.serial) # 1337 print(c4.serial) # 1337 self.serial берётся из class attribute print("c5.serial", c5.serial) # 1337 print("c5.next_serial", c5.next_serial) # 1338 print("c6.serial", c6.serial) # 1337 print("c6.next_serial", c6.next_serial) # 1338 print(ShippingContainer.next_serial) # 1337 print("c5.serial", c5.serial) # 1337 print("c5.next_serial", c5.next_serial) # 1338 print("c6.serial", c6.serial) # 1337 print("c6.next_serial", c6.next_serial) # 1338

1337 1337 c5.serial 1337 c5.next_serial 1338 c6.serial 1337 c6.next_serial 1338 1337 c5.serial 1337 c5.next_serial 1338 c6.serial 1337 c6.next_serial 1338

Как видно из вывода - сперва в .serial идёт 1337 из атрибута класса (больше негде брать)

Затем в строке

self.next_serial = self.next_serial + 1

Создаётся новый атрибут (уже экземпляра а не класса). И дальше он увеличивается на 1, а атрибут класса так и остаётся равным 1337.

И так повторяется при каждом новом создании нового экземпляра объекта класса.

Похожие статьи
ООП в Python
Классы
Методы
class variables
class methods
Статические методы
Наследование
Специальные методы
dataclass
__slots__
Декоратор property
super()

Поиск по сайту

Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых

Перейти на канал

@aofeed

Задать вопрос в Телеграм-группе

@aofeedchat

Контакты и сотрудничество:
Рекомендую наш хостинг beget.ru
Пишите на info@urn.su если Вы:
1. Хотите написать статью для нашего сайта или перевести статью на свой родной язык.
2. Хотите разместить на сайте рекламу, подходящую по тематике.
3. Реклама на моём сайте имеет максимальный уровень цензуры. Если Вы увидели рекламный блок недопустимый для просмотра детьми школьного возраста, вызывающий шок или вводящий в заблуждение - пожалуйста свяжитесь с нами по электронной почте
4. Нашли на сайте ошибку, неточности, баг и т.д. ... .......
5. Статьи можно расшарить в соцсетях, нажав на иконку сети: