📜 ⬆️ ⬇️

Compilation @pythonetc, January 2019



This is the eighth collection of tips about Python and programming from my author’s @pythonetc channel.

Previous selections:



Two implicit class methods


To create a class method, use the @classmethod decorator. Then this method can be called directly from the class, not from its instances, and it will take the class as the first argument (it is usually called cls , not self ).

However, there are two implicit class methods in the Python data model: __new__ and __init_subclass__ . They work as if they were also decorated with @classmethod , although this is not the case ( __new__ creates new instances of the class, and __init_subclass__ is a hook that is called when creating a derived class).

 class Foo: def __new__(cls, *args, **kwargs): print(cls) return super().__new__( cls, *args, **kwargs ) Foo() # <class '__main__.Foo'> 

Asynchronous context managers


If you want the context manager to suspend coruntine when entering or exiting the context, then use asynchronous managers. Then instead of calling m.__enter__() and m.__exit__() Python will do await on m.__aenter__() and m.__aexit__() respectively.

Asynchronous context managers need to be used with the syntax async with :

 import asyncio class Slow: def __init__(self, delay): self._delay = delay async def __aenter__(self): await asyncio.sleep(self._delay / 2) async def __aexit__(self, *exception): await asyncio.sleep(self._delay / 2) async def main(): async with Slow(1): print('slow') loop = asyncio.get_event_loop() loop.run_until_complete(main()) 

We define an asynchronous context manager.


Starting in Python 3.7, the contextlib provides the contextlib decorator, which allows you to define an asynchronous context manager in the same way that the contextmanager does:

 import asyncio from contextlib import asynccontextmanager @asynccontextmanager async def slow(delay): half = delay / 2 await asyncio.sleep(half) yield await asyncio.sleep(half) async def main(): async with slow(1): print('slow') loop = asyncio.get_event_loop() loop.run_until_complete(main()) 

In older versions of the language, you can use @asyncio_extras.async_contextmanager .

Unary plus operator


In Python, there is no ++ operator, x += 1 used instead. But at the same time, the syntax ++x is valid (and x++ is no longer).

The trick is that there is a unary plus operator in Python, and ++x is actually x.__pos__().__pos__() . This can be abused and make ++ work as an increment (but I would not recommend it):

 class Number: def __init__(self, value): self._value = value def __pos__(self): return self._Incrementer(self) def inc(self): self._value += 1 def __str__(self): return str(self._value) class _Incrementer: def __init__(self, number): self._number = number def __pos__(self): self._number.inc() x = Number(4) print(x) # 4 ++x print(x) # 5 

MagicMock object


The MagicMock object allows MagicMock to take in any attribute and call any method. With this access method, a new stub (mock) is returned. Moreover, you get the same stub object if you access the same attribute (or call the same method):

 >>> from unittest.mock import MagicMock >>> m = MagicMock() >>> a = ma >>> b = mb >>> a is ma True >>> mx() is mx() True >>> mx() <MagicMock name='mock.x()' id='139769776427752'> 

Obviously, this code will work with sequential access to attributes at any depth. In this case, the arguments of the methods are ignored:

 >>> mabcd <MagicMock name='mock.abcd' id='139769776473480'> >>> mabcd <MagicMock name='mock.abcd' id='139769776473480'> >>> mx().y().z() <MagicMock name='mock.x().y().z()' id='139769776450024'> >>> mx(1).y(1).z(1) <MagicMock name='mock.x().y().z()' id='139769776450024'> 

And if you set some attribute value, then the stub will no longer return:

 >>> mabcd = 42 >>> mabcd 42 >>> mxreturn_value.y.return_value = 13 >>> mx().y() 13 

However, this does not work with m[1][2] . The fact is that MagicMock does not handle the call to the element, it is just a method call:

 >>> m[1][2] = 3 >>> m[1][2] <MagicMock name='mock.__getitem__().__getitem__()' id='139769776049848'> >>> m.__getitem__.return_value.__getitem__.return_value = 50 >>> m[1][2] 50 

Source: https://habr.com/ru/post/438778/