company_banner

Подборка @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:

    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)

    Классы, не являющиеся ни ковариантными, ни контравариантными, называются инвариантными.
    • +29
    • 4,1k
    • 3
    Mail.ru Group
    859,33
    Строим Интернет
    Поделиться публикацией

    Похожие публикации

    Комментарии 3

      +3
      Иногда нужно исчерпать генератор, но при этом вам интересуют не создаваемые им значения, а какие-то побочные эффекты.

      Это уже само по себе является плохой практикой. А предложенное решение с deque к тому же ещё малопонятно для человека, который не знает заранее смысл этого кода.
        0
        Баг?
        А куда должна вести ссылка «Предыдущие подборки»?
        habr.com/ru/search/?q=%5Bpythonetc%5D&target_type=post

        image

        У меня она никуда не ведёт:

        image

        Видимо, там должно быть: habr.com/ru/search/?q=pythonetc

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое