Свойства

Сеттер

Атрибутам пространства имен могут быть присвоены значения, но что, если атрибут является свойством? Если вы попытаетесь присвоить значение свойству, имеющему только один метод получения, вы получите ошибку «AttributeError: невозможно установить атрибут».

Чтобы присвоить значение свойству, необходимо использовать сеттер. Сеттер — это также метод, который принимает новое значение атрибута и каким-то образом его обрабатывает. Чтобы метод стал сеттером, он также должен быть соответствующим образом оформлен. Если у вас уже есть геттер, вы можете сделать это:

classPerson: def__init __ (self, имя, фамилия): self.name=selfname.surname=surname@propertydeffull_name (self): returnself.name + » + self.surname # установщик для свойства full_name @ full_name.setterdeffull_name (self , новое): self.name, self.surname = new.split(‘ ‘)

Теперь свойству full_name можно присвоить новое полное имя, которое в конечном итоге будет разделено пробелом на две части, первая из которых станет новым именем, а вторая — фамилией. Пример:

>>> tom = Person (‘Thomas’, ‘Smith’) >>> tom.full_name’Thomas Smith ‘>>> tom.full_name =’ Alice Cooper ‘>>> tom.name’Alice’ >>> фамилия тома ‘Купер’

Сеттеры часто используются для проверки правильности нового значения или выполнения какого-либо преобразования перед фактическим сохранением его в других атрибутах.

Порядок разрешения доступа к атрибутам

Картина довольно интересная: объект может иметь несколько родительских классов, а также специальные методы, такие как __getattribute__, которые перехватывают запросы атрибутов.

Как интерпретатор решает сложные запросы по свойствам и методам? Рассмотрим последовательность поиска на примере запроса obj.field:

  1. Вызовите obj .__ getattribute __ (‘field’), если он определен. При установке или удалении атрибута он проверяет наличие __setattr__ или __delattr__ соответственно.
  2. Найдите obj .__ dict__ (настраиваемые атрибуты).
  3. Искать в объекте .__ class __.__ slot__.
  4. Рекурсивный поиск поля __dict__ всех родительских классов. Если у класса есть несколько предков, порядок проверки — это порядок, в котором они перечислены в определении.
  5. Если определен метод __getattr__, вызывается obj .__ getattr __ (‘field’)
  6. Выдается исключение несуществующего атрибута — AttributeError.

Наконец, когда атрибут найден, проверяется существование метода __get__ (__set__ при установке, __delete__ при удалении).

Все эти проверки выполняются только для настраиваемых атрибутов.

Making the Attributes Private

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

Example

Живая демонстрация

class year_graduated: def __init __ (self, year = 32): self._year = year # создать метод getter def get_year (self): return self .__ year # создать метод установки def set_year (self, a): self. __ year = a grad_obj = year_graduated () print (grad_obj._year) # Перед использованием установщика print (grad_obj.get_year ()) # # # После использования установщика grad_obj.set_year (2019) print (grad_obj._year)

Output

Выполнение приведенного выше кода дает следующий результат:

32 AttributeError: объект ‘year_graduated’ не имеет атрибута ‘_year_graduated__year’

Пример установки

Теперь предположим, что мы хотим установить атрибут name и mark при изменении значения gotmarks. Внимательно посмотрите на код:

class Student: def __init __ (self, name, mark): self.name = name self.marks = mark # self.gotmarks = self.name + ‘got’ + self.marks + ‘mark’ @property def gotmarks (self): return self.name + ‘got’ + self.marks + ‘mark’ @ gotmarks.setter def gotmarks (self, предложение): name, rand, mark = предложение.split (») self.name = name self voices = оценки st = Студент («Jaki», «25») print (st.name) print (st.marks) print (st.gotmarks) print («############### # ### «) st.name =» Anusha «print (st.name) print (st.gotmarks) print (» ################### «) st gotmarks = ‘Голам получил 36’ print (st.gotmarks) print (st.name) print (st.marks)

Потому что мы хотим обновить имя и значение меток при установке значения gotmarks. Итак, с помощью установщика декоратора @proprety мы можем этого добиться.

