Чистыйкод дляпродолжающи х
Скачать 7.85 Mb.
|
Dunder-методы сравнения Метод Python sort() и функция sorted() используют эффективный алгоритм сортировки, для выполнения которого достаточно простого вызова. Но если вы хотите сравнивать и сортировать объекты создаваемого вами класса, необходимо сообщить Python, как должны сравниваться два таких объекта — для этого нужно 378 Глава 17.ООП в Python: свойства и dunder-методы реализовать dunder-методы сравнения. За кулисами Python вызывает dunder- методы сравнения каждый раз, когда ваши объекты используются в выражениях с операторами сравнения < , > , <= , >= , == и != Прежде чем исследовать dunder-методы сравнения, рассмотрим шесть функций модуля operator , которые выполняют те же операции, что и шесть операций срав- нения. Наши dunder-методы сравнения будут вызывать эти функции. Введите следующий фрагмент в интерактивной оболочке: >>> import operator >>> operator.eq(42, 42) # "Равно"; то же, что 42 == 42 True >>> operator.ne('cat', 'dog') # "Не равно"; то же, что 'cat' != 'dog' True >>> operator.gt(10, 20) # "Больше"; то же, что 10 > 20 False >>> operator.ge(10, 10) # "Больше или равно"; то же, что 10 >= 10 True >>> operator.lt(10, 20) # "Меньше"; то же, что 10 < 20 True >>> operator.le(10, 20) # "Меньше или равно"; то же, что 10 <= 20 True Модуль operator предоставляет версии операторов сравнения в виде функций. Их реализации очень просты. Например, можно написать собственную функцию operator.eq() всего в две строки: def eq(a, b): return a == b Реализация оператора сравнения в виде функции полезна, потому что, в отличие от операторов, функции можно передавать как аргументы при вызове других функций. Мы воспользуемся этой возможностью и реализуем вспомогательный метод для наших dunder-методов сравнения. Сначала добавьте следующий фрагмент в начало файла wizcoin.py . Эти команды импортирования открывают доступ к функциям модуля operator и позволяют проверить, является ли аргумент other нашего метода последовательностью, для чего он сравнивается с collections.abc.Sequence : import collections.abc import operator Затем добавьте следующий фрагмент в конец файла wizcoin.py : --snip-- def _comparisonOperatorHelper(self, operatorFunc, other): ❶ """A helper method for our comparison dunder methods.""" Dunder-методы Python 379 if isinstance(other, WizCoin): ❷ return operatorFunc(self.total, other.total) elif isinstance(other, (int, float)): ❸ return operatorFunc(self.total, other) elif isinstance(other, collections.abc.Sequence): ❹ otherValue = (other[0] * 17 * 29) + (other[1] * 29) + other[2] return operatorFunc(self.total, otherValue) elif operatorFunc == operator.eq: return False elif operatorFunc == operator.ne: return True else: return NotImplemented def __eq__(self, other): # "Равно" return self._comparisonOperatorHelper(operator.eq, other) ❺ def __ne__(self, other): # "Не равно" return self._comparisonOperatorHelper(operator.ne, other) ❻ def __lt__(self, other): # "Меньше" return self._comparisonOperatorHelper(operator.lt, other) ❼ def __le__(self, other): # "Меньше или равно" return self._comparisonOperatorHelper(operator.le, other) ❽ def __gt__(self, other): # "Больше" return self._comparisonOperatorHelper(operator.gt, other) ❾ def __ge__(self, other): # "Больше или равно" return self._comparisonOperatorHelper(operator.ge, other) ❿ Наши dunder-методы сравнения вызывают метод _comparisonOperatorHelper() ❶ и передают соответствующую функцию из модуля operator для параметра operatorFunc . При вызове operatorFunc() вызывается функция, которая была передана в операторе operatorFunc — eq() ❺ , ne() ❻ , lt() ❼ , le() ❽ , gt() ❾ или ge() ❿ — из модуля operator . В противном случае нам пришлось бы продублировать код в _comparisonOperatorHelper() в каждом из шести dunder-методов сравнения. ПРИМЕЧАНИЕ Функции (или методы), получающие другие функции в аргументах (как _compa- risonOperatorHelper()), называются функциями высшего порядка. Теперь наши объекты WizCoin можно сравнивать с другими объектами WizCoin ❷ , с целыми числами и числами с плавающей точкой ❸ , а также со значениями-по- следовательностями из трех чисел, представляющими значения galleons , sickles и knuts ❹ . Чтобы увидеть эту возможность в действии, введите следующий фрагмент в интерактивной оболочке: >>> import wizcoin >>> purse = wizcoin.WizCoin(2, 5, 10) # Создать объект WizCoin. >>> tipJar = wizcoin.WizCoin(0, 0, 37) # Создать другой объект WizCoin. >>> purse.total, tipJar.total # Проверить значение в кнатах. 380 Глава 17.ООП в Python: свойства и dunder-методы (1141, 37) >>> purse > tipJar # Сравнение объектов WizCoin при помощи оператора. True >>> purse < tipJar False >>> purse > 1000 # Сравнение с целым числом. True >>> purse <= 1000 False >>> purse == 1141 True >>> purse == 1141.0 # Сравнение с числом с плавающей точкой. True >>> purse == '1141' # Объект WizCoin не равен никакому строковому значению. False >>> bagOfKnuts = wizcoin.WizCoin(0, 0, 1141) >>> purse == bagOfKnuts True >>> purse == (2, 5, 10) # Возможно сравнение с кортежем из 3 целых чисел. True >>> purse >= [2, 5, 10] # Возможно сравнение со списком из 3 целых чисел. True >>> purse >= ['cat', 'dog'] # Должно привести к ошибке. Traceback (most recent call last): File " File "C:\Users\Al\Desktop\wizcoin.py", line 265, in __ge__ return self._comparisonOperatorHelper(operator.ge, other) File "C:\Users\Al\Desktop\wizcoin.py", line 237, in _ comparisonOperatorHelper otherValue = (other[0] * 17 * 29) + (other[1] * 29) + other[2] IndexError: list index out of range Наш вспомогательный метод вызывает isinstance(other, collections.abc. Sequence) , чтобы проверить, имеет ли other тип данных последовательности, на- пример кортеж или список. Обеспечив возможность сравнения объектов WizCoin с последовательностями, можно писать код вида purse >= [2, 5, 10] для быстрого сравнения. Не существует отраженных dunder-методов сравнения (таких как __req__() или __rne__() ), которые вам было бы необходимо реализовать. Вместо этого __lt__() и __gt__() отражают друг друга, __le__() и __ge__() отражают друг друга, а __eq__() и __ne__() отражают сами себя. Дело в том, что следующие отношения остаются истинными независимо от значений в левой и правой части оператора: purse > [2, 5, 10] то же, что [2, 5, 10] < purse purse >= [2, 5, 10] то же, что [2, 5, 10] <= purse purse == [2, 5, 10] то же, что [2, 5, 10] == purse purse != [2, 5, 10] то же, что [2, 5, 10] != purse Dunder-методы Python 381 СРАВНЕНИЯ ПОСЛЕДОВАТЕЛЬНОСТЕЙ При сравнении двух объектов встроенных типов последовательностей (таких, как строки, списки или кортежи) Python назначает более высокий приори- тет более ранним элементам последовательности. Другими словами, более поздние элементы сравниваются только в том случае, если более ранние элементы имеют равные значения. Например, введите следующий фрагмент в интерактивной оболочке: >>> 'Azriel' < 'Zelda' True >>> (1, 2, 3) > (0, 8888, 9999) True Строка 'Azriel' предшествует строке 'Zelda' (то есть меньше нее), потому что 'A' предшествует 'Z'. Кортеж (1, 2, 3) следует после (0, 8888, 9999) (то есть больше него), потому что 1 больше 0. А теперь введите следующий фрагмент в интерактивной оболочке: >>> 'Azriel' < 'Aaron' False >>> (1, 0, 0) > (1, 0, 9999) False Строка 'Azriel' не предшествует 'Aaron', потому что, хотя элемент 'A' в 'Azriel' равен 'A' в 'Aaron', следующий элемент 'z' в 'Azriel' не предшествует 'a' в 'Aaron'. То же относится к кортежам (1, 0, 0) и (1, 0, 9999): первые два элемента каждого кортежа равны, поэтому третий элемент (0 и 9999 соот- ветственно) определяет, что (1, 0, 0) предшествует (1, 0, 9999). Таким образом, нам приходится принять проектировочное решение относи- тельно класса WizCoin. Должен ли объект WizCoin(0, 0, 9999) предшествовать объекту WizCoin(1, 0, 0) или же следовать после него? Если количество галле- онов более значимо, чем количество сиклей или кнатов, то объект WizCoin(0, 0, 9999) должен предшествовать WizCoin(1, 0, 0). А может, объекты должны сравниваться на основании их значений кнатов? Тогда объект WizCoin(0, 0, 9999) (равно 9999 кнатам) должен следовать после WizCoin(1, 0, 0) (равно 493 кнатам). В программе wizcoin.py я решил использовать значение объекта в кнатах, потому что такое поведение соответствует принципам сравнения объектов WizCoin с целыми числами и числами с плавающей точкой. Вам придется неоднократно принимать подобные решения при проектировании собственных классов. 382 Глава 17.ООП в Python: свойства и dunder-методы После того как вы реализуете dunder-методы сравнения, функция Python sort() автоматически использует их для сортировки объектов. Введите следующий фраг- мент в интерактивной оболочке: >>> import wizcoin >>> oneGalleon = wizcoin.WizCoin(1, 0, 0) # 493 кната. >>> oneSickle = wizcoin.WizCoin(0, 1, 0) # 29 кнатов. >>> oneKnut = wizcoin.WizCoin(0, 0, 1) # 1 кнат. >>> coins = [oneSickle, oneKnut, oneGalleon, 100] >>> coins.sort() # Отсортировать по возрастанию. >>> coins [WizCoin(0, 0, 1), WizCoin(0, 1, 0), 100, WizCoin(1, 0, 0)] В табл. 17.3 приведен полный список доступных dunder-методов сравнения. Таблица 17.3. Dunder-методы сравнения и функции модулей operator Dunder-метод Операция Оператор сравнения Функция модуля operator __eq__() Равно == operator.eq() __ne__() Не равно != operator.ne() __lt__() Меньше < operator.lt() __le__() Меньше или равно <= operator.le() __gt__() Больше > operator.gt() __ge__() Больше или равно >= operator.ge() Реализация этих методов доступна на https://autbor.com/wizcoinfull. Полное опи- сание dunder-методов сравнения доступно в документации Python на https://docs. python.org/3/reference/datamodel.html#object.__lt__. Dunder-методы сравнения позволяют использовать операторы сравнения Python с объектами вашего класса (вместо того, чтобы создавать ваши собственные мето- ды). Если вы создаете методы с именами вида equals() или isGreaterThan() , это не питонический стиль и признак того, что вам стоит использовать dunder-методы сравнения. Итоги В языке Python объектно-ориентированные средства реализуются не так, как в других языках (например, Java или C++). Вместо явного определения Итоги 383 getter- и setter-методов в Python используются свойства, которые позволяют про- верять атрибуты или делать их доступными только для чтения. Python также позволяет перегружать операторы при помощи dunder-методов, имена которых начинаются и заканчиваются двойными символами подчеркивания __ Основные математические операторы перегружаются числовыми и отраженными числовыми dunder-методами. Эти методы дают возможность использовать встро- енные операторы Python для работы с объектами созданных вами классов. Если методы не способны обработать тип данных объекта в другой части оператора, они возвращают встроенное значение NotImplemented . Такие методы создают и возвра- щают новые объекты, тогда как dunder-методы присваивания на месте (которые перегружают расширенные операторы присваивания) изменяют объект на месте. Dunder-методы сравнения не только реализуют шесть операторов сравнения Python для объектов, но и позволяют функции Python sort() сортировать объекты ваших классов. Для реализации этих dunder-методов можно воспользоваться функциями eq() , ne() , lt() , le() , gt() и ge() модуля operator Свойства и dunder-методы помогают писать классы, которые хорошо читаются и по- следовательно работают. Они избавят вас от необходимости писать значительный объем шаблонного кода, что обязательно в других языках (таких как Java). Если вам захочется больше узнать о написании питонического кода, более подробно о некоторых концепциях этой главы (и не только) рассказано в паре докладов Рэймонда Хеттингера (Raymond Hettinger) на PyCon: «Transforming Code into Beautiful, Idiomatic Python» (https://youtu.be/OSGv2VnC0go/) и «Beyond PEP 8 — Best Practices for Beautiful, Intelligible Code» (https://youtu.be/wf-BqAjZb8M/). Об эффективном использовании Python еще можно сказать очень много. В кни- гах «Fluent Python» (O’Reilly Media, 2021) Лучано Рамальо (Luciano Ramalho) 1 и «Effective Python» (Addison-Wesley Professional, 2019) Бретта Слаткина (Brett Slatkin) 2 более подробно рассказывается о синтаксисе и передовых практиках Python. Эти книги стоит прочитать каждому, кто хочет больше знать о Python. 1 Рамальо Л. Python. К вершинам мастерства. 2 Слаткин Б. Секреты Python. 59 рекомендаций по написанию эффективного кода. Эл Свейгарт Python. Чистый код для продолжающих Перевел с английского Е. Матвеев Руководитель дивизиона Ю. Сергиенко Ведущий редактор Е. Строганова Литературный редактор Ю. Леонова Художественный редактор В. Мостипан Корректоры С. Беляева, М. Молчанова Верстка Л. Егорова Изготовлено в России. Изэготовитель: ООО «Прогресс книга». Место нахождения и фактический адрес: 194044, Россия, г. Санкт-Петербург, Б. Сампсониевский пр., д. 29А, пом. 52. Тел.: +78127037373. Дата изготовления: 07.2022. Наименование: книжная продукция. Срок годности: не ограничен. Налоговая льгота — общероссийский классификатор продукции ОК 034-2014, 58.11.12 — Книги печатные профессиональные, технические и научные. Импортер в Беларусь: ООО «ПИТЕР М», 220020, РБ, г. Минск, ул. Тимирязева, д. 121/3, к. 214, тел./факс: 208 80 01. Подписано в печать 27.05.22. Формат 70×100/16. Бумага офсетная. Усл. п. л. 30,960. Тираж 1000. Заказ 0000. |