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.topbicycle.ru" tb.name = "TopBicycle.ru" print(hh.protocol, hh.domain, tb.protocol, tb.domain)
python class_variable.py
https www.heihei.ru https www.topbicycle.ru
Пример с __init__()
Перед изучением следующего примера рекомендую ознакомится со статьями про
__init__()
и
self
Рассмотрим класс Cosmo
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
Изучим __dict__ и убедимся, что там присутствуют cosmo_count, raise_amout и apply_raise()
# Class print(Cosmo.__dict__)
python class_variable.py
{'__module__': '__main__', 'cosmo_count': 0, '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}
Создадим два экземпляра класса Cosmo и первым делом убедимся, что счётчик cosmo_count вырос. Затем изучим __dict__ экземпляра и убедимся, что он не содержит атрибут cosmo_count.
… # Create two instances cosm_2 = Cosmo('German', 'Titov', 2000) cosm_3 = Cosmo('Andriyan', 'Nikolaev', 3000) # print(Cosmo.cosmo_count) print(Cosmo.__dict__['cosmo_count']) # Check cosm_2 instance print(cosm_2.__dict__)
2
{'first': 'German', 'last': 'Titov', 'pay': 2000, 'fullname': 'German Titov', 'email': 'German.Titov@vostok.su'}
Изучим как работает apply_raise()
… # 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__)
Initial Titov pay: 2000 New Titov pay: 2080 {'first': 'German', 'last': 'Titov', 'pay': 2080, 'fullname': 'German Titov', 'email': 'German.Titov@vostok.su'}
Покажем, что можно задать экземпляру класса атрибут с таким же названием как и атрибут класса и это не повлияет на другие экземпляры.
# 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}") print(cosm_3.__dict__)
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 {'first': 'Andriyan', 'last': 'Nikolaev', 'pay': 3000, 'fullname': 'Andriyan Nikolaev', 'email': 'Andriyan.Nikolaev@vostok.su'}
Видно, что у cosmo_2 raise_amount есть, а у cosmo_3 нет.
Убедимся, что счётчик космонавтов по-прежнему равен двум
… # Check that instance count works print(f"Created {Cosmo.cosmo_count} cosmonaut profiles")
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 Scope пуст, а в 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 | |
| Статические методы | |
| Наследование | |
| super() | |
| Специальные методы | |
| dataclass | |
| __slots__ | |
| Декоратор property | |
| Полиморфизм |