
Это восьмая подборка советов про Python и программирование из моего авторского канала @pythonetc.
Предыдущие подборки:
Два неявных метода классов
Для создания метода класса нужно использовать декоратор
@classmethod. Потом этот метод можно вызывать напрямую из класса, а не из его экземпляров, и он будет принимать класс в качестве первого аргумента (его обычно вызывают cls, а не self).Однако в модели данных Python есть два неявных метода класса:
__new__ и __init_subclass__. Они работают так, словно тоже декорированы с помощью @classmethod, хотя это и не так (__new__ создаёт новые экземпляры класса, а __init_subclass__ является хуком, который вызывается при создании производного класса).class Foo: def __new__(cls, *args, **kwargs): print(cls) return super().__new__( cls, *args, **kwargs ) Foo() # <class '__main__.Foo'>
Асинхронные менеджеры контекста
Если вы хотите, чтобы менеджер контекста приостанавливал корутину при входе или выходе из контекста, то пользуйтесь асинхронными менеджерами. Тогда вместо вызова
m.__enter__() и m.__exit__() Python будет делать await на m.__aenter__() и m.__aexit__() соответственно.Асинхронные менеджеры контекста нужно применять с синтаксисом
async with:import asyncio class Slow: def __init__(self, delay): self._delay = delay async def __aenter__(self): await asyncio.sleep(self._delay / 2) async def __aexit__(self, *exception): await asyncio.sleep(self._delay / 2) async def main(): async with Slow(1): print('slow') loop = asyncio.get_event_loop() loop.run_until_complete(main())
Определяем асинхронный менеджер контекста
Начиная с Python 3.7
contextlib предоставляет декоратор asynccontextmanager, который позволяет определять асинхронный менеджер контекста таким же образом, как это делает contextmanager:import asyncio from contextlib import asynccontextmanager @asynccontextmanager async def slow(delay): half = delay / 2 await asyncio.sleep(half) yield await asyncio.sleep(half) async def main(): async with slow(1): print('slow') loop = asyncio.get_event_loop() loop.run_until_complete(main())
В более старых версиях языка вы можете использовать
@asyncio_extras.async_contextmanager.Унарный оператор «плюс»
В Python нет оператора
++, вместо него используется x += 1. Но при этом синтаксис ++x является валидным (а x++ — уже нет).Хитрость в том, что в Python есть унарный оператор «плюс», и
++x на самом деле является x.__pos__().__pos__(). Этим можно злоупотребить и заставить ++ работать как инкрементирование (но я бы не рекомендовал так делать):class Number: def __init__(self, value): self._value = value def __pos__(self): return self._Incrementer(self) def inc(self): self._value += 1 def __str__(self): return str(self._value) class _Incrementer: def __init__(self, number): self._number = number def __pos__(self): self._number.inc() x = Number(4) print(x) # 4 ++x print(x) # 5
Объект MagicMock
Объект
MagicMock позволяет брать у себя любой атрибут и вызывать любой метод. При таком способе доступа возвращается новая заглушка (mock). Более того, вы получаете такой же объект-заглушку, если обращаетесь к тому же атрибуту (или вызываете тот же метод):>>> from unittest.mock import MagicMock >>> m = MagicMock() >>> a = m.a >>> b = m.b >>> a is m.a True >>> m.x() is m.x() True >>> m.x() <MagicMock name='mock.x()' id='139769776427752'>
Очевидно, что этот код будет работать с последовательным обращением к атрибутам на любую глубину. При этом аргументы методов игнорируются:
>>> m.a.b.c.d <MagicMock name='mock.a.b.c.d' id='139769776473480'> >>> m.a.b.c.d <MagicMock name='mock.a.b.c.d' id='139769776473480'> >>> m.x().y().z() <MagicMock name='mock.x().y().z()' id='139769776450024'> >>> m.x(1).y(1).z(1) <MagicMock name='mock.x().y().z()' id='139769776450024'>
А если вы зададите какому-нибудь атрибуту значение, то заглушка больше не будет возвращаться:
>>> m.a.b.c.d = 42 >>> m.a.b.c.d 42 >>> m.x.return_value.y.return_value = 13 >>> m.x().y() 13
Однако это не работает с
m[1][2]. Дело в том, что MagicMock не обрабатывает обращение к элементу, это просто вызов метода:>>> m[1][2] = 3 >>> m[1][2] <MagicMock name='mock.__getitem__().__getitem__()' id='139769776049848'> >>> m.__getitem__.return_value.__getitem__.return_value = 50 >>> m[1][2] 50
