company_banner

Tips and tricks from my Telegram-channel @pythonetc, January 2019



    It is new selection of tips and tricks about Python and programming from my Telegram-channel @pythonetc.

    Previous publications:



    Two implicit class methods


    To create a class method, you should use the @classmethod decorator. This method can be called from the class directly, not from its instances, and accepts the class as a first argument (usually called cls, not self).

    However, there are two implicit class methods in Python data model: __new__ and __init_subclass__. They work exactly as though they are decorated with @classmethod except they aren't. (__new__ creates new instances of a class, __init_subclass__ is a hook that is called when a derived class is created.)

    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 a context manager to suspend coroutine on entering or exiting context, you should use asynchronous context managers. Instead of calling m.__enter__() and m.__exit__() Python does await m.__aenter__() and await m.__aexit__() respectively.

    Asynchronous context managers should be used with async with syntax:

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

    Defining asynchronous context manager


    Since Python 3.7, contextlib provides the asynccontextmanager decorator which allow you to define asynchronous context manager in the exact same manner as 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())

    For older versions, you could use @asyncio_extras.async_contextmanager.

    Unary plus operator


    There is no ++ operator in Python, x += 1 is used instead. However, even ++x is still a valid syntax (but x++ is not).

    The catch is Python has unary plus operator, and ++x is actually x.__pos__().__pos__(). We can abuse this fact and make ++ work as increment (not recommended though):

    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

    The MagicMock object


    The MagicMock object allows you to get any attribute from it or call any method. New mock will be returned upon such access. What is more, you get the same mock object if 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'>

    This obviously will work with sequential attribute access of any deep. Method arguments are ignored though:

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

    Once you set a value for any attribute, it doesn't return mock anymore:

    >>> 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, it doesn't work with m[1][2]. The reason is, the item access is not treated specially by MagicMock, it's merely 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
    Mail.ru Group
    1123.40
    Строим Интернет
    Share post

    Comments 0

    Only users with full accounts can post comments. Log in, please.