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() |