Tips and tricks from my Telegram-channel @pythonetc, September 2019



    It is a new selection of tips and tricks about Python and programming from my Telegram-channel @pythonetc.

    Previous publications


    asyncio loop doesn’t have to be run to have tasks. You can create and stop tasks even though the loop is stopped right now. If loop is stopped, some tasks may stay incompleted for good.

    import asyncio
    
    
    async def printer():
        try:
            try:
                while True:
                    print('*')
                    await asyncio.sleep(1)
            except asyncio.CancelledError:
                print('x')
        finally:
            await asyncio.sleep(2)
            print('o')  # 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))  # x printed

    Output:

    *
    *
    ||
    *
    x

    You have to be sure to await all tasks before stopping the loop. In case you don’t you may have some finally blocks being skipped and some context managers not being exited.


    Python lets you overload many different operators and the shift operator is one of them. Here is an example of how to create a function composition using this operator. Here, arrow-like signs show the data-flow direction:

    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


    You can pass arguments to custom metaclass from the class definition. The class notation support keyword arguments: class Klass(Parent, arg='arg'). The metaclass keyword is reserved for setting metaclass, but others are free to use.

    Here is an example of metaclass that creates class without one of the attributes. The name of that attribute is provided in the remove argument:

    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


    Sometimes you want to exhaust a generator, but you don’t care about the values it yields. You do care about some side effect though, it may be an exception, writing to a file, global variable modification etc.

    The convenient and widely used way to do this is list(gen()). However, this code saves all the value into the memory just to discard them immediately after. That can be undesirable.

    If you want to avoid this you can use deque with the limited size instead:

    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')</code>
    To be more semantically precise you better define your own <code>exhaust</code> function:
    
    <source lang="python">
    def exhaust(iterable):
        for _ in iterable:
            pass


    Imagine you have a pair of classes that are a parent and a child, say User and Admin. You also have a function that takes a list of users as an argument. Can you provide a list of admins then? The answer is no: the function can add another user to the list of admins which is invalid and breaks guarantees that the list provides.

    However, you can provide a Sequence of admins since Sequence is read-only. The proper term here is Sequence is covariant on its members type.

    You can define covariant types by providing covariant=True as a TypeVar argument:

    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)

    Contrariwise, the function may require container only to put admins there. Such write-only containers are contravariant on its members type:

    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)

    Classes that are neither covariant nor contravariant are called invariant.
    Mail.ru Group
    1,217.15
    Building the Internet
    Share post

    Comments 1

      0
      Hi, informative and valuable content! My name is Dmitry Kotlyarenko, and I love blogging, email marketing, SEO, Social Media, and Telegram-channels!
      Thanks for the awesome article!

      Only users with full accounts can post comments. Log in, please.