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