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

    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 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, contextlibprovides a decorator asynccontextmanagerthat allows you to define an asynchronous context manager in the same way that it does contextmanager:

    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


    There is no operator in Python ++, it is used instead x += 1. But at the same time, the syntax ++xis valid (and x++no longer).

    The trick is that in Python there is a unary operator "plus", and ++xin fact it is x.__pos__().__pos__(). This can be abused and made ++to work as an increment (but I would not recommend doing this):

    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 object MagicMockallows 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 MagicMockdoes 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

    Also popular now: