Подборка @pythonetc, сентябрь 2019
Новая подборка советов про 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
:
Допустим, у вас есть пара классов — родительский дочерний, 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)
Классы, не являющиеся ни ковариантными, ни контравариантными, называются инвариантными.