Как обратиться к атрибуту live класса hero
Перейти к содержимому

Как обратиться к атрибуту live класса hero

  • автор:

Пользовательские атрибуты в Python

Вы когда нибудь задумывались о том, что происходит, когда вы ставите точку в python? Что скрывает за собой символ str(“\u002E”)? Какие тайны он хранит? Если без мистики, вы знаете как происходит поиск и установка значений пользовательских атрибутов в python? Хотели бы узнать? Тогда… добро пожаловать!
Чтобы время, проведённое за чтением прошло легко, приятно и с пользой, было бы неплохо знать несколько базовых понятий языка. В частности, понимание type и object будут исключительно полезны, так же как знание нескольких примеров обеих сущностей. Почитать о них можно, в том числе, здесь.
Немного о терминологии, которую я использую, прежде чем мы приступим к тому, ради чего собрались:

  • Объект есть любая сущность в python (функция, число, строка… словом, всё).
  • Класс это объект, чьим типом является type (тип можно подсмотреть в атрибуте __class__).
  • Экземпляр некоторого класса A — это объект, у которого в атрибуте __class__ есть ссылка на класс A.

Ах, да, все примеры в статье написаны на python3! Это определённо следует учесть.
Если ничто из вышесказанного не смогло умерить ваше желание узнать, что там будет дальше, приступим!

__dict__
  1. Сам объект (o.__dict__ и его системные атрибуты).
  2. Класс объекта (o.__class__.__dict__). Только __dict__ класса, не системные атрибуты.
  3. Классы, от которых унасаледован класс объекта (o.__class__.__bases__.__dict__).
class StuffHolder: stuff = "class stuff" a = StuffHolder() b = StuffHolder() a.stuff # "class stuff" b.stuff # "class stuff" b.b_stuff = "b stuff" b.b_stuff # "b stuff" a.b_stuff # AttributeError 

В примере описан класс StuffHolder с одним атрибутом stuff, который, наследуют оба его экземпляра. Добавление объекту b атрибута b_stuff, никак не отражается на a.
Посмотрим на __dict__ всех действующих лиц:

StuffHolder.__dict__ # a.__dict__ # <> b.__dict__ # a.__class__ # b.__class__ #

(У класса StuffHolder в __dict__ хранится объект класса dict_proxy с кучей разного барахла, на которое пока не нужно обращать внимание).

Ни у a ни у b в __dict__ нет атрибута stuff, не найдя его там, механизм поиска ищет его в __dict__ класса (StuffHolder), успешно находит и возвращает значение, присвоенное ему в классе. Ссылка на класс хранится в атрибуте __class__ объекта.
Поиск атрибута происходит во время выполнения, так что даже после создания экземпляров, все изменения в __dict__ класса отразятся в них:

a.new_stuff # AttributeError b.new_stuff # AttributeError StuffHolder.new_stuff = "new" StuffHolder.__dict__ # a.new_stuff # "new" b.new_stuff # "new" 

В случае присваивания значения атрибуту экземпляра, изменяется только __dict__ экземпляра, то есть значение в __dict__ класса остаётся неизменным (в случае, если значением атрибута класса не является data descriptor):

StuffHolder.__dict__ # c = StuffHolder() c.__dict__ # <> c.stuff = "more c stuff" c.__dict__ # StuffHolder.__dict__ #

Если имена атрибутов в классе и экземпляре совпадают, интерпретатор при поиске значения выдаст значение экземпляра (в случае, если значением атрибута класса не является data descriptor):

StuffHolder.__dict__ # d = StuffHolder() d.stuff # "class stuff" d.stuff = "d stuff" d.stuff # "d stuff" 

По большому счёту это всё, что можно сказать про __dict__. Это хранилище атрибутов, определённых пользователем. Поиск в нём производится во время выполнения и при поиске учитывается __dict__ класса объекта и базовых классов. Также важно знать, что есть несколько способов переопределить это поведение. Одним из них является великий и могучий Дескриптор!

Дескрипторы

С простыми типами в качестве значений атрибутов пока всё ясно. Посмотрим, как ведёт себя функция в тех же условиях:

class FuncHolder: def func(self): pass fh = FuncHolder() FuncHolder.func # FuncHolder.__dict__ # <. 'func': . > fh.func # > 

WTF!? Спросите вы… возможно. Я бы спросил. Чем функция в этом случае отличается от того, что мы уже видели? Ответ прост: методом __get__.

FuncHolder.func.__class__.__get__ #

Этот метод переопределяет механизм получения значения атрибута func экземпляра fh, а объект, который реализует этот метод непереводимо называется non-data descriptor.

Дескриптор — это объект, доступ к которому через атрибут переопределён методами в дескриптор протоколе:

descr.__get__(self, obj, type=None) --> value (переопределяет способ получения значения атрибута) descr.__set__(self, obj, value) --> None (переопределяет способ присваивания значения атрибуту) descr.__delete__(self, obj) --> None (переопределяет способ удаления атрибута)
  1. Data Descriptor (дескриптор данных) — объект, который реализует метод __get__() и __set__()
  2. Non-data Descriptor (дескриптор не данных?) — объект, который реализует метод __get__()
Дескрипторы данных

Рассмотрим повнимательней дескриптор данных:

class DataDesc: def __get__(self, obj, cls): print("Trying to access from class ".format(obj, cls)) def __set__(self, obj, val): print("Trying to set for ".format(val, obj)) def __delete__(self, obj): print("Trying to delete from ".format(obj)) class DataHolder: data = DataDesc() d = DataHolder() DataHolder.data # Trying to access from None class d.data # Trying to access from class d.data = 1 # Trying to set 1 for del(d.data) # Trying to delete from

Стоит обратить внимание, что вызов DataHolder.data передаёт в метод __get__ None вместо экземпляра класса.
Проверим утверждение о том, что у дата дескрипторов преимущество перед записями в __dict__ экземпляра:

d.__dict__["data"] = "override!" d.__dict__ # d.data # Trying to access from class

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

Ещё один важный момент. Если изменить значение атрибута с дескриптором через класс, никаких методов дескриптора вызвано не будет, значение изменится в __dict__ класса как если бы это был обычный атрибут:

DataHolder.__dict__ # . > DataHolder.data = "kick descriptor out" DataHolder.__dict__ # DataHolder.data # "kick descriptor out" 
Дескрипторы не данных

Пример дескриптора не данных:

class NonDataDesc: def __get__(self, obj, cls): print("Trying to access from class ".format(obj, cls)) class NonDataHolder: non_data = NonDataDesc() n = NonDataHolder() NonDataHolder.non_data # Trying to access from None class n.non_data # Trying to access from class n.non_data = 1 n.non_data # 1 n.__dict__ #

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

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

Дескрипторы это мощный инструмент, позволяющий контролировать доступ к атрибутам экземпляра класса. Один из примеров их использования — функции, при вызове через экземпляр они становятся методами (см. пример выше). Также распространённый способ применения дескрипторов — создание свойства (property). Под свойством я подразумеваю некое значение, характеризующее состояние объекта, доступ к которому управляется с помощью специальных методов (геттеров, сеттеров). Создать свойство просто с помощью дескриптора:

class Descriptor: def __get__(self, obj, type): print("getter used") def __set__(self, obj, val): print("setter used") def __delete__(self, obj): print("deleter used") class MyClass: prop = Descriptor() 

Или можно воспользоваться встроенным классом property, он представляет собой дескриптор данных. Код, представленный выше можно переписать следующим образом:

class MyClass: def _getter(self): print("getter used") def _setter(self, val): print("setter used") def _deleter(self): print("deleter used") prop = property(_getter, _setter, _deleter, "doc string") 

В обоих случаях мы получим одинаковое поведение:

m = MyClass() m.prop # getter used m.prop = 1 # setter used del(m.prop) # deleter used 

Важно знать, что property всегда является дескриптором данных. Если в его конструктор не передать какую либо из функций (геттер, сеттер или делитер), при попытке выполнить над атрибутом соответствующее действие — выкинется AttributeError.

class MySecondClass: prop = property() m2 = MySecondClass() m2.prop # AttributeError: unreadable attribute m2.prop = 1 # AttributeError: can't set attribute del(m2) # AttributeError: can't delete attribute 
  • staticmethod — то же, что функция вне класса, в неё не передаётся экземпляр в качестве первого аргумента.
  • classmethod — то же, что метод класса, только в качестве первого аргумента передаётся класс экземпляра.
class StaticAndClassMethodHolder: def _method(*args): print("_method called with ", args) static = staticmethod(_method) cls = classmethod(_method) s = StaticAndClassMethodHolder() s._method() # _method called with (,) s.static() # _method called with () s.cls() # _method called with (,) 
__getattr__(), __setattr__(), __delattr__() и __getattribute__()

Если нужно определить поведение какого-либо объекта как атрибута, следует использовать дескрипторы (например property). Тоже справедливо для семейства объектов (например функций). Ещё один способ повлиять на доступ к атрибутам: методы __getattr__(), __setattr__(), __delattr__() и __getattribute__(). В отличие от дескрипторов их следует определять для объекта, содержащего атрибуты и вызываются они при доступе к любому атрибуту этого объекта.

__getattr__(self, name) будет вызван в случае, если запрашиваемый атрибут не найден обычным механизмом (в __dict__ экземпляра, класса и т.д.):

class SmartyPants: def __getattr__(self, attr): print("Yep, I know", attr) tellme = "It's a secret" smarty = SmartyPants() smarty.name = "Smartinius Smart" smarty.quicksort # Yep, I know quicksort smarty.python # Yep, I know python smarty.tellme # "It's a secret" smarty.name # "Smartinius Smart" 

__getattribute__(self, name) будет вызван при попытке получить значение атрибута. Если этот метод переопределён, стандартный механизм поиска значения атрибута не будет задействован. Следует иметь ввиду, что вызов специальных методов (например __len__(), __str__()) через встроенные функции или неявный вызов через синтаксис языка осуществляется в обход __getattribute__().

class Optimist: attr = "class attribute" def __getattribute__(self, name): print(" is great!".format(name)) def __len__(self): print("__len__ is special") return 0 o = Optimist() o.instance_attr = "instance" o.attr # attr is great! o.dark_beer # dark_beer is great! o.instance_attr # instance_attr is great! o.__len__ # __len__ is great! len(o) # __len__ is special\n 0 

__setattr__(self, name, value) будет вызван при попытке установить значение атрибута экземпляра. Аналогично __getattribute__(), если этот метод переопределён, стандартный механизм установки значения не будет задействован:

class NoSetters: attr = "class attribute" def __setattr__(self, name, val): print("not setting =".format(name,val)) no_setters = NoSetters() no_setters.a = 1 # not setting a=1 no_setters.attr = 1 # not setting attr=1 no_setters.__dict__ # <> no_setters.attr # "class attribute" no_setters.a # AttributeError 

__delattr__(self, name) — аналогичен __setattr__(), но используется при удалении атрибута.

При переопределении __getattribute__(), __setattr__() и __delattr__() следует иметь ввиду, что стандартный способ получения доступа к атрибутам можно вызвать через object:

class GentleGuy: def __getattribute__(self, name): if name.endswith("_please"): return object.__getattribute__(self, name.replace("_please", "")) raise AttributeError("And the magic word!?") gentle = GentleGuy() gentle.coffee = "some coffee" gentle.coffee # AttributeError gentle.coffee_please # "some coffee" 
Соль
  1. Если определён метод a.__class__.__getattribute__(), то вызывается он и возвращается полученное значение.
  2. Если attrname это специальный (определённый python-ом) атрибут, такой как __class__ или __doc__, возвращается его значение.
  3. Проверяется a.__class__.__dict__ на наличие записи с attrname. Если она существует и значением является дескриптор данных, возвращается результат вызова метода __get__() дескриптора. Также проверяются все базовые классы.
  4. Если в a.__dict__ существует запись с именем attrname, возвращается значение этой записи. Если a — это класс, то атрибут ищется и среди его базовых классов и, если там или в __dict__a дескриптор данных — возвращается результат __get__() дескриптора.
  5. Проверяется a.__class__.__dict__, если в нём существует запись с attrname и это “дескриптор не данных”, возвращается результат __get__() дескриптора, если запись существует и там не дескриптор, возвращается значение записи. Также обыскиваются базовые классы.
  6. Если существует метод a.__class__.__getattr__(), он вызывается и возвращается его результат. Если такого метода нет — выкидывается AttributeError.
  1. Если существует метод a.__class__.__setattr__(), он вызывается.
  2. Проверяется a.__class__.__dict__, если в нём есть запись с attrname и это дескриптор данных — вызывается метод __set__() дескриптора. Также проверяются базовые классы.
  3. В a.__dict__ добавляется запись value с ключом attrname.
__slots__

Как пишет Guido в своей истории python о том, как изобретались new-style classes:

… Я боялся что изменения в системе классов плохо повлияют на производительность. В частности, чтобы дескрипторы данных работали корректно, все манипуляции атрибутами объекта начинались с проверки __dict__ класса на то, что этот атрибут является дескриптором данных…

На случай, если пользователи разочаруются ухудшением производительности, заботливые разработчики python придумали __slots__.
Наличие __slots__ ограничивает возможные имена атрибутов объекта теми, которые там указаны. Также, так как все имена атрибутов теперь заранее известны, снимает необходимость создавать __dict__ экземпляра.

class Slotter: __slots__ = ["a", "b"] s = Slotter() s.__dict__ # AttributeError s.c = 1 # AttributeError s.a = 1 s.a # 1 s.b = 1 s.b # 1 dir(s) # [ . 'a', 'b' . ] 

Оказалось, что опасения Guido не оправдались, но к тому времени, как это стало ясно, было уже слишком поздно. К тому же, использование __slots__ действительно может увеличить производительность, особенно уменьшив количество используемой памяти при создании множества небольших объектов.

Заключение

Доступ к атрибутом в python можно контролировать огромным количеством способов. Каждый из них решает свою задачу, а вместе они подходят практически под любой мыслимый сценарий использования объекта. Эти механизмы — основа гибкости языка, наряду с множественным наследованием, метаклассами и прочими вкусностями. У меня ушло некоторое время на то, чтобы разобраться, понять и, главное, принять это множество вариантов работы атрибутов. На первый взгляд оно показалось слегка избыточным и не особенно логичным, но, учитывая, что в ежедневном программировании это редко пригодиться, приятно иметь в своём арсенале такие мощные инструменты.
Надеюсь, и вам эта статья прояснила парочку моментов, до которых руки не доходили разобраться. И теперь, с огнём в глазах и уверенностью в Точке, вы напишите огромное количество наичистейшего, читаемого и устойчивого к изменениям требований кода! Ну или комментарий.

Спасибо за ваше время.

Ссылки
  1. Shalabh Chaturvedi. Python Attributes and Methods
  2. Guido Van Rossum. The Inside Story on New-Style Classes
  3. Python documentation

Создание класса в Python

Эксперименты будем ставить на коде, который решает очень важную и ответственную задачу — выводит на экран вес фруктов. Делает он это, разумеется, с использованием ООП. Куда же без него во “взрослых” проектах?! И пускай от объявленного класса Fruit нет никакого проку, без него код выглядел бы слишком просто, а теперь в самый раз:

class Fruit(): pass apple = Fruit() apple.fruit_name = 'Яблоко' apple.weight_kg = 0.1 orange = Fruit() orange.fruit_name = 'Апельсин' orange.weight_kg = 0.3 print(apple.fruit_name, "| вес в граммах", apple.weight_kg * 1000) print(orange.fruit_name, "| вес в граммах", orange.weight_kg * 1000) 

Скопируйте код к себе, запустите его в консоли:

$ python script.py Яблоко | вес в граммах 100.0 Апельсин | вес в граммах 300.0 

Как это работает

Разберем что написано в этом коде. Первые две строки объявят новый класс Fruit :

class Fruit(): pass 

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

Следующая строка кода создаст новый объект — яблоко “apple”:

apple = Fruit() 

Класс Fruit выступает шаблоном, который описывает свойства общие сразу для всех фруктов: и для яблок, и для апельсинов. Пока что шаблон пуст, и экземпляр apple ничего полезного от вызова Fruit не получит.

Следующие две строки добавят пару атрибутов к яблоку — название фрукта и его вес:

apple.fruit_name = 'Яблоко' apple.weight_kg = 0.1 

Затем аналогичным образом по тому же шаблону — классу Fruit — будет создан еще один фрукт, на этот раз апельсин:

orange = Fruit() orange.fruit_name = 'Апельсин' orange.weight_kg = 0.3 

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

print(apple.fruit_name, "| вес в граммах", apple.weight_kg * 1000) print(orange.fruit_name, "| вес в граммах", orange.weight_kg * 1000) 

Обязательные атрибуты

Можно заметить, что оба фрукта в программе имеют свойства с одинаковыми названиями — название fruit_name и вес weight_kg . Доработаем класс Fruit таким образом, чтобы все экземпляры созданные по этому шаблону обязательно имели свойства fruit_name и weight_kg .

Идея следующая. Пусть Python каждый раз после создания нового экземпляра фрукта сразу добавляет все нужные атрибуты. Добавим для этого новую функцию init_attrs :

# . объявление класса Fruit . def init_attrs(fruit, fruit_name='неизвестный фрукт', weight_kg=None): fruit.fruit_name = fruit_name fruit.weight_kg = weight_kg apple = Fruit() init_attrs(apple, 'Яблоко', 0.1) orange = Fruit() init_attrs(orange, 'Апельсин', 0.3) # . вызовы print . 

Кода стало больше, но он стал надежнее. Добавляя новые фрукты: груши, вишню или абрикосы — вы точно не забудете указать вес и название. Вызовы функции init_attrs гарантирует, что все фрукты получат одинаковый набор обязательных атрибутов.

В Python коде функции похожие на init_attrs встречаются настолько часто, что для них есть стандартное название и специальный синтаксис. Переименуем функцию init_attrs в __init__ и переместим внутрь класса Fruit .

class Fruit(): def __init__(fruit, fruit_name='неизвестный фрукт', weight_kg=None): fruit.fruit_name = fruit_name fruit.weight_kg = weight_kg apple = Fruit('Яблоко', 0.1) orange = Fruit('Апельсин', 0.3) print(apple.fruit_name, "| вес в граммах", apple.weight_kg * 1000) print(orange.fruit_name, "| вес в граммах", orange.weight_kg * 1000) 

Обратите внимание, что в программе нигде нет явного вызова __init__ . Python сам его вызовет выполняя эти строки кода:

apple = Fruit('Яблоко', 0.1) orange = Fruit('Апельсин', 0.3) 

Теперь создавать фрукты по шаблону стало еще проще. Не надо писать вызовы функций, достаточно передать все аргументы для __init__ в класс Fruit .

Часто метод __init__ называют конструктором класса по аналогии с другими языками программирования. Это не совсем верно. Подробнее читайте на StackOverflow.

Добавим метод

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

print(..., apple.weight_kg * 1000) print(..., orange.weight_kg * 1000) 

Пересчет веса можно вынести в отдельную функцию:

# . объявление класса, __init__ для Fruit def get_weight_gr(fruit): return fruit.weight_kg * 1000 apple = Fruit('Яблоко', 0.1) orange = Fruit('Апельсин', 0.3) print(apple.fruit_name, "| вес в граммах", get_weight_gr(apple)) print(orange.fruit_name, "| вес в граммах", get_weight_gr(orange)) 

Функция get_weight_gr требует единственный аргумент — объект описывающий фрукт. Ей нет разницы, с каким именно фруктом работать, яблоком или апельсином. Главное, это чтобы фрукт был создан по стандартному шаблону — на основе класса Fruit . Python позволяет спрятать функцию get_weight_gr внутрь класса Fruit , чтобы эту связь между ними было не разорвать.

class Fruit(): def __init__(fruit, fruit_name='неизвестный фрукт', weight_kg=None): fruit.fruit_name = fruit_name fruit.weight_kg = weight_kg def get_weight_gr(fruit): return fruit.weight_kg * 1000 

Функции, объявленные внутри класса называют методами и Python предлагает специальный набор инструментов для работы с ними.

По аналогии с функциями метод можно вызвать так:

Fruit.get_weight_gr(apple) 

Однако, не вдаваясь в детали, принято делать так:

apple.get_weight_gr() 

Python всегда помнит от какого класса был создан тот или иной объект, и знает что яблоко apple принадлежит к классу Fruit . От класса Fruit яблоко apple получило все его методы, включая get_weight_gr . Доступ к методам объекта получают через точку apple.get_weight_gr , а при вызове указывают первый аргумент функции, потому что его автоматически подставляет Python.

Теперь программа выглядит так:

class Fruit(): def __init__(fruit, fruit_name='неизвестный фрукт', weight_kg=None): fruit.fruit_name = fruit_name fruit.weight_kg = weight_kg def get_weight_gr(fruit): return fruit.weight_kg * 1000 apple = Fruit('Яблоко', 0.1) orange = Fruit('Апельсин', 0.3) print(apple.fruit_name, "| вес в граммах", apple.get_weight_gr()) print(orange.fruit_name, "| вес в граммах", orange.get_weight_gr()) 

Кто такой self

В Python есть стандартное название для первого атрибута метода. Вернемся к коду с фруктами и увидим там такое объявление методов:

class Fruit(): def __init__(fruit, fruit_name='неизвестный фрукт', weight_kg=None): ... def get_weight_gr(fruit): ... 

В методах принято первый аргумент fruit называть self . Ход исполнения программы от этого не меняется, но другим программистам будет проще разобраться в коде:

class Fruit(): def __init__(self, fruit_name='неизвестный фрукт', weight_kg=None): self.fruit_name = fruit_name self.weight_kg = weight_kg def get_weight_gr(self): return self.weight_kg * 1000 apple = Fruit('Яблоко', 0.1) orange = Fruit('Апельсин', 0.3) print(apple.fruit_name, "| вес в граммах", apple.get_weight_gr()) print(orange.fruit_name, "| вес в граммах", orange.get_weight_gr()) 

Когда __init__ не нужен

Часто новый класс создается не на пустом месте, а наследуется от другого класса, предоставленного библиотекой. Например, так в Django выглядит добавление на сайт нового типа объектов — статей для блога:

from django.db.models import Model class Article(Model): pass 

Сразу после объявления нового класса Article указан класс-предок Model . Из него будут позаимствованы все методы, включая готовый к использованию __init__ . Теперь внутри класса Article будет достаточно описать те методы, что отличают его от стандартного класса Model .

Когда аргументов много

Подобно другим функциям метод может принимать больше одного аргумента. Например:

class Fruit(): ... def get_title(self, upper_case, max_length): title = self.fruit_name if upper_case: title = title.upper() if max_length: title = title[:max_length] return title 

Метод get_title принимает три аргумента. Так как это не просто функция, а метод, то первым аргументом обязан быть self . Его значение автоматически подставит Python. Остальные два аргумента upper_case и max_length должны быть вручную указаны при вызове метода:

apple.get_title(True, 20) 

Попробуйте бесплатные уроки по Python

Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.

Переходите на страницу учебных модулей «Девмана» и выбирайте тему.

Как внутри одного класса обратиться к атрибутам другого класса?

Только начинаю постигать азы ООП, и уперся в следующую проблему: в методе receprtion класса Warehouse необходимо обратиться к атрибуту (например, model) другого класса. Если обращаться через class_name.attribute — пишет, что такого атрибута у класса нет. Корректно работает, только если обращаться напрямую к атрибуту созданного экземпляра класса. Но поскольку объектов будет больеше одного, то данный вариант не подходит. Каким образом возможно реализовать данный функционал?

class Warehouse: def __init__(self, sales, storage, repairs): self.sales = sales self.storage = storage self.repairs = repairs def reception(self): return f' принята на склад' class Equip: def __init__(self, model, quantity, price): self.model = model self.quantity = quantity self.price = price @classmethod def uniq_param(cls): pass class Xerox(Equip): def __init__(self, model, quantity, price): super().__init__(model, quantity, price) def uniq_param(self): return 'Ксерокс делает ксерографические копии' w = Warehouse('Отдел продаж', 'отдел хранения', 'отдел ремонта') x = Xerox(6525, 2, 1000) print(x.model) print(w.reception()) 

Отслеживать

задан 21 фев 2021 в 15:05

Artyom Lisenkov Artyom Lisenkov

1 2 2 бронзовых знака

Очевидно, функция приёма должна в аргументах ожидать то, что она принимает.

21 фев 2021 в 15:25

Дополнительно: Вы используете classmethod не для того, для чего он нужен. super().__init__(model, quantity, price) — __init__ метод только с этой строкой равносилен отсутствию __init__ метода.

21 фев 2021 в 15:27

Благодарю, заработало))

Классы и объекты. Атрибуты классов и объектов

Меня зовут Сергей Балакирев и на этом занятии мы с вами узнаем, как в Python определять классы, создавать объекты (экземпляры) этих классов, а также добавлять и удалять их атрибуты (то есть, данные).

Предположим, мы хотим определить класс для хранения координат точек на плоскости. Для начала я его запишу без какого-либо содержимого, только имя класса Point и все:

class Point: pass

Здесь оператор pass указывает, что мы в классе ничего не определяем. Также обратите внимание, что в соответствии со стандартом PEP8 имя класса принято записывать с заглавной буквы. И, конечно же, называть так, чтобы оно отражало суть этого класса. В дальнейшем я буду придерживаться этого правила.

Итак, у нас получилось простейшее определение класса с именем Point. Но в таком виде он не особо полезен. Поэтому я пропишу в нем два атрибута: color – цвет точек; circle – радиус точек:

class Point: color = 'red' circle = 2

Обратите внимание, переменные внутри класса обычно называются атрибутами класса или его свойствами. Я буду в дальнейшем использовать эту терминологию. Теперь в нашем классе есть два атрибута color и circle. Но, как правильно воспринимать эту конструкцию? Фактически, сам класс образует пространство имен, в данном случае с именем Point, в котором находятся две переменные color и circle. И мы можем обращаться к ним, используя синтаксис для пространства имен, например:

Point.color = 'black'

или для считывания значения:

Point.circle

(В консольном режиме увидим значение 2). А чтобы увидеть все атрибуты класса можно обратиться к специальной коллекции __dict__:

Point.__dict__

Здесь отображается множество служебных встроенных атрибутов и среди них есть два наших: color и circle.

Теперь сделаем следующий шаг и создадим экземпляры этого класса. В нашем случае для создания объекта класса Point достаточно после его имени прописать круглые скобки:

a = Point()

Смотрите, справа на панели в Python Console у нас появилась переменная a, через которую доступны два атрибута класса: color и circle.

Давайте создадим еще один объект этого класса:

b = Point()

Появилась переменная b, которая ссылается на новый объект (он расположен по другому адресу) и в этом объекте мы также видим два атрибута класса Point. По аналогии можно создавать произвольное количество экземпляров класса.

С помощью функции type мы можем посмотреть тип данных для переменных a или b:

type(a)

Видим, что это класс Point. Эту принадлежность можно проверить, например, так:

type(a) == Point
isinstance(a, Point)

То есть, имя класса здесь выступает в качестве типа данных. Но давайте детальнее разберемся, что же у нас в действительности получилось?

Во-первых, объекты a и b образуют свое пространство имен – пространство имен экземпляров класса и, во-вторых, не содержат никаких собственных атрибутов. Свойства color и circle принадлежат непосредственно классу Point и находятся в нем, а объекты a и b лишь имеют ссылки на эти атрибуты класса. Поэтому я не случайно называю их именно атрибутами класса, подчеркивая этот факт. То есть, атрибуты класса – общие для всех его экземпляров. И мы можем легко в этом убедиться. Давайте изменим значение свойства circle на 1:

Point.circle = 1

И в обоих объектах это свойство стало равно 1. Мало того, если посмотреть коллекцию __dict__ у объектов:

a.__dict__

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

a.color b.circle

Но, если мы выполним присваивание, например:

a.color = 'green'

То, смотрите, в объекте a свойство color стало ‘green’, а в b – прежнее. Почему? Дело в том, что мы здесь через переменную a обращаемся к пространству имен уже экземпляра класса и оператор присваивания в Python создает новую переменную, если она отсутствует в текущей локальной области видимости, то есть, создается атрибут color уже непосредственно в объекте a:

Мы можем в этом убедиться, если отобразим коллекцию __dict__ этого объекта:

a.__dict__

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

Добавление и удаление атрибутов класса

Кстати, по аналогии, мы можем создавать новые атрибуты и в классе, например, так:

Point.type_pt = 'disc'

Или то же самое можно сделать с помощью специальной функции:

setattr(Point, 'prop', 1)

Она создает новый атрибут в указанном пространстве имен (в данном случае в классе Point) с заданным значением. Если эту функцию применить к уже существующему атрибуту:

setattr(Point, 'type_pt', 'square')

то оно будет изменено на новое значение.

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

Point.circle

Но, при обращении к несуществующему атрибуту класса, например:

Point.a

возникнет ошибка. Этого можно избежать, если воспользоваться специальной встроенной функцией:

getattr(Point, 'a', False)

Здесь третий аргумент – возвращаемое значение, если атрибут не будет найден. Эту же функцию можно вызвать и с двумя аргументами:

getattr(Point, 'a')

Но тогда также будет сгенерирована ошибка при отсутствии указанного атрибута. Иначе:

getattr(Point, 'color')

она возвратит его значение. То есть, эта функция дает нам больше гибкости при обращении к атрибутам класса. Хотя на практике ей пользуются только в том случае, если есть опасность обращения к несуществующим атрибутам. Обычно, все же, применяют обычный синтаксис:

Point.color

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

del Point.prop

Если повторить эту команду и попытаться удалить несуществующий атрибут, возникнет ошибка. Поэтому перед удалением рекомендуется проверять существование удаляемого свойства. Делается это с помощью функции hasattr:

hasattr(Point, 'prop')

Она возвращает True, если атрибут найден и False – в противном случае.

Также удалить атрибут можно с помощью функции:

delattr(Point, 'type_pt')

Она работает аналогично оператору del.

И, обратите внимание, удаление атрибутов выполняется только в текущем пространстве имен. Например, если попытаться удалить свойство color из объекта b:

del b.color

то получим ошибку, т.к. в объекте b не своих локальных свойств и удалять здесь в общем то нечего. А вот в объекте a есть свое свойство color, которое мы с вами добавляли:

a.__dict__

и его можно удалить:

del a.color

Смотрите, после удаления локального свойства color в объекте a становится доступным атрибут color класса Point с другим значение ‘black’. И это логично, т.к. если свойство не обнаруживается в локальной области, то поиск продолжается в следующей (внешней) области видимости. А это (для объекта a) класс Point. Вот этот момент также следует хорошо понимать при работе с локальными свойствами объектов и атрибутами класса.

Атрибуты экземпляров классов

Теперь, когда мы знаем, как создаются атрибуты, вернемся к нашей задаче формирования объектов точек на плоскости. Мы полагаем, что атрибуты color и circle класса Point – это общие данные для всех объектов этого класса. А вот координаты точек должны принадлежать его экземплярам. Поэтому для объектов a и b мы определим локальные свойства x и y:

a.x = 1 a.y = 2 b.x = 10 b.y = 20

То есть, свойства x, y будут существовать непосредственно в объектах, но не в самом классе Point:

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

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

class Point: "Класс для представления координат точек на плоскости" color = 'red' circle = 2

В результате, специальная переменная:

Point.__doc__

будет ссылаться на это описание. Обычно, при создании больших программ, в ключевых классах создают такие описания, чтобы в последующем было удобнее возвращаться к ранее написанному коду, корректировать его и использовать, не обращаясь к специальной документации.

Заключение

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

Видео по теме

Концепция ООП простыми словами

#1. Классы и объекты. Атрибуты классов и объектов

#2. Методы классов. Параметр self

#3. Инициализатор __init__ и финализатор __del__

#4. Магический метод __new__. Пример паттерна Singleton

#5. Методы класса (classmethod) и статические методы (staticmethod)

#6. Режимы доступа public, private, protected. Сеттеры и геттеры

#7. Магические методы __setattr__, __getattribute__, __getattr__ и __delattr__

#8. Паттерн Моносостояние

#9. Свойства property. Декоратор @property

#10. Пример использования объектов property

#11. Дескрипторы (data descriptor и non-data descriptor)

#12. Магический метод __call__. Функторы и классы-декораторы

#13. Магические методы __str__, __repr__, __len__, __abs__

#14 Магические методы __add__, __sub__, __mul__, __truediv__

#15. Методы сравнений __eq__, __ne__, __lt__, __gt__ и другие

#16. Магические методы __eq__ и __hash__

#17. Магический метод __bool__ определения правдивости объектов

#18. Магические методы __getitem__, __setitem__ и __delitem__

#19. Магические методы __iter__ и __next__

#20. Наследование в объектно-ориентированном программировании

#21. Функция issubclass(). Наследование от встроенных типов и от object

#22. Наследование. Функция super() и делегирование

#23. Наследование. Атрибуты private и protected

#24. Полиморфизм и абстрактные методы

#25. Множественное наследование

#26. Коллекция __slots__

#27. Как работает __slots__ с property и при наследовании

#28. Введение в обработку исключений. Блоки try / except

#29. Обработка исключений. Блоки finally и else

#30. Распространение исключений (propagation exceptions)

#31. Инструкция raise и пользовательские исключения

#32. Менеджеры контекстов. Оператор with

#33. Вложенные классы

#34. Метаклассы. Объект type

#35. Пользовательские метаклассы. Параметр metaclass

#36. Метаклассы в API ORM Django

#37. Введение в Python Data Classes (часть 1)

#38. Введение в Python Data Classes (часть 2)

#39. Python Data Classes при наследовании

© 2023 Частичное или полное копирование информации с данного сайта для распространения на других ресурсах, в том числе и бумажных, строго запрещено. Все тексты и изображения являются собственностью сайта

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *