С. В. Вабищевич инженерпрограммист компании ооо ск хайникс мемори солюшнс Восточная Европа
Скачать 1.28 Mb.
|
assert – statement, позволяющий проверить на истинность некоторое выражение: >>> def calculate_binomial_mean(n, p): >>> assert n > 0, ‘number of experiments must be positive’ >>> assert 0 <= p <= 1, ‘probability must be in [0; 1]’ >>> return n * p Важно! Скобки в assert имеют смысл проверки кортежа на пу- стоту. 1 https ://pypi.python.org/pypi/line_profiler 2 https ://pypi.python.org/pypi/memory_profiler/ 3 https ://pypi.python.org/pypi/snakeviz 4 https ://habrahabr.ru/company/mailru/blog/202832 78 ЮНИТ- ТЕСТЫ Общая идея юнит- тестов: – разбить код на независимые части (юниты); – тестировать каждую часть отдельно. Преимущества: – нужно меньше тестов; – проще отлаживать. Недостатки: – нужны тесты, проверяющие взаимодействие юнитов. БИБЛИОТЕКА DOCTEST Библиотека doctest (Python 2.x) 1 , (Python 3.x) 2 позволяет расположить тест непосредственно в документации, чтобы и пока- зать, и проверить, как работает функция. Недостаток подхода в его сложности: проверка некорректных входных данных или результатов сложных типов трудоемка. >>> def factorial(n): >>> “””Return the factorial of n, an exact integer >= 0. … … >>> factorial(5) … 120 … “”” >>> pass # implementation is here >>> >>> if __name__ == ‘__main__’: >>> import doctest >>> doctest.testmod() БИБЛИОТЕКА UNITTEST Тесты можно писать, используя библиотеку unittest (Python 2.x) 3 , (Python 3.x) 4 , что позволяет, в частности, группиро- 1 https ://docs.python.org/2.7/library/doctest.html 2 https ://docs.python.org/3.7/library/doctest.html 3 https ://docs.python.org/2.7/library/unittest.html 4 https ://docs.python.org/3.7/library/unittest.html 79 вать тесты в test cases, а также использовать множество удобных проверок. >>> import unittest >>> >>> class TestFactorial(unittest.TestCase): >>> def test_simple(self): >>> self.assertEqual(factorial(5), 120) >>> >>> if __name__ == ‘__main__’: >>> unittest.main() Возможности unittest: – проверка различных типов и видов возвращаемых значе- ний: assertTrue, assertIsNone, assertAlmostEqual, assertRaisesRegexp и др.; – возможность подготовить тестирование: методы setup ( setUpClass) и teardown (tearDownClass) – вызываются перед и по- сле каждого запуска теста (класса test case), создавая и удаляя ис- пользуемые объекты; – возможность пропустить тест по некоторому условию (см. unittest.SkipTest). Также есть библиотека pytest, в которой все проверки можно проводить с помощью assert statement’ов, и библиотека nose, объе- диняющая все виды тестов. ПОДМЕНА ОБЪЕКТОВ При помощи библиотеки mock 1 (в стандартной библио- теке с Python 3.3, см. примеры) 2 можно подменить любой объект, будь то поток ввода- вывода stdout, некоторый модуль, класс, атри- бут, свой ство, метод. >>> from io import StringIO >>> >>> class InterfaceTestCase(unittest.TestCase): >>> def setUp(self): >>> self._stdout_mock = self._setup_stdout_mock() 1 https ://docs.python.org/3.7/library/unittest.mock.html 2 https ://docs.python.org/3.7/library/unittest.mock‑ examples.html 80 >>> >>> def _setup_stdout_mock(self): >>> patcher = mock.patch(‘sys.stdout’, new=StringIO()) >>> patcher.start() >>> self.addCleanup(patcher.stop) >>> return patcher.new 3.7. ДЕКОРАТОРЫ Декораторы (PEP-318) 1 : – выполняют некоторое дополнительное действие при вызове или создании функции; – модифицируют функцию после создания; – могут принимать аргументы; – упрощают написание кода. Рассмотрим пример декоратора – функции, которая при вызо- ве декорированной функции проверяет, возвращенное ей значение имеет тип float. Функцию- декоратор назовем check_return_type_float. >>> @check_return_type_float >>> def g(): >>> return ‘not a float’ Эквивалентной записью будет следующая: >>> def g(): >>> return ‘not a float’ >>> >>> g = check_return_type_float(g) Декоратор реализован как функция, которая возвращает другую функцию – wrapper: >>> def check_return_type_float(f): >>> def wrapper(*args, **kwargs): >>> result = f(*args, **kwargs) >>> assert isinstance(result, float) >>> return result >>> return wrapper 1 https ://www.python.org/dev/peps/pep‑0318 81 Поскольку декоратор возвращает другую функцию, в примере check_return_type_float у переменной f будет имя ‘wrapper’. Для того чтобы метаданные (имя, документация) были кор- ректными, внутренней функции (wrapper’у) добавляют декоратор functools.wraps: >>> def check_return_type_float(f): >>> @functools.wraps(f) >>> def wrapper(*args, **kwargs): >>> # some code >>> return wrapper Замечание. В Python 3 декорировать можно не только функции, но и классы (PEP-3129) 1 РЕАЛИЗАЦИЯ ДЕКОРАТОРА С ПОМОЩЬЮ КЛАССА >>> class FloatTypeChecker(object): >>> def __init__(self, f): >>> self.f = f >>> >>> def __call__(self, *args, **kwargs): >>> result = self.f(*args, **kwargs) >>> assert isinstance(result, float) >>> return result >>> >>> check_return_type_float = FloatTypeChecker Вопрос: Как применить здесь functools.wraps? Замечание. Добавлять данный декоратор желательно везде. Это и хороший стиль кода, и так остается возможность узнать имя вы- званной функции run- time. ДЕКОРАТОРЫ С ПАРАМЕТРАМИ Для создания более общего декоратора логично добавить ему возможность принимать параметры. >>> def check_return_type(type_): >>> def wrapper(f): 1 https ://www.python.org/dev/peps/pep‑3129 82 >>> @functools.wraps(f) >>> def wrapped(*args, **kwargs): >>> result = f(*args, **kwargs) >>> assert isinstance(result, type_) >>> return result >>> return wrapped >>> return wrapper >>> >>> @check_return_type(float) >>> def g(): >>> return ‘not a float’ НЕСКОЛЬКО ДЕКОРАТОРОВ Эквивалентной записью будет следующая: >>> def g(): >>> return ‘not a float’ >>> >>> g = check_return_type(float)(g) Допустимо применять несколько декораторов – один над другим. >>> @decorator2 >>> @decorator1 >>> def f(): >>> pass Эквивалентная запись применения декораторов к функции f имеет вид: >>> f = decorator2(decorator1(f)) ПАРАМЕТРИЗОВАННЫЙ ДЕКОРАТОР- КЛАСС Декораторы с параметрами можно реализовать как класс. В таком случае параметры будут сохраняться в методе __init__, а де- корированную функцию следует возвращать в __call__. >>> class FloatTypeChecker(object): >>> def __init__(self, result_type): >>> self._result_type = result_type 83 >>> >>> def __call__(self, f): >>> @functools.wraps(f) >>> def wrapper(*args, **kwargs): >>> result = f(*args, **kwargs) >>> assert isinstance(result, self. _result_type) >>> return result >>> return wrapper СВОЙ СТВА КАК ДЕКОРАТОРЫ Рассмотрим пример реализации свой ства: >>> class Animal(object): >>> def __init__(self, age=0): >>> self._age = age >>> >>> @property >>> def age(self): >>> “””age of animal””” >>> return self._age >>> >>> @age.setter >>> def age(self, age): >>> assert age >= self._age >>> self._age = age Свой ства – это дескрипторы, которые можно создать, декори- руя методы с помощью property (docstring- свой ства получаются из getter’а): – getter – @property – setter – @ – deleter – @ Полный синтаксис декоратора- дескриптора property имеет вид: age = property(fget, fset, fdelete, doc) Замечание. Создать write- only- свой ство можно, только явно вы- звав property с параметром fget, равным None. 84 3.8. ИТЕРАТОРЫ И ГЕНЕРАТОРЫ SEQUENCE AND ITERABLE Последовательность (sequence) – упорядоченный индекси- руемый набор объектов, например list, tuple и str. У этих объектов переопределены «магические методы» __len__ (длина последовательности) и __getitem__ (отвечает за индексацию). Итерируемое (iterable) – упорядоченный набор объектов, элемен- ты которого можно получать по одному. У таких объектов реализован метод __iter__ – возвращает итера- тор, который позволяет обойти итерируемый объект. Итератор (iterator) представляет собой «поток данных» – он по- зволяет обойти все элементы итерируемого объекта, возвращая их в некоторой последовательности. В итераторе переопределен метод __next__ (next в Python 2), вы- зов которого либо возвращает следующий объект, либо бросает ис- ключение StopIteration, если все объекты закончились. Для явного получения итератора и взятия следующего элемента используются built- in- методы iter и next. Следующие примеры равносильны: >>> for item in sequence: >>> action(item) >>> def for_sequence(sequence, action): # “for” for sequence >>> i, length = 0, len(sequence) >>> while i < length: >>> item = sequence[i] >>> action(item) >>> i += 1 >>> def for_iterable(iterable, action): # “for” for iterator >>> iterator = iter(iterable) >>> try: >>> while True: >>> item = next(iterator) >>> action(item) >>> except StopIteration: >>> pass 85 ИТЕРАТОРЫ Итераторы представляют собой классы, содержащие ин- формацию о текущем состоянии итерирования по объекту (напри- мер, индекс). После обхода всех элементов итератор «истощается» (exhausted), бросая исключение StopIteration при каждом следующем вызове __next__. Замечания. Функция next имеет второй параметр – значение по умолчанию, которое будет возвращено, когда итератор исчерпается. У функции iter также есть второй аргумент – значение, до полу- чения которого будет продолжаться итерирование. Пример реализации итератора: >>> class RangeIterator(collections.Iterator): >>> def __init__(self, start, stop=None, step=1): >>> self._start = start if stop is not None else 0 >>> self._stop = stop if stop is not None else start >>> self._step = step # positive only >>> self._current = self._start >>> >>> def __next__(self): >>> if self._current >= self._stop: >>> raise StopIteration() >>> >>> result = self._current >>> self._current += self._step >>> return result Поскольку итераторы хранят информацию о состоянии, их мож- но прервать и впоследствии продолжить итерироваться. Вопрос: что выведет следующий код? >>> odd_indices_iterator = RangeIterator(1, 10, 2) >>> >>> for idx in odd_indices_iterator: >>> if idx > 5: >>> break >>> print(idx) >>> >>> for idx in odd_indices_iterator: >>> print(idx) 86 ИТЕРИРУЕМЫЕ И ИСТОЩАЕМЫЕ Последовательности итерируемы и неистощаемы (можно много раз итерироваться по ним). Итерируемые объекты (не последовательности) могут как не истощаться ( range в Python 3.x или xrange в Python 2.x), так и истощаться (генераторы). Итераторы итерируемы (возвращают сами себя) и истощаемы (можно только один раз обойти). Замечания. Часто в классах не реализуют отдельный класс- итератор. В таком случае метод __iter__ возвращает генератор. В Python 2 range возвращает список, а xrange – генератор. Пример итерируемого объекта >>> class SomeSequence(collections.Iterable): >>> def __init__(self, *items): >>> self._items = items >>> >>> def __iter__(self): >>> for item in self._items: >>> yield item >>> >>> def __iter__(self): >>> yield from self._items # только в Python 3.3+ >>> >>> def __iter__(self): # простой и менее гибкий вариант >>> return iter(self._items) Напоминание. В модуле collections есть и другие базовые клас- сы, например Sequence. Эти классы реализуют множество полезных методов, требуя переопределить лишь несколько. ГЕНЕРАТОР Генератор – итератор, с которым можно взаимодейство- вать (PEP-255) 1 1 https://www.python.org/dev/peps/pep-0255 87 Каждый следующий объект возвращается с помощью выражения yield. Это выражение приостанавливает работу генератора и переда- ет значение в вызывающую функцию. При повторном вызове испол- нение продолжается с текущей позиции либо до следующего yield, либо до конца функции. Генераторы удобно использовать, когда вся последовательность сразу не нужна, а следует лишь по ней итерироваться. >>> assert all(x % 2 for x in range(1, 10, 2)) Замечание. Выражения- генераторы имеют вид comprehensions с круглыми скобками. При передаче в функцию дополнительные кру- глые скобки не нужны. ЗАМЕЧАНИЯ ПО ГЕНЕРАТОРАМ Конструкция yield from делегирует, по сути, исполнение некоторому другому итератору (c Python 3.3, PEP-380) 1 В Python 3 появилась возможность у генераторов (например, range) узнать длину генерируемой ими последовательности (метод __len__) и проверить, генерируют ли они определенный элемент (ме- тод __contains__). Также в Python 3 есть специальный класс – collections. ChainMap, который представляет собой обертку над нескольки- ми mapping’ами. Генераторы могут использоваться как coroutines: выражение yield не только отдает значение из функции, но может принимать и некоторые значения, исключения или завершать выполнение ге- нератора. Подробнее о coroutines, а также их асинхронных верси- ях написано здесь: документация 2 , PEP-342 3 , PEP-479 4 , PEP-492 5 , PEP-525 6 , PEP-530 7 , PEP-3148 8 , PEP-3156 9 1 https://www.python.org/dev/peps/pep-0380 2 https ://docs.python.org/3.7/library/asyncio.html 3 https ://www.python.org/dev/peps/pep‑0342 4 https ://www.python.org/dev/peps/pep‑0479 5 https ://www.python.org/dev/peps/pep‑0492 6 https ://www.python.org/dev/peps/pep‑0525 7 https ://www.python.org/dev/peps/pep‑0530 8 https ://www.python.org/dev/peps/pep‑3148 9 https ://www.python.org/dev/peps/pep‑3156 88 ДОПОЛНИТЕЛЬНЫЕ СПОСОБЫ ИТЕРИРОВАНИЯ В стандартной библиотеке есть модуль itertools, в кото- ром реализовано множество итераторов: – cycle – зацикливает некоторый iterable; – count – бесконечный счетчик с заданным начальным значе- нием и шагом; – repeat – возвращает некоторое значение заданное число раз. Есть и комбинаторные итераторы: – product– итератор по декартову произведению последова- тельностей (по сути, генерирует кортежи, если бы был реализован вложенный for); – combinations– итератор по упорядоченным сочетаниям эле- ментов; – permutations– итератор по перестановкам переданных эле- ментов. Дополнительные способы итерирования: – chain – итерируется последовательно по нескольким iterable; – zip_longest – аналог zip, только прекращает итерироваться, когда исчерпывается не первый, а последний итератор; – takewhile/dropwhile/filterfalse/compress – отбирает элемен- ты последовательности в соответствии с предикатом; – islice – итераторный аналог slice (не создает списка элементов); – groupby – группирует последовательные элементы; – starmap – аналог map, только распаковывает аргумент при пе- редаче; – tee – создает n копий итератора. Замечания. В Python 2 доступны ifilter и izip – итераторные ана- логи filter и zip. В Python 3.2 появилась функция accumulate, которая возвраща- ет итератор по кумулятивному массиву. 3.9. МЕНЕДЖЕРЫ КОНТЕКСТА В процессе работы с файлами важно корректно работать с исключениями: файл необходимо закрыть в любом случае. Данный синтаксис позволяет закрыть файл по выходе из бло- ка with: >>> with open(file_name) as f: >>> # some actions |