@Pythonetc September 2019





A new selection of Python tips and programming from my @pythonetc feed.



← Previous collections









In asyncio



a loop does not have to be started to contain tasks. You can create and stop tasks even when the cycle is stopped. If it is stopped, some tasks may remain incomplete.



 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
      
      





Result:



 * * || * 
      
      





Make sure you wait for all tasks to complete before stopping the cycle. If this is not done, then you can skip some finally



blocks and some context managers will not be disabled.









Python allows you to override many operators, including the bitwise shift operator. Here is an example of creating a composition of functions using this operator. The arrows indicate the direction of data transfer:



 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
      
      











When defining a class, you can pass arguments to its metaclass. The class



notation supports keywords as arguments: class Klass(Parent, arg='arg')



. The metaclass keyword is reserved for choosing a metaclass, and you can use others as you wish.



Here is an example of a metaclass that creates a class without one of the attributes. The attribute name 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 need to exhaust the generator, but you are not interested in the values ​​it creates, but in some side effects. For example, an exception, writing to a file, changing a global variable, etc.



There is a convenient and popular way to do this is list(gen())



. However, this method saves all values ​​to memory, and then immediately deletes them. This may be redundant. If you want to avoid this behavior, you can use deque



with a size limit:



 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')
      
      





For the sake of semantic accuracy, you can define your own exhaust



function:



 def exhaust(iterable): for _ in iterable: pass
      
      











Suppose you have a couple of classes - the parent child, User



and Admin



. And you also have a function that takes a list of users as an argument. Can you provide a list of admins? No: the function can add another user to the list of admins, which is erroneous and violates the guarantees provided by the list.



However, you can provide a Sequence



type because it is read-only. More precisely, in this case, Sequence



is covariant in type of participants.



You can define covariant types by providing covariant=True



as an argument to 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)
      
      





Conversely, a function may require a container only to place admins in it. Such containers, which are available for recording only, are contravariant by type of participants:



 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 .



All Articles