Обратите внимание, что мы написали @ gotmarks.setter, что означает, что мы применяем сеттер к методу gotmarks. Затем мы разбиваем предложение и обновляем значение имени и оценок.

Приведенный выше декоратор свойств с сеттером даст результат, показанный ниже.

Джаки 25 Джаки набрала 25 очков ################### Ануша Ануша набрала 25 очков ################# Голам получил 36 оценок Golam 36 Оценка (3 балла, в среднем 5 из 5) Комментарии0Поделиться: Загрузка… Связанный контент

The @property Decorator

В Python property () — это встроенная функция, которая создает и возвращает объект свойства. Синтаксис этой функции:

свойство (fget = None, fset = None, fdel = None, doc = None)

где это находится,

  • fget — это функция для получения значения атрибута
  • fset — это функция для установки значения атрибута
  • fdel — это функция для удаления атрибута
  • doc — это строка (например, комментарий)

Как видно из реализации, эти аргументы функции необязательны. Следовательно, объект свойства можно просто создать следующим образом.

>>> свойство()

У объекта свойства есть три метода: getter (), setter () и deleter () для указания функций fget, fset и fdel в более позднее время. Это означает, что строка:

температура = свойство (get_tempera, set_temperature)

можно разложить как:

# создать пустое свойство temperature = property () # назначить fget temperature = temperature.getter (get_temperature) # назначить fset temperature = temperature.setter (set_temperature)

Эти два кода эквивалентны.

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

Мы даже не можем определить имена get_tempera и set_temperature, поскольку они не нужны и загрязняют пространство имен класса.

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

# Использование класса декоратора @property Celsius: def __init __ (self, temperature = 0): self.temperature = temperature def to_fahrenheit (self): return (self.temperature * 1.8) + 32 @property def temperature (self): print («Get value…») return self._temperature @ temperature.setter def temperature (self, value): print («Set value…») если значение <-273.15: увеличить ValueError («Температура ниже -273 не возможно «) self._temperature = value # создать объект human = Celsius (37) print (human.temperature) print (human.to_fahrenheit ()) coldest_thing = Celsius (-300)

Производство

Установка значения… Получение значения… 37 Получение значения… 98.60000000000001 Настройка значения… Traceback (последний самый последний вызов): File ««, строка 29, в Файл ««, строка 4, в файле __init__ ««, строка 18, в значении температуры ValueError: температура ниже -273 невозможна

Вышеупомянутая реализация проста и эффективна. Это рекомендуемый способ использования собственности.

Using getters and setters

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

Example

Живая демонстрация

class year_graduated: def __init __ (self, year = 0): self._year = year # метод получения def get_year (self): return self._year # метод установки def set_year (self, a): self._year = a grad_obj = year_graduated () # Перед использованием сеттера print (grad_obj.get_year ()) # После использования сеттера grad_obj.set_year (2019) print (grad_obj._year)

Output

Выполнение приведенного выше кода дает следующий результат:

0 2019

Пересечение множества в Python

Проверка является ли переменная строковой в Python

Проблема

Возьмем пример. Есть класс людей, возраст которых не может быть ниже 0 и выше 120. Типичная программа ООП будет выглядеть так:

class Human: «» «Человек, возраст которого не может быть больше 120 и меньше 0» «» def __init __ (self, age = 0): self.set_age (age) def get_age (self): return self age def set_age (self, age): если возраст <120 и возраст> = 0: self.age = age else: self.age = 0

Как мы видим, в этой реализации есть некоторые проблемы, и давайте рассмотрим их внимательно.

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

h = Человек (возраст = 30) print (h.get_age ()) 30

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

Попробуем представить возраст вне человеческой жизни

h.set_age (150) печать (h.get_age ()) 0

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

Но мы можем обратиться напрямую к атрибуту self.age и указать абсолютно любой возраст.

age = 150 отпечаток (возраст) 150

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

Решение

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

Использование декоратора

@property — это декоратор, который обрабатывает получение, установку и удаление переменных класса так, как ожидает Python. Код для предыдущего сценария теперь будет выглядеть так:

class Human: «» «Человек, возраст которого не может быть больше 120 и меньше 0» «» def __init __ (self, age = 0): self.age = age @property def age (self): return self .__ age @age .setter def age (self, age): if age <120 и age> = 0: self .__ age = age else: self .__ age = 0

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

  • Мы использовали декоратор @property, который создает геттер для декорирования функции.
  • Затем мы использовали декоратор @ age.setter, который создает сеттер для функции для украшения. Установщик определяет, что делать при установке значения переменной.

Обратите внимание, что обе функции имеют одинаковое имя. Это необходимо для работы программы.

Попробуем выставить неверное значение и посмотрим, что получится:

h = Человек (возраст = 30) h возраст = 150 отпечаток (h возраст) 0

Используя декоратор @property, мы ограничиваем возможность указывать значения для переменных в классе.

Метаклассы

Метаклассы — это классы, экземпляры которых также являются классами.

class MetaClass (type): # выделить память для класса def __new __ (cls, name, base, dict): print («Создать новый класс {}» format (name)) return type .__ new __ (cls, name, base, dict) # инициализация класса def __init __ (cls, name, base, dict): print («Инициализация нового класса {}» формат (имя)) return super (MetaClass, cls) .__ init __ (имя, базы, dict) # генерация класса на основе метакласса SomeClass = MetaClass («SomeClass», (), {}) # нормальный класс наследования Child (SomeClass): def __init __ (self, param): print (param) # получить экземпляр class obj = Child («Привет»)

Делитер

Удаление объявляется аналогично объявлению установщика. Этот метод вызывается, когда старое значение свойства отбрасывается, например, когда свойству присваивается новое значение. Eliminator позволяет, наконец, сделать что-то необходимое, например закрыть ранее открытый файл или сетевое соединение, одним словом, «убрать за собой».

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

Функция set union() в Python

Функция slice() в Python

Properties vs. Getters and Setters

Properties

Геттеры (также известные как «аксессуары») и сеттеры (также известные как «мутаторы») используются во многих объектно-ориентированных языках программирования для обеспечения принципа инкапсуляции данных. Инкапсуляция данных — как мы узнали из введения в объектно-ориентированное программирование в нашем руководстве — рассматривается как группировка данных с помощью методов, которые с ними работают. Эти методы, очевидно, являются геттером для восстановления данных и установщиком для модификации данных. Согласно этому принципу, атрибуты класса делаются закрытыми, чтобы скрыть и защитить их.

К сожалению, широко распространено мнение, что соответствующий класс Python должен инкапсулировать частные атрибуты с помощью геттеров и сеттеров. Как только один из этих программистов вводит новый атрибут, они сделают его частной переменной и «автоматически» создадут геттер и сеттер для этого атрибута. Такие программисты могут даже использовать редактор или IDE, которые автоматически создают геттеры и сеттеры для всех частных атрибутов. Эти инструменты даже предупреждают программиста, если он использует публичный атрибут! Программисты на Java хмурятся, морщат носы или даже кричат ​​от ужаса, когда читают следующее: Пифонический способ введения атрибутов — сделать их общедоступными.

Мы объясним это позже. Во-первых, мы демонстрируем в следующем примере, как мы можем разработать класс в стиле Javaesque с помощью геттеров и сеттеров для инкапсуляции частного атрибута self .__ x:

classP: def__init __ (self, x): self .__ x = xdefget_x (self): returnself .__ xdefset_x (self, x): self .__ x = x

В следующем демонстрационном сеансе мы можем увидеть, как работать с этим классом и методами:

frommutatorsimportPp1 = P (42) p2 = P (4711) p1.get_x () Вывод: 42p1.set_x (47) p1.set_x (p1.get_x () + p2.get_x ()) p1.get_x () Вывод: 4758

Что вы думаете о выражении «p1.set_x (p1.get_x () + p2.get_x ())»? Это уродливо, правда? Гораздо проще написать такое выражение, если бы у нас был публичный атрибут x:

p1.x = p1.x + p2.x

Это присваивание легче написать и, что более важно, легче читать, чем выражение Javaesque.

Перепишем P-класс в стиле Python. Никаких геттеров, сеттеров и вместо частного атрибута self .__ x мы используем публичный:

classP: def__init __ (self, x): self.x = x

Красиво, не правда ли? Всего три строчки кода, если не считать пустую строку!

dapimportPp1 = P (42) p2 = P (4711) p1.x Вывод: 42p1.x = 47p1.x = p1.x + p2.xp1.x Вывод: 4758

«Но, но, но, но, но…», мы можем слышать их вой и крик: «Но НЕ ДАЕТСЯ КАПСУЛЯЦИЯ!» Да, в этом случае инкапсуляции данных нет. В данном случае он нам не нужен. Единственное, что get_x и set_x сделали в нашем начальном примере, — это «передали данные», не делая ничего лишнего.

Но что, если мы захотим изменить реализацию в будущем? Это серьезная тема. Предположим, мы хотим изменить реализацию следующим образом: Атрибут x может иметь значения от 0 до 1000. Если присвоено значение больше 1000, x должен быть установлен на 1000. Следовательно, x должен быть установлен на 0, если значение меньше 0.

наш первый P-класс легко изменить, чтобы решить эту проблему. Давайте соответствующим образом изменим метод set_x:

classP: def__init __ (self, x): self.set_x (x) defget_x (self): returnself .__ xdefset_x (self, x): ifx <0: self .__ x = 0elifx> 1000: self .__ x = 1000else : auto .__ x = x

Следующий сеанс Python показывает, что он работает так, как мы хотим:

frommutators1importPp1 = P (1001) p1.get_x () Вывод: 1000p2 = P (15) p2.get_x () Вывод: 15p3 = P (-1) p3.get_x () Вывод: 0

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

classP2: def__init __ (self, x): self.x = x

Люди уже много использовали его и написали такой код:

p1 = P2 (42) p1.x = 1001p1.x Выход: 1001

Если мы теперь изменим P2 на режим класса P, наш новый класс сломает интерфейс, потому что атрибут x больше не будет доступен. Вот почему в Java, например, людям рекомендуется использовать только частные атрибуты с геттерами и сеттерами, чтобы они могли изменить реализацию без изменения интерфейса.

Но Python предлагает решение этой проблемы. Решение называется владением!

Класс со свойством выглядит так:

classP: def__init __ (self, x): self.x=x@propertydefx (self): returnself .__ x@x.setterdefx (self, x): ifx <0: self .__ x = 0elifx> 1000: self. __ x = 1000else: self .__ x = x

Метод, используемый для получения значения, обозначается символом «@property», т.е мы помещаем эту строку непосредственно перед заголовком. Метод, выполняющий функции сеттера, украшен символом «@ x.setter». Если бы функция была названа «f», нам пришлось бы украсить ее «@ f.setter». Следует отметить две вещи: мы просто помещаем строку кода «self.x = x» в метод __init__, а метод свойства x используется для проверки границ значений. Вторая интересная вещь заключается в том, что мы написали «два» метода с одинаковым именем и разным количеством параметров «def x (self)» и «def x (self, x)». В предыдущей главе нашего курса мы узнали, что это невозможно. Здесь работает из-за декора:

fromp2importPp1 = P (1001) p1.x Выход: 1000p1.x = -12p1.x Выход: 0

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

classP: def__init __ (self, x): self.set_x (x) defget_x (self): returnself .__ xdefset_x (self, x): ifx <0: self .__ x = 0elifx> 1000: self .__ x = 1000else : self .__ x = xx = свойство (get_x, set_x)

В новой версии есть еще одна проблема. Теперь у нас есть два способа доступа или изменения значения x: с помощью «p1.x = 42» или «p1.set_x (42)». Таким образом, мы нарушаем одну из основ Python: «Должен быть один — и желательно только один — очевидный способ сделать это». (см. Python Zen)

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

classP: def__init __ (self, x): self .__ set_x (x) def__get_x (self): returnself .__ xdef__set_x (self, x): ifx <0: self .__ x = 0elifx> 1000: self .__ x = 1000else: self .__ x = xx = свойство (__ get_x, __ set_x)

Хотя мы решили эту проблему, используя частные методы получения и установки, версия с декоратором «@property» — это способ решения Pythonic!

Из того, что мы написали до сих пор, и из того, что также можно увидеть в других книгах и учебных пособиях, мы можем легко создать впечатление, что существует взаимно однозначная связь между свойствами (или методами изменения) и атрибутами, т. Е. Что каждый атрибут имеет или должен иметь собственное свойство (или пару геттер-установщик) и наоборот. Даже в других объектно-ориентированных языках, отличных от Python, обычно не рекомендуется реализовывать такой класс. Основная причина в том, что многие атрибуты необходимы только для внутреннего использования, а создание пользовательских интерфейсов для класса излишне увеличивает удобство использования класса. Возможный пользователь класса не должен быть «завален» тысячей — в основном бесполезными методами или свойствами!

В следующем примере показан класс с внутренними атрибутами, к которому нельзя получить доступ извне. Это частные атрибуты self .__ Physical_potential и self .__ Psyic_potential. Мы также показываем, что свойство может быть выведено из значений более чем одного атрибута. Свойство «condition» в нашем примере возвращает состояние робота в описательной строке. Состояние зависит от суммы значений психического и физического состояния робота.

classRobot: def__init __ (self, name, build_year, lk = 0.5, lp = 0.5): self.name = nameself.build_year = build_yearself .__ physical_potential = lkself .__ mental_potential = lp @ propertydefcondition (self): s = self .__ Physical_potential + self __potential_psychicifs <= — 1: return «Я чувствую себя несчастным!» elifs <= 0: return «Мне плохо!» elifs <= 0.5: return «Могло быть и хуже!» elifs <= 1: return «Кажется, все в порядке!» else: верните «Отлично!» if__name __ == «__ main __»: x = Робот («Марвин», 1979,0.2,0.4) y = Робот («Калибан», 1993, -0.4,0.3) print (x.condition) print (y.condition) Вроде бы нормально! Я плохо себя чувствую!

Использование декоратора свойств Python

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

Вот простой пример:

@property def fun (a, b): возвращает a + b

Это то же самое, что:

def fun (a, b): вернуть a + b fun = property (удовольствие)

Итак, здесь мы оборачиваем свойство () вокруг fun (), что и делает декоратор. Давайте теперь рассмотрим простой пример использования декоратора свойств в методе класса.

Рассмотрим следующий класс без декорированных методов:

class Student (): def __init __ (self, name, id): self.name = name self.id = id self.full_id = self.name + «-» + str (self.id) def get_name (self): return self.name s = Student («Amit», 10) print (s.name) print (s.full_id) # Изменить только имя s.name = «Rahul» print (s.name) print (s.full_id)

Производство

Амит Амит — 10 Рахул Амит — 10

Здесь, как вы можете видеть, когда мы изменяем только атрибут name нашего объекта, ссылка на атрибут full_id еще не обновляется.

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

class Student (): def __init __ (self, name, id): self.name = name self.id = id def get_name (self): return self.name # Преобразование full_id в метод def full_id (self): return self .name + «-» + str (self.id) s = Student («Amit», 10) print (s.name) # Вызов метода print (s.full_id ()) s.name = «Rahul» print (s .name) # Вызов метода print (s.full_id())

Производство

Амит Амит — 10 Рахул Рахул — 10

Здесь мы решили нашу проблему, преобразовав метод full_id в метод full_id() .

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

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

Идея состоит в том, чтобы превратить full_id () в метод, но обернуть его @property. Таким образом, мы можем обновить full_id, не рассматривая его как вызов функции.

Мы можем сделать это напрямую: s.full_id. Обратите внимание, что здесь нет вызова метода. Это связано с декоратором собственности.

Давай попробуем сейчас.

class Student (): def __init __ (self, name, id): self.name = name self.id = id def get_name (self): return self.name @property def full_id (self): return self.name + » — «+ str (self.id) s = Student (» Amit «, 10) print (s.name) # Больше никаких вызовов методов! print (s.full_id) s.name = «Rahul» print (s.name) # Больше никаких вызовов методов! печать (s.full_id)

Производство

Амит Амит — 10 Рахул Рахул — 10

Фактически, теперь это работает. Теперь нам не нужно вызывать full_id, используя круглые скобки.

Хотя это все еще метод, декоратор свойств маскирует его и рассматривает как свойство класса.

The property Class

Один из способов Python решить указанную выше проблему — использовать класс свойств. Вот как мы можем обновить наш код:

# использование класса свойств class Celsius: def __init __ (self, temperature = 0): self.temperature = temperature def to_fahrenheit (self): return (self.temperature * 1.8) + 32 # getter def get_temperature (self): print (» Получить значение… «) return self._temperature # setter def set_temperature (self, value): print (» Set value… «) if value <-273.15: увеличить ValueError (» Температура ниже -273.15 невозможна «) self ._temperature = value # создание свойства объекта temperature = property (get_temperature, set_temperature)

Мы добавили функцию print () внутри get_tempera () и set_temperature (), чтобы четко видеть, что они выполняются.

Последняя строка кода создает температуру объекта свойства. Вкратце, свойство связывает некоторый код (get_temperature и set_temperature) с доступом к атрибутам элемента (температура).

Мы используем этот код обновления:

# использование класса свойств class Celsius: def __init __ (self, temperature = 0): self.temperature = temperature def to_fahrenheit (self): return (self.temperature * 1.8) + 32 # getter def get_temperature (self): print (» Получить значение… «) return self._temperature # setter def set_temperature (self, value): print (» Set value… «) if value <-273.15: увеличить ValueError (» Температура ниже -273.15 невозможна «) self ._temperature = value # создание свойства object temperature = property (get_temperature, set_temperature) human = Celsius (37) print (human.temperature) print (human.to_fahrenheit ()) human.temperature = -300

Производство

Установка значения… Получение значения… 37 Получение значения… 98.60000000000001 Настройка значения… Traceback (последний самый последний вызов): File ««, строка 31, в Файл ««, строка 18, в set_temperature ValueError: температура ниже -273 невозможна

Как мы видим, любой код, который извлекает значение температуры, автоматически вызывает get_tempera () вместо поиска по словарю (__dict__). Точно так же любой код, который присваивает значение температуре, автоматически вызывает set_tempera().

Мы также можем видеть выше, что set_temperature () также вызывалась, когда мы создавали объект.

>>> человек = Цельсий (37) Установить значение…

Вы можете догадаться, почему?

Причина в том, что при создании объекта вызывается метод __init __ (). В этом методе есть строка self.temperature = temperature. Это выражение автоматически вызывает set_tempera().

Точно так же любой логин, такой как c.temperature, автоматически вызывает get_tempera (). Это то, чем занимается отель. Вот еще несколько примеров.

>>> human.temperature Получить значение 37 >>> human.temperature = 37 Установить значение >>> c.to_fahrenheit () Получить значение 98.60000000000001

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

Примечание. Текущее значение температуры сохраняется в частной переменной _temperature. Атрибут температуры — это объект свойства, который предоставляет интерфейс для этой частной переменной.

Декоратор property

Если вы посмотрите документацию к декоратору свойств, то увидите следующую подпись:

свойство (fget = None, fset = None, fdel = None, doc = None)

Первые три аргумента позволяют указать метод получения, установки и удаления, а аргумент doc позволяет указать строку документации. В этом модуле свойство удобно использовать, когда у вас уже есть готовые функции, которые вы просто хотите «упаковать» в свойство:

defget_full_name (self): . defset_full_name (self, new): . classPerson: . full_name = property (fget = get_full_name, fset = set_full_name, doc = ‘Полное имя человека’)

Функция locals() в Python

Глобальные переменные в Python

Специальные методы

Жизненный цикл объекта

Вы уже знакомы с инициализатором объекта __init__. В дополнение к нему существует также метод __new__, который непосредственно создает новый экземпляр класса. В качестве первого параметра он принимает ссылку на сам класс:

class SomeClass (object): def __new __ (cls): print («new») return super (SomeClass, cls) .__ new __ (cls) def __init __ (self): print («init») obj = SomeClass (); # новый # инициализация

Это обсуждение StackOverflow поможет вам лучше понять экземпляр класса.

Метод __new__ может быть очень полезен для решения ряда задач, например, для создания неизменяемых объектов или реализации паттерна Singleton:

class Singleton (object): obj = None # единственный экземпляр класса def __new __ (cls, * args, ** kwargs): если cls.obj равно None: cls.obj = object .__ new __ (cls, * args, ** kwargs) return cls.obj single = Singleton () single.attr = 42 newSingle = Singleton () newSingle.attr # 42 newSingle is single # true

В Python вы можете участвовать не только в создании объекта, но и в его удалении. Для этого специально разработан метод деструктора __del__.

class SomeClass (объект): def __init __ (self, name): self.name = name def __del __ (self): print (‘объект {} класса SomeClass удален’ формат (self.name)) obj = SomeClass («Джон»); del obj # удалил объект класса SomeClass Джона

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

Объект как функция

Объект класса может имитировать стандартную функцию, то есть при желании его можно «вызвать» с параметрами. За эту функцию отвечает специальный метод __call__:

class Multiplier: def __call __ (self, x, y): return x * y multiply = Multiplier () multiplier (19, 19) # 361 # тот же множитель .__ call __ (19, 19) # 361

Имитация контейнеров

Вы знаете функцию len (), которая может вычислять длину списков значений?

list = ‘привет’, ‘мир’ len (список) # 2

Но для объектов вашего пользовательского класса это не сработает:

коллекция классов: def __init __ (self, list): self.list = list collection = Коллекция (список) len (коллекция)

Этот код сгенерирует объект ошибки типа «Коллекция», у которого нет len (). Интерпретатор просто не понимает, как рассчитать длину коллекции.

Решить эту проблему поможет специальный метод __len__:

коллекция классов: def __init __ (self, list): self.list = list def __len __ (self): return len (self.list) collection = Collection (1, 2, 3) len (collection) # 3

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

  • __getItem__ — реализация синтаксиса obj key, получение значения по ключу;
  • __setItem__ — установка значения ключа;
  • __delItem__ — удалить значение;
  • __contains__ — проверяет наличие значения.

Имитация числовых типов

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

class SomeClass: def __init __ (self, value): self.value = value def __mul __ (self, number): return self.value * number obj = SomeClass (42) print (obj * 100) # 4200

Другие специальные методы

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

Эти методы могут имитировать поведение встроенных классов, но они не обязательно существуют в самих встроенных классах. Например, для объектов типа int метод __add__ не вызывается во время добавления. Следовательно, их нельзя перезаписать.

Reading Values from Private Methods

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

Example

Живая демонстрация

class year_graduated: def __init __ (self, year = 32): self._year = year @property def Aboutyear (self): return self .__ year @ Aboutyear.setter def Aboutyear (self, a): self .__ year = a grad_obj = year_graduated () print (grad_obj._year) grad_obj.year = 2018 print (grad_obj.year)

Принципы ООП на Python

Давайте посмотрим на три большие объектно-ориентированные концепции: инкапсуляцию, полиморфизм и наследование.

Инкапсуляция

Все объекты в Python инкапсулируют данные и методы для работы с ними внутри них, обеспечивая общедоступные интерфейсы для взаимодействия.

Атрибут может быть объявлен частным (внутренним) с подчеркиванием перед именем, но на самом деле никакого фактического скрытия не происходит — все это на уровне соглашений.

class SomeClass: def _private (self): print («Это внутренний метод объекта») obj = SomeClass () obj._private () # Это внутренний метод объекта

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

class SomeClass (): def __init __ (self): self .__ param = 42 # защищенный атрибут obj = SomeClass () obj .__ param # AttributeError: объект SomeClass не имеет атрибута __param obj._SomeClass__param # 42

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

Помимо прямого доступа к атрибутам (obj.attrName), можно использовать специальные аксессуары (геттеры, сеттеры и деструкторы):

class SomeClass (): def __init __ (self, value): self._value = value def getvalue (self): # получить значение атрибута return self._value def setvalue (self, value): # установить значение атрибута attribute self._value = def value delvalue (self): # удаление атрибута self._value value = property (getvalue, setvalue, delvalue, «Property value») obj = SomeClass (42) print (obj.value) obj .value = 43

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

Вместо того, чтобы вручную создавать геттеры и сеттеры для каждого атрибута, вы можете перегрузить встроенные методы __getattr__, __setattr__ и __delattr__. Например, вот как вы можете перехватывать вызовы свойств и методов, которых нет в объекте:

class SomeClass (): attr1 = 42 def __getattr __ (self, attr): return attr.upper () obj = SomeClass () obj.attr1 # 42 obj.attr2 # ATTR2

__getattribute__ перехватывает все попадания (включая попадания в существующие атрибуты):

class SomeClass (): attr1 = 42 def __getattribute __ (self, attr): return attr.upper () obj = SomeClass () obj.attr1 # ATTR1 obj.attr2 # ATTR2

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

Наследование

В языке программирования Python реализовано стандартное одинарное наследование:

class Mammal (): className = ‘Mammal’ class Dog (Млекопитающее): разновидности = ‘Canis lupus’ dog = Dog () dog.className # Млекопитающее

и множественное число:

class Horse (): isHorse = True class Donkey (): isDonkey = True class Mule (Horse, Donkey): mule = Mule () mule.isHorse # True mule.isDonkey # True

ООП в Python

Используя множественное наследование, вы можете создавать классы миксинов (миксинов), которые представляют определенную характеристику поведения. Этот микс можно «смешать» с любым классом.

Ассоциация

Помимо наследования существует еще один способ организации взаимодействия между классами: ассоциация (агрегирование или композиция), где один класс является полем другого.

Пример композиции:

class Salary: def __init __ (self, pay): self.pay = pay def getTotal (self): return (self.pay * 12) class Employee: def __init __ (self, pay, Bonus): self.pay = pay self.bonus = бонус self.salary = Salary (self.pay) def AnnualSalary (self): return «Total:» + str (self.salary.getTotal () + self.bonus) employee = Employee (100,10) print (сотрудник .annualSalary())

Пример агрегирования:

class Salary (объект): def __init __ (self, pay): self.pay = pay def getTotal (self): return (self.pay * 12) class Employee (object): def __init __ (self, pay, Bonus) : self .pay = pay self.bonus = бонус def AnnualSalary (self): return «Total:» + str (self.pay.getTotal () + self.bonus) salary = Salary (100) employee = Employee (salary, 10)) print (employee.annualSalary())

Связанные объекты могут ссылаться друг на друга циклически, нарушая стандартный механизм сбора мусора. Слабые ссылки (слабая форма f) помогают избежать таких проблем ассоциации).

Полиморфизм

Концепция полиморфизма — важная часть ООП в Python. Все методы на языке изначально виртуальные. Это означает, что дочерние классы могут переопределять их и решать одну и ту же проблему по-разному, а конкретная реализация будет выбрана только во время выполнения. Такие классы называются полиморфными.

class Mammal: def move (self): print (‘Moves’) class Hare (Mammal): def move (self): print (‘Jumps’) animal = Mammal () animal.move () # Перемещает hare = Hare () hare.move () # Прыгает

Однако вы также можете получить доступ к методам класса-предка через прямой вызов или с помощью функции super:

class Parent (): def __init __ (self): print (‘Parent init’) def method (self): print (‘Parent method’) class Child (Parent): def __init __ (self): Parent .__ init __ (self) def method (self): super (Child, self) .method () child = Child () # Родительский дочерний метод init () # Родительский метод

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

class English: def приветствие (self): print («Hello») class French: def приветствие (self): print («Bonjour») def intro (language): language.greeting () john = English () gerard = French () intro (john) # Hello intro (gerard) # Bonjour

Это возможно благодаря утиному набору текста.

Множественная диспетчеризация

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

Программирование на Python не поддерживает готовые к использованию мультиметоды, поэтому для их реализации вам необходимо включить сторонние библиотеки Python, такие как multimethods.py.

Источники

  • https://ru.hexlet.io/courses/python-oop-basics/lessons/properties/theory_unit
  • https://proglib.io/p/python-oop
  • https://www.tutorialspoint.com/getter-and-setter-in-python
  • https://pythonim.ru/osnovy/dekorator-svoystv-property-python
  • https://www.programiz.com/python-programming/property
  • [https://egorovegor.ru/python-property/]
  • [https://www.python-course.eu/python3_properties.php]
  • [https://pythononline.ru/osnovy/dekorator-python]

Оцените статью
Блог о JavaScript