Шпаргалка по протоколам
Утиная типизация и протоколы
Утиная типизация — это неявная реализация интерфейса через реализацию набора специальных методов. Объект, который их реализует, имитирует работу определенного типа без явного наследования интерфейса.
Python является языком протоколов. Суть протоколов в том, что помимо специальных методов, реализующих интерфейс они включают другие виды взаимодействий. Например, генерация специальных исключений.
Протоколы
Протокол — это способ реализовать неявный интерфейс без дополнительных средств.
Протокол сопоставления (Comparable)
- Если метод eq() не переопределен, он возвращает
'id(self) == id(other)', что то же самое, что и'self is other'. - Это означает, что по умолчанию все объекты не идентичны.
- Метод сравнения вызывается только у левого объекта. Но если он вернет NotImplemented, то метод сравнения вызывается у правого объекта.
__ne__()автоматически работает на любом объекте, для которого определен метод__eq__().
class MyComparable:
def __init__(self, a):
self.a = a
def __eq__(self, other):
if isinstance(other, type(self)):
return self.a == other.a
return NotImplemented
Хэширование объектов (Hashable)
- Хешируемому объекту нужны
__hash()__и__eq()__, и для объекта значение его хэша никогда не должно меняться - Хешируемые объекты считающиеся равными должны иметь одинаковые значения хеша. Например,
__hash()__которая возвращает'id(self)'работать не будет - Поэтому Python автоматически делает классы не хешируемыми если будет реализован только метод
__eq__()
class MyHashable:
def __init__(self, a):
self._a = a
@property
def a(self):
return self._a
def __eq__(self, other):
if isinstance(other, type(self)):
return self.a == other.a
return NotImplemented
def __hash__(self):
return hash(self.a)
Сортируемые (Sortable)
- При использовании декоратора 'total_ordering' вам нужно предоставить только
__eq__()и один из специальных методов__lt__(),__gt__(),__le__()или__ge__(), остальное будет сгенерировано автоматически - Функции sorted() и min() требуют только метода
__lt__(), а max() - только__gt__(). Однако лучше определить их все, чтобы не возникало путаницы в других контекстах - При сравнении двух списков, строк или dataclass значения сравниваются по порядку, пока не будет найдена пара неравных значений. Результат сравнения возвращается. Если сравниваются последовательности, то более короткая считается меньшей в случае, если все значения равны.
from functools import total_ordering
@total_ordering
class MySortable:
def __init__(self, a):
self.a = a
def __eq__(self, other):
if isinstance(other, type(self)):
return self.a == other.a
return NotImplemented
def __lt__(self, other):
if isinstance(other, type(self)):
return self.a < other.a
return NotImplemented
Итератор (Iterator)
- Любой объект, у которого есть методы
__next__()и__iter__()являются итераторами - Метод
__next__()должен вернуть следующий элемент или вызвать StopIteration - Метод
__iter__()должен возвращаться 'self'
class Counter:
def __init__(self):
self.i = 0
def __next__(self):
self.i += 1
return self.i
def __iter__(self):
return self
>>> counter = Counter()
>>> next(counter), next(counter), next(counter)
(1, 2, 3)
В Python много разных типов объектов итераторов:
- Последовательности, которые возвращаются функцией iter(), например list_iterator и set_iterator
- Объекты, которые возвращаются функциями модуля itertools, например count, repeat и cycle
- Генераторы, создаваемые функциями генераторами (с ключевым слово yield) и генераторными выражениями
- Файловыми объектами, которые получаются функцией open() и другими
Вызываемый тип (Callable)
- Все функции и классы, у которых есть метод
__call__()считаются вызываемыми (callable) - Когда эта шпаргалка использует
'<function>'в качестве аргумента, это означает'<callable>'
class Counter:
def __init__(self):
self.i = 0
def __call__(self):
self.i += 1
return self.i
>>> counter = Counter()
>>> counter(), counter(), counter()
(1, 2, 3)
Контекстный менеджер (Context Manager)
- Метод
__enter__()должен заблокировать необходимые ресурсы и, возможно, вернуть объект - Метод
__exit__()должен освободить заблокированные ресурсы - Любое исключение, которое случается внутри блока with передается методу
__exit__() - Если исключение должно быть подавлено, то метод
__exit__()должен вернуть значение True
class MyOpen:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename)
return self.file
def __exit__(self, exc_type, exception, traceback):
self.file.close()
>>> with open('test.txt', 'w') as file:
... file.write('Hello World!')
>>> with MyOpen('test.txt') as file:
... print(file.read())
Hello World!
Утиная типизация для итераторов
Итерируемый (Iterable)
- Единственный необходимый метод -
__iter__(). Он должен возвращать итератор элементов объекта __contains__()автоматически работает на любом объекте, для которого определен метод__iter__()
class MyIterable:
def __init__(self, a):
self.a = a
def __iter__(self):
return iter(self.a)
def __contains__(self, el):
return el in self.a
>>> obj = MyIterable([1, 2, 3])
>>> [el for el in obj]
[1, 2, 3]
>>> 1 in obj
True
Коллекции (Collection)
- Обязательные методы
__iter__()и__len__() - Если вам в документации попадались описания функций или методов, которые получают на вход коллекции (термин collection) то может возникнуть впечатление, что они так же могут работать и с итерируемыми объектами. Но есть случаи, даже в стандартной библиотеке, когда это не так. Поэтому эти термины не равнозначны.
class MyCollection:
def __init__(self, a):
self.a = a
def __iter__(self):
return iter(self.a)
def __contains__(self, el):
return el in self.a
def __len__(self):
return len(self.a)
Последовательности (Sequence)
- Обязательные методы
__len__()и__getitem__() - Метод
__getitem__()должен вернуть элемент под нужным индексом или породить IndexError - Методы
__iter__()и__contains__()автоматом работают с любым объектом, у которого определен 'getitem()' - Функция reversed() автоматически работает с любым объектом, у которого есть методы
__len__()и__getitem__()
class MySequence:
def __init__(self, a):
self.a = a
def __iter__(self):
return iter(self.a)
def __contains__(self, el):
return el in self.a
def __len__(self):
return len(self.a)
def __getitem__(self, i):
return self.a[i]
def __reversed__(self):
return reversed(self.a)
Таблица методов
Таблица необходимых и автоматически доступных специальных методов:
| Iterable | Collection | Sequence | |
|---|---|---|---|
__iter__() |
REQ | REQ | Yes |
__contains__() |
Yes | Yes | Yes |
__len__() |
REQ | REQ | |
__getitem__() |
REQ | ||
__reversed__() |
Yes | ||
__index__() |
|||
__count__() |