company_banner

Подборка @pythonetc, январь 2019



    Это восьмая подборка советов про Python и программирование из моего авторского канала @pythonetc.

    Предыдущие подборки:



    Два неявных метода классов


    Для создания метода класса нужно использовать декоратор @classmethod. Потом этот метод можно вызывать напрямую из класса, а не из его экземпляров, и он будет принимать класс в качестве первого аргумента (его обычно вызывают cls, а не self).

    Однако в модели данных Python есть два неявных метода класса: __new__ и __init_subclass__. Они работают так, словно тоже декорированы с помощью @classmethod, хотя это и не так (__new__ создаёт новые экземпляры класса, а __init_subclass__ является хуком, который вызывается при создании производного класса).

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

    Асинхронные менеджеры контекста


    Если вы хотите, чтобы менеджер контекста приостанавливал корутину при входе или выходе из контекста, то пользуйтесь асинхронными менеджерами. Тогда вместо вызова m.__enter__() и m.__exit__() Python будет делать await на m.__aenter__() и m.__aexit__() соответственно.

    Асинхронные менеджеры контекста нужно применять с синтаксисом 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())

    Определяем асинхронный менеджер контекста


    Начиная с Python 3.7 contextlib предоставляет декоратор asynccontextmanager, который позволяет определять асинхронный менеджер контекста таким же образом, как это делает 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())

    В более старых версиях языка вы можете использовать @asyncio_extras.async_contextmanager.

    Унарный оператор «плюс»


    В Python нет оператора ++, вместо него используется x += 1. Но при этом синтаксис ++x является валидным (а x++ — уже нет).

    Хитрость в том, что в Python есть унарный оператор «плюс», и ++x на самом деле является x.__pos__().__pos__(). Этим можно злоупотребить и заставить ++ работать как инкрементирование (но я бы не рекомендовал так делать):

    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


    Объект MagicMock позволяет брать у себя любой атрибут и вызывать любой метод. При таком способе доступа возвращается новая заглушка (mock). Более того, вы получаете такой же объект-заглушку, если обращаетесь к тому же атрибуту (или вызываете тот же метод):

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

    Очевидно, что этот код будет работать с последовательным обращением к атрибутам на любую глубину. При этом аргументы методов игнорируются:

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

    А если вы зададите какому-нибудь атрибуту значение, то заглушка больше не будет возвращаться:

    >>> m.a.b.c.d = 42
    >>> m.a.b.c.d
    42
    >>> m.x.return_value.y.return_value = 13
    >>> m.x().y()
    13

    Однако это не работает с m[1][2]. Дело в том, что MagicMock не обрабатывает обращение к элементу, это просто вызов метода:

    >>> 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
    867,54
    Строим Интернет
    Поделиться публикацией

    Похожие публикации

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое