
Новая подборка советов про Python и программирование из моего авторск��го канала @pythonetc.
← Предыдущие подборки

В
asyncio цикл (loop) не обязан быть запущенным, чтобы содержать задачи (tasks). Вы можете создавать и останавливать задачи даже при остановленном цикле. Если он остановлен, некоторые задачи могут так и остаться незавершёнными.import asyncio async def printer(): try: try: while True: print('*') await asyncio.sleep(1) except asyncio.CancelledError: print('х') finally: await asyncio.sleep(2) print('о') # never happens loop = asyncio.get_event_loop() run = loop.run_until_complete task = loop.create_task(printer()) run(asyncio.sleep(1)) # printer works here print('||') run(asyncio.sleep(1)) # printer works here task.cancel() # nothing happens run(asyncio.sleep(1)) # х printed
Результат:
* * || * х
Удостоверьтесь, что вы дождались завершения всех задач, прежде чем остановить цикл. Если этого не сделать, то вы можете пропустить какие-то блоки
finally и некоторые контекстные менеджеры не будут отключены.
Python позволяет переопределять многие операторы, в том числе оператор побитового сдвига. Вот пример создания композиции функций с помощью этого оператор. Стрелки обозначают направление передачи данных:
from collections import deque from math import sqrt class Compose: def __init__(self): self._functions = deque() def __call__(self, *args, **kwargs): result = None for f in self._functions: result = f(*args, **kwargs) args = [result] kwargs = dict() return result def __rshift__(self, f): self._functions.append(f) return self def __lshift__(self, f): self._functions.appendleft(f) return self compose = Compose sqrt_abs = (compose() << sqrt << abs) sqrt_abs2 = (compose() >> abs >> sqrt) print(sqrt_abs(-4)) # 2.0 print(sqrt_abs2(-4)) # 2.0

При определении класса можно передавать аргументы в его метакласс. Нотация
class поддерживает ключевые слова в качестве аргументов: class Klass(Parent, arg='arg'). Ключевое слово metaclass зарезервировано для выбора метакласса, а другие вы можете использовать по своему усмотрению.Вот пример метакласса, создающего класс без одного из атрибутов. Название атрибута предоставлено в аргументе
remove:class FilterMeta(type): def __new__(mcs, name, bases, namespace, remove=None, **kwargs): if remove is not None and remove in namespace: del namespace[remove] return super().__new__(mcs, name, bases, namespace) class A(metaclass=FilterMeta, remove='half'): def half(x): return x // 2 half_of_4 = half(4) half_of_100 = half(100) a = A() print(a.half_of_4) # 2 print(a.half_of_100) # 50 a.half # AttributeError

Иногда нужно исчерпать генератор, но при этом вас интересуют не создаваемые им значения, а какие-то побочные эффекты. Например, исключение, запись в файл, изменение глобальной переменной и т.д.
Для этого есть удобный и популярный способ
list(gen()). Однако этот способ сохраняет все значения в память, а потом сразу их удаляет. Это может быть излишним. Если хотите избежать такого поведения, можете использовать deque с ограничением размера:from collections import deque def inversed(nums): for num in nums: yield 1 / num try: deque(inversed([1, 2, 0]), maxlen=0) except ZeroDivisionError: print('E')
Ради семантической точности можно определить собственную функцию
exhaust:def exhaust(iterable): for _ in iterable: pass

Допустим, у вас есть пара классов — родительский дочерний,
User и Admin. И ещё у вас есть функция, берущая в качестве аргумента список пользователей. Вы можете предоставить список админов? Нет: функция может добавить ещё одного пользователя в список админов, который является ошибочным и нарушает предоставляемые списком гарантии.Однако вы можете предоставить тип
Sequence, потому что он доступен только для чтения. Точнее, в данном случае Sequence является ковариантным по типу участников.Можно определять ковариантные типы, предоставляя
covariant=True в качестве аргумента для TypeVar:from typing import TypeVar, Generic T = TypeVar('T', covariant=True) class Holder(Generic[T]): def __init__(self, var: T): self._var: T = var def get(self) -> T: return self._var class User: pass class Admin(User): pass def print_user_from_holder(holder: Holder[User]) -> None: print(holder.get()) h: Holder[Admin] = Holder(Admin()) print_user_from_holder(h)
И напротив, функции может потребоваться контейнер лишь для того, чтобы поместить в него админов. Такие контейнеры, доступные только для записи, являются контравариантными по типу участников:
from typing import TypeVar, Generic T = TypeVar('T', contravariant=True) class Holder(Generic[T]): def __init__(self, var: T): self._var: T = var def change(self, x: T): self._var = x class User: pass class Admin(User): pass def place_admin_to_holder(holder: Holder[Admin]) -> None: holder.change(Admin()) h: Holder[User] = Holder(User()) place_admin_to_holder(h)
Классы, не являющиеся ни ковариантными, ни контравариантными, называются инвариантными.
