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, 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
    @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 ++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):

    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 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: