Магические методы для управления поведением объектов в Python
Что такое магические методы
Магические методы (или специальные методы) — это встроенные функции в Python для определения специального поведения внутри класса. Они помогают более разнообразно определять объекты: перегрузка операторов, изменение способа вывода объекта, поведение как контейнера и т.д. Основные характеристики:
- Позволяют определять поведение для базовых операций (арифметика и т.п.)
- Определяются с помощью двойного подчёркивания (
__) - В отличие от обычных функций, не вызываются явно
class Example():
def __init__(self, value):
self.value = value
Классический пример магического метода — __init__(), который вызывается для инициализации при создании объекта. Тем, кто сначала знакомился с объектно-ориентированным программированием на других языках, может показаться неудобным определять внутренние переменные в __init__(). Однако, поняв контекст использования магических методов в Python, можно более чисто воспринимать это как «Python пытается решить эту проблему с помощью встроенных методов».
Магические методы используются гораздо шире, чем кажется. Например, сложение, которое внешне выглядит как оператор +, на самом деле вызывает функцию __add__() внутри объекта.
class Example():
def __init__(self, value):
self.value = value
def __add__(self, x):
return self.value + x.value
ex1 = Example(10)
ex2 = Example(20)
print(ex1 + ex2) # возвращает 30
При необходимости можно реализовать и такое: оператор + используется, но на самом деле не выполняет сложения.
def __add__(self, x):
return "А вы хотели выполнить сложение?"
Включая арифметику, можно определить множество различных магических методов. Помимо приведённых примеров, можно по-новому определять и другие функции.
| Магический метод | Оператор/Функция |
|---|---|
__add__ | + |
__sub__ | - |
__mul__ | * |
__truediv__ | / |
__gt__ | > |
__lt__ | < |
__le__ | <= |
__ge__ | >= |
__eq__ | == |
__new__ | Создание объекта |
__del__ | Удаление объекта |
Два особых момента
Создание внутренней документации
Хотя это не магический метод, полезно знать о __doc__. __doc__ создаёт внутреннюю документацию (docstring) для класса или метода с помощью многострочного комментария сразу под определением.
class Example():
'''This is a doc'''
# ...
Можно записать краткую информацию о классе или методе: для чего он разработан, как его использовать и т.п. Доступ к документации осуществляется через print(Example.__doc__) или метод help(). Более подробные сведения о правилах оформления можно найти в PEP 257.
Просмотр магических методов
При необходимости все магические методы, определённые для конкретного объекта, можно просмотреть с помощью dir(). Например, если проверить магические методы целого числа через dir(10), можно увидеть следующий результат:
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__',
'__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__',
'__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__',
'__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__',
'__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__',
'__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__',
'__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag',
'is_integer', 'numerator', 'real', 'to_bytes']
Благодаря этому можно использовать dir() для определения набора магических методов, поддерживаемых внешним объектом (например, библиотекой), или различать поведение в зависимости от того, поддерживает ли объект определённую операцию, с помощью if '__add__' in dir(obj).
Пример использования магических методов
Может пригодиться для создания упрощённого делегата. Хотя в Python для создания цепочек делегатов проще и рекомендуется добавлять функции в список, меня лично продолжало беспокоить какое-то неясное неудобство, и я нашёл такой способ.
class RaiseEvent():
def __init__(self):
self._handlers = []
def __iadd__(self, function):
self._handlers.append(function)
return self
def __call__(self, *args, **kwargs):
for function in self._handlers:
function(*args, **kwargs)
Определив такой класс, можно добавлять функции с помощью оператора += и использовать привычную форму raise_event += function, похожую на C#. Когда проект разрастается, заранее написанный подобный пользовательский класс, как мне кажется, даёт преимущество в читаемости в долгосрочной перспективе.