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, you need to use a decorator
@classmethod
. 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, the Python data model has two implicit class methods:
__new__
and __init_subclass__
. They work as if they were also decorated with help @classmethod
, although this is not the case ( __new__
creates new instances of the class, but __init_subclass__
is a hook that is called when creating a derived class).classFoo: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 syntax
async with
:import asyncio
classSlow:def__init__(self, delay):
self._delay = delay
asyncdef__aenter__(self):await asyncio.sleep(self._delay / 2)
asyncdef__aexit__(self, *exception):await asyncio.sleep(self._delay / 2)
asyncdefmain():asyncwith 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,
contextlib
provides a decorator asynccontextmanager
that allows you to define an asynchronous context manager in the same way that it does contextmanager
:import asyncio
from contextlib import asynccontextmanager
@asynccontextmanagerasyncdefslow(delay):
half = delay / 2await asyncio.sleep(half)
yieldawait asyncio.sleep(half)
asyncdefmain():asyncwith 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
There is no operator in Python
++
, it is used instead x += 1
. But at the same time, the syntax ++x
is valid (and x++
no longer). The trick is that in Python there is a unary operator "plus", and
++x
in fact it is x.__pos__().__pos__()
. This can be abused and made ++
to work as an increment (but I would not recommend doing this):classNumber:def__init__(self, value):
self._value = value
def__pos__(self):return self._Incrementer(self)
definc(self):
self._value += 1def__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 object
MagicMock
allows you to take 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 = m.a
>>> b = m.b
>>> a is m.a
True>>> m.x() is m.x()
True>>> m.x()
<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:
>>> m.a.b.c.d
<MagicMock name='mock.a.b.c.d' id='139769776473480'>
>>> m.a.b.c.d
<MagicMock name='mock.a.b.c.d' id='139769776473480'>
>>> m.x().y().z()
<MagicMock name='mock.x().y().z()' id='139769776450024'>
>>> m.x(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:
>>> m.a.b.c.d = 42>>> m.a.b.c.d
42>>> m.x.return_value.y.return_value = 13>>> m.x().y()
13
However, this does not work with
m[1][2]
. The fact is that it 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