04.02.2019       Выпуск 268 (04.02.2019 - 10.02.2019)       Статьи

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

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

Читать>>




Экспериментальная функция:

Ниже вы видите текст статьи по ссылке. По нему можно быстро понять ссылка достойна прочтения или нет

Просим обратить внимание, что текст по ссылке и здесь может не совпадать.

Это восьмая подборка советов про 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


Лучшая Python рассылка




Разместим вашу рекламу

Пиши: mail@pythondigest.ru

Нашли опечатку?

Выделите фрагмент и отправьте нажатием Ctrl+Enter.

Система Orphus