Простой Python — страница 25 из 66

любых объектов, включающих эти методы. Определим класс BabblingBrook, который не имеет никакого отношения к нашим охотнику и его жертвам (наследникам класса Quote), созданным ранее:

>>> class BabblingBrook():

…·····def who(self):

…·········return 'Brook'

…·····def says(self):

…·········return 'Babble'

>>> brook = BabblingBrook()

Теперь запустим методы who() и says() разных объектов, один из которых (brook) совершенно не связан с остальными:

>>> def who_says(obj):

…·····print(obj.who(), 'says', obj.says())

>>> who_says(hunter)

Elmer Fudd says I'm hunting wabbits.

>>> who_says(hunted1)

Bugs Bunny says What's up, doc?

>>> who_says(hunted2)

Daffy Duck says It's rabbit season!

>>> who_says(brook)

Brook says Babble

Такое поведение иногда называется утиной типизацией благодаря старой поговорке «Если нечто выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, утка и есть».

Особые методы

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

Когда вы пишете что-то вроде a = 3 + 8, откуда целочисленные объекты со значениями 3 и 8 узнают, как реализовать операцию +? Кроме того, откуда a знает, как использовать =, чтобы получить результат? Вы можете воспользоваться этими операторами, применяя специальные методы Python (также можно назвать их магическими методами). Вам не нужно быть Гэндальфом, чтобы творить магию, эти методы совсем не сложны.

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

Предположим, у вас есть простой класс Word и вы хотите написать для него метод equals(), который сравнивает два слова, игнорируя регистр. Так и есть, объект класса Word, содержащий значение 'ha', будет считаться равным другому объекту, который содержит значение 'HA'.

В следующем примере показана наша первая попытка, где мы вызываем обычный метод equals(). self.text — это текстовая строка, которую содержит объект класса Word, метод equals() сравнивает ее с текстовой строкой, содержащейся в объекте word2 (другой объект класса Word):

>>> class Word():

…····def __init__(self, text):

…········self.text = text

…····def equals(self, word2):

…········return self.text.lower() == word2.text.lower()

Далее создадим три объекта Word с помощью трех разных текстовых строк:

>>> first = Word('ha')

>>> second = Word('HA')

>>> third = Word('eh')

Когда строки 'ha' и 'HA' сравниваются в нижнем регистре, они должны быть равными:

>>> first.equals(second)

True

Но строка 'eh' не совпадет со строкой 'ha':

>>> first.equals(third)

False

Мы определили метод equals(), который выполняет преобразование строки в нижний регистр и сравнение. Однако было бы здорово, если бы мы могли просто сказать first == second, как в случае встроенных типов Python. Реализуем такую возможность. Мы изменим имя метода equals() на особое имя __eq__() (вы узнаете, зачем я это сделал, через минуту):

>>> class Word():

…·····def __init__(self, text):

…·········self.text = text

…·····def __eq__(self, word2):

…·········return self.text.lower() == word2.text.lower()

Проверим, как это работает:

>>> first = Word('ha')

>>> second = Word('HA')

>>> third = Word('eh')

>>> first == second

True

>>> first == third

False

Магия! Все, что нам было нужно, — указать особое имя метода для проверки на равенство __eq__(). В табл. 6.1 и 6.2 приведены имена самых полезных магических методов.


Таблица 6.1. Магические методы для сравнения
__eq__(self, other)self == other
__ne__(self, other)self!= other
__lt__(self, other)self < other
__gt__(self, other)self > other
__le__(self, other)self <= other
__ge__(self, other)self >= other

Таблица 6.2. Магические методы для вычислений

__add__(self, other)self + other
__sub__(self, other)self — other
__mul__(self, other)self * other
__floordiv__(self, other)self // other
__truediv__(self, other)self / other
__mod__(self, other)self % other
__pow__(self, other)self ** other

Не обязательно использовать математические операторы вроде + (магический метод __add__()) и — (магический метод __sub__()) только для работы с числами. Например, строковые объекты используют + для конкатенации и * для дуплицирования. Существует множество других методов, задокументированных онлайн по адресу http://bit.ly/pydocs-smn. Наиболее распространенные из них представлены в табл. 6.3.


Таблица 6.3. Другие магические методы
__str__(self)str(self)
__repr__(self)repr(self)
__len__(self)len(self)

Вы можете обнаружить, что, помимо __init__(), часто пользуетесь методом __str__(). Он нужен для того, чтобы выводить данные на экран. Этот метод используется методами print(), str() и строками форматирования, о которых вы можете прочитать в главе 7. Интерактивный интерпретатор применяет функцию __repr__() для того, чтобы выводить на экран переменные. Если вы не определите хотя бы один из этих методов, то увидите на экране ваш объект, преобразованный в строку по умолчанию:

>>> first = Word('ha')

>>> first

<__main__.Word object at 0x1006ba3d0>

>>> print(first)

<__main__.Word object at 0x1006ba3d0>

Добавим в класс Word методы __str__() и __repr__(), чтобы он лучше смотрелся:

>>> class Word():

…·····def __init__(self, text):

…·········self.text = text

…·····def __eq__(self, word2):

…·········return self.text.lower() == word2.text.lower()

…·····def __str__(self):

…·········return self.text

…·····def __repr__(self):

…·········return 'Word("'··self.text··'")'

>>> first = Word('ha')

>>> first··········# используется __repr__

Word("ha")

>>> print(first)···# используется __str__

ha

Чтобы узнать о других особых методах, обратитесь к документации Python (http://bit.ly/pydocs-smn).

Композиция

Наследование может сослужить хорошую службу, если вам нужно создать класс-потомок, который ведет себя как родительский класс бо́льшую часть времени (когда потомок является предком). Возможность создавать иерархии наследования довольно заманчива, но иногда композиция или агрегирование (когда x имеет y) имеет больше смысла. Утка является птицей, но имеет хвост. Хвост не похож на утку, он является частью утки. В следующем примере создадим объекты bill и tail и предоставим их новому объекту duck: