Создание вызываемого типа и менеджеры контекста
Callable
Вам уже скорее всего попадалось упоминание, что функции тоже являются объектами. Если функция является объектом, то значит объект реализует какой-то протокол, а значит для него должен существовать магический метод. Плюс к этому, по наличию этого метода можно определять может ли объект быть вызван.
Если сильно упростить, то, когда к объекту добавляются круглые скобки, то внутри происходит обращение к методу __call__. То есть obj() на самом деле obj.__call__(). То есть добавляя этот метод можно превратить объект в вызываемый (callable).
Но ведь когда создаем объект мы тоже "вызываем" класс. Как различить эти случаи? Вот пример, который объясняет разницу:
# создание экземпляра класса
class Foo:
def __init__(self, a, b, c):
# ...
x = Foo(1, 2, 3) # это вызов конструктора
# создание вызываемого объекта
class Foo:
def __call__(self, a, b, c):
# ...
x = Foo()
x(1, 2, 3) # а это вызов метода __call__
То есть фактически, добавление метода __call__ позволяет "вызывать" экземпляр класса, а __init__ позволяет создавать экземпляр класса.
Менеджеры контекста
Один из самых важных протоколов для понимания. Менеджеры контекста настолько упростили жизнь программистов, что используются повсеместно. И, конечно, надо уметь их реализовывать самостоятельно.
Напомню, что такое менеджеры контекста и как они выглядят в коде:
with open('name.txt', 'r+') as f:
f.read()
Такая запись позволяет не беспокоиться о закрытии файла или закрытия транзакции в базе данных. Во всех тех случаях, когда надо обязательно не забыть выполнить завершающее действие.
Для реализации контекстного менеджера надо реализовать два метода:
__enter__(self)этот метод вызывается в начале блока with и возвращает объект, который потом будет доступен внутри блока в переменной, которая стоит после as. Его асинхронная версия__aenter__(self).__exit__(self, exception_type, exception_value, traceback). Автоматически вызывается после завершения блока. Если при выполнении возникли исключения, то они тоже станут доступны в качестве параметров при вызове метода. В зависимости от необходимости исключение можно перехватывать и обрабатывать в самом методе, тогда функция должна вернуть True. Или отдавать исключение дальше, тогда его надо вызвать еще раз с помощью raise. Асинхронная версия__aexit__.