SlideShare una empresa de Scribd logo
1 de 39
Descargar para leer sin conexión
Лекция 3: Декораторы и модуль functools
Сергей Лебедев
sergei.a.lebedev@gmail.com
22 сентября 2015 г.
Синтаксис использования декораторов
• Декоратор — функция, которая принимает другую
функцию и что-то возвращает.
>>> @trace
... def foo(x):
... return 42
...
• Аналогичная по смыслу версия без синтаксического сахара
>>> def foo(x):
... return 42
...
>>> foo = trace(foo)
• Теперь понятно, что по имени foo будет доступно то, что
вернула функция trace. Это и есть результат применения
декоратора.
• Возвращаемый объект может быть любого типа.
1 / 35
“Теория”
декораторов
Пример: @trace
• Декоратор trace выводит на экран сообщение с
информацией о вызове декорируемой функции.
>>> def trace(func):
... def inner(*args, **kwargs):
... print(func.__name__, args, kwargs)
... return func(*args, **kwargs)
... return inner
• Применим его к тождественной функции
>>> @trace
... def identity(x):
... "I do nothing useful."
... return x
...
>>> identity(42)
identity (42, ) {}
42
2 / 35
Пример: что дальше?
• Проблема с help и атрибутами декорируемой функции.
>>> help(identity)
Help on function inner in module __main__:
inner(*args, **kwargs)
• Возможность глобально отключать trace без лишних
телодвижений.
• Явное указание файла при использовании trace
>>> @trace(sys.stderr)
... def identity(x):
... return x
• Использование sys.stdout для вывода по умолчанию.
3 / 35
Декораторы и help: проблема
>>> def identity(x):
... "I do nothing useful."
... return x
...
>>> identity.__name__, identity.__doc__
('identity', 'I do nothing useful as well.')
>>> identity = trace(identity)
>>> identity.__name__, identity.__doc__
('inner, None)
__module__
У любой функции в Python есть атрибут __module__,
содержащий имя модуля, в котором функция была определена.
Для функций, определённых в интерпретаторе, например:
>>> identity.__module__
'__main__'
4 / 35
Декораторы и help: решение в лоб
• Давайте просто возьмём и установим “правильные”
значения в атрибуты декорируемой функции:
>>> def trace(func):
... def inner(*args, **kwargs):
... print(func.__name__, args, kwargs)
... return func(*args, **kwargs)
... inner.__module__ = func.__module__
... inner.__name__ = func.__name__
... inner.__doc__ = func.__doc__
... return inner
• Проверим
>>> @trace
... def identity(x):
... "I do nothing useful."
... return x
...
>>> identity.__name__, identity.__doc__
('identity', 'I do nothing useful as well.')
5 / 35
Декораторы и help: модуль functools
• В модуле functools из стандартной библиотеки Python
есть функция, реализующая логику копирования
внутренних атрибутов
>>> import functools
>>> def trace(func):
... def inner(*args, **kwargs):
... print(func.__name__, args, kwargs)
... return func(*args, **kwargs)
... functools.update_wrapper(inner, func)
... return inner
• То же самое можно сделать с помощью декоратора wraps
>>> def trace(func):
... @functools.wraps(func)
... def inner(*args, **kwargs):
... print(func.__name__, args, kwargs)
... return func(*args, **kwargs)
... return inner
6 / 35
Управление поведением trace
• Заведём глобальную переменную trace_enabled и с её
помощью будем отключать и включать trace.
>>> trace_enabled = False
>>> def trace(func):
... @functools.wraps(func)
... def inner(*args, **kwargs):
... print(func.__name__, args, kwargs)
... return func(*args, **kwargs)
... return inner if trace_enabled else func
• Если trace выключен, то результатом применения
декоратора является сама функция func — никаких
дополнительных действий в момент её исполнения
производиться не будет.
7 / 35
Декораторы с аргументами: синтаксис
• Напоминание:
>>> @trace
... def identity(x):
... return x
≡ >>> def identity(x):
... return x
...
>>> identity = trace(identity)
• Для декораторов с аргументами эквивалентность
сохраняется
>>> @trace(sys.stderr)
... def identity(x):
... return x
≡ >>> def identity(x):
... return x
...
>>> deco = trace(sys.stderr)
>>> identity = deco(identity)
8 / 35
Декораторы с аргументами: реализация
>>> def trace(handle):
... def decorator(func):
... @functools.wraps(func)
... def inner(*args, **kwargs):
... print(func.__name__, args, kwargs,
... file=handle)
... return func(*args, **kwargs)
... return inner
... return decorator
...
9 / 35
Декораторы с аргументами: @with_arguments
• Можно обобщить логику декоратора с аргументами в виде
декоратора with_arguments
>>> def with_arguments(deco):
... @functools.wraps(deco)
... def wrapper(*dargs, **dkwargs):
... def decorator(func):
... result = deco(func, *dargs, **dkwargs)
... functools.update_wrapper(result, func)
... return result
... return decorator
... return wrapper
• Что происходит:
1. with_arguments принимает декоратор deco,
2. заворачивает его во wrapper, так как deco — декоратор с
аргументами, а затем в decorator.
3. decorator конструирует новый декоратор с помощью deco
и копирует в него внутренние атрибуты функции func.
10 / 35
Декораторы с аргументами: человечный trace
>>> @with_arguments
... def trace(func, handle):
... def inner(*args, **kwargs):
... print(func.__name__, args, kwargs, file=handle)
... return func(*args, **kwargs)
... return inner
...
>>> @trace(sys.stderr)
... def identity(x):
... return x
Вопросы?
11 / 35
Декораторы с опциональными аргументами: наивная версия
• Почему бы просто не указать аргумент по умолчанию для
параметра handle?
>>> @with_arguments
... def trace(func, handle=sys.stdout):
... @functools.wraps(func)
... def inner(*args, **kwargs):
... print(func.__name__, args, kwargs,
... file=handle)
... return func(*args, **kwargs)
... return inner
• Попробуем применить новую версию декоратора trace к
функции identity
>>> @trace
... def identity(x):
... return x
...
>>> identity(42)
<function trace.<locals>.inner at 0x10b3969d8>
12 / 35
Декораторы с опциональными аргументами: работающая версия
>>> @trace()
... def identity(x):
... return x
...
>>> identity(42)
identity (42,) {}
42
Вопрос
Можно ли как-нибудь избавиться от лишних скобок?
13 / 35
Декораторы с опциональными аргументами: магическая версия
>>> def trace(func=None, *, handle=sys.stdout):
... # со скобками
... if func is None:
... return lambda func: trace(func, handle=handle)
...
... # без скобок
... @functools.wraps(func)
... def inner(*args, **kwargs):
... print(func.__name__, args, kwargs)
... return func(*args, **kwargs)
... return inner
Вопрос
Зачем требовать, чтобы аргументы декоратора были только
ключевыми?
14 / 35
“Теория” декораторов: резюме
• Декоратор — способ модифицировать поведение функции,
сохраняя читаемость кода.
• Декораторы бывают:
• без аргументов @trace
• с аргументами @trace(sys.stderr)
• с опциональными аргументами.
15 / 35
Практика
декораторов
Использование декораторов: @timethis
>>> def timethis(func=None, *, n_iter=100):
... if func is None:
... return lambda func: timethis(func, n_iter=n_iter)
...
... @functools.wraps(func)
... def inner(*args, **kwargs):
... print(func.__name__, end=" ... ")
... acc = float("inf")
... for i in range(n_iter):
... tick = time.perf_counter()
... result = func(*args, **kwargs)
... acc = min(acc, time.perf_counter() - tick)
... print(acc)
... return result
... return inner
...
>>> result = timethis(sum)(range(10 ** 6))
sum ... 0.026534789009019732
16 / 35
Использование декораторов: @profiled
>>> def profiled(func):
... @functools.wraps(func)
... def inner(*args, **kwargs):
... inner.ncalls += 1
... return func(*args, **kwargs)
...
... inner.ncalls = 0
... return inner
...
>>> @profiled
... def identity(x):
... return x
...
>>> identity(42)
42
>>> identity.ncalls
1
17 / 35
Использование декораторов: @once
>>> def once(func):
... @functools.wraps(func)
... def inner(*args, **kwargs):
... if not inner.called:
... func(*args, **kwargs)
... inner.called = True
... inner.called = False
... return inner
...
>>> @once
... def initialize_settings():
... print("Settings initialized.")
...
>>> initialize_settings()
Settings initialized.
>>> initialize_settings()
Вопрос
Как модифицировать декоратор @once, чтобы он поддерживал
функции, возвращающие не None значения?
18 / 35
Использование декораторов: @memoized
• Мемоизация — сохранение результатов выполнения
функции для предотвращения избыточных вычислений.
• Напишем декоратор для автоматической мемоизации
“любой” функции.
>>> def memoized(func):
... cache = {}
...
... @functools.wraps(func)
... def inner(*args, **kwargs):
... key = args, kwargs
... if key not in cache:
... cache[key] = func(*args, **kwargs)
... return cache[key]
... return inner
19 / 35
Использование декораторов: @memoized и функция Аккермана
>>> @memoized
... def ackermann(m, n):
... if not m:
... return n + 1
... elif not n:
... return ackermann(m - 1, 1)
... else:
... return ackermann(m - 1, ackerman(m, n - 1))
...
>>> ackermann(3, 4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in inner
TypeError: unhashable type: 'dict'
Вопрос
Что делать?
20 / 35
Использование декораторов: снова @memoized
• Частное решение проблемы1:
>>> def memoized(func):
... cache = {}
...
... @functools.wraps(func)
... def inner(*args, **kwargs):
... key = args + tuple(sorted(kwargs.items()))
... if key not in cache:
... cache[key] = func(*args, **kwargs)
... return cache[key]
... return inner
...
>>> ackermann(3, 4)
125
• Нет универсального и быстрого решения. Можно
сериализовывать аргументы в строку, например, через str
или, что более осмысленно, через pickle.
1
Кстати, почему частное?
21 / 35
Использование декораторов: @deprecated
>>> def deprecated(func):
... code = func.__code__
... warnings.warn_explicit(
... func.__name__ + " is deprecated.",
... category=DeprecationWarning,
... filename=code.co_filename,
... lineno=code.co_firstlineno + 1)
... return func
...
>>> @deprecated
... def identity(x):
... return x
...
<stdin>:2: DeprecationWarning: identity is deprecated.
22 / 35
Использование декораторов: контракты pre и post
• Контрактное программирование — способ проектирования
программ, основывающийся на формальном описании
интерфейсов в терминах предусловий, постусловий и
инвариантов.
• В Python контрактное программирование можно
реализовать в виде библиотеки декораторов2:
>>> @pre(lambda x: x >= 0, "negative argument")
... def checked_log(x):
... pass
...
>>> is_not_nan = post(lambda r: not math.isnan(r),
... "not a number")
>>> @is_not_nan
... def something_useful():
... pass
...
2
https://pypi.python.org/pypi/contracts
23 / 35
Использование декораторов: реализация @pre
>>> def pre(cond, message):
... def wrapper(func):
... @functools.wraps(func)
... def inner(*args, **kwargs):
... assert cond(*args, **kwargs), message
... return func(*args, **kwargs)
... return inner
... return wrapper
...
>>> @pre(lambda x: r >= 0, "negative argument")
... def checked_log(x):
... return math.log(x)
...
>>> checked_log(-42)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in inner
AssertionError: negative argument
24 / 35
Использование декораторов: реализация @post
>>> def post(cond, message):
... def wrapper(func):
... @functools.wraps(func)
... def inner(*args, **kwargs):
... result = func(*args, **kwargs)
... assert cond(result), message
... return result
... return inner
... return wrapper
...
>>> @post(lambda x: not math.isnan(x), "not a number")
... def something_useful():
... return float("nan")
...
>>> something_useful()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in inner
AssertionError: not a number
25 / 35
Цепочки декораторов
• Синтаксис Python разрешает одновременное применение
нескольких декораторов.
• Порядок декораторов имеет значение:
>>> def square(func):
... return lambda x: func(x * x)
...
>>> def addsome(func):
... return lambda x: func(x + 42)
...
>>> @square
... @addsome
... def identity(x):
... return x
...
>>> identity(2)
46
>>> @addsome
... @square
... def identity(x):
... return x
...
>>> identity(2)
1936
26 / 35
Использование декораторов: резюме
• Декораторы в мире Python вездесущи и полезны.
• Больше примеров можно найти по ссылке3 и практически
в любой библиотеке на Python.
3
https://wiki.python.org/moin/PythonDecoratorLibrary
27 / 35
Модуль
functools
Модуль functools: lru_cache
• Родственник уже рассмотренного memoized, сохраняющий
фиксированное количество последних вызовов.
• Познакомим lru_cache с функцией Аккермана
>>> @functools.lru_cache(maxsize=64)
... def ackermann(m, n):
... # ...
...
>>> ackermann(3, 4)
125
>>> ackermann.cache_info()
CacheInfo(hits=65, misses=315, maxsize=64, currsize=64)
• Можно не ограничивать количество сохраняемых вызовов,
тогда мы получим в точности поведение memoized4:
>>> @functools.lru_cache(maxsize=None)
... def ackermann(m, n):
... # ...
...
4
Почему использовать None в качестве значения по умолчанию для
maxsize — плохая идея?
28 / 35
Модуль functools: partial
• С помощью partial можно зафиксировать часть
позиционных и ключевых аргументов в функции.
• Выполнившие домашнее задание узнают в partial
расширение функции curry.
• Пример:
>>> f = functools.partial(sorted, key=lambda p: p[1])
>>> f([("a", 4), ("b", 2)])
[('b', 2), ('a', 4)]
>>> g = functools.partial(sorted, [2, 3, 1, 4])
>>> g()
[1, 2, 3, 4]
29 / 35
Модуль functools: обобщённые функции5
• Функция len называется обобщённой, так как её
реализация может быть специализирована для
конкретного типа.
>>> len([1, 2, 3, 4])
4
>>> len({1, 2, 3, 4})
4
>>> len(range(4))
4
• Примеры обобщённых функций в Python:
>>> str([1, 2, 3, 4])
'[1, 2, 3, 4]'
>>> hash((1, 2, 3, 4))
485696759010151909
>>> sum([[1], [2]], [])
[1, 2]
• Как реализовать свою обобщённую функцию?
5
http://python.org/dev/peps/pep-0443
30 / 35
Модуль functools: singledispatch
• В качестве примера реализуем функцию pack, которая
сериализует объект в компактное строковое
представление
>>> @functools.singledispatch
... def pack(obj):
... type_name = type(obj).__name__
... assert False, "Unsupported type: " + type_name
• Научим функцию pack сериализовывать числа и списки
>>> @pack.register(int)
... def _(obj):
... return b"I" + hex(obj).encode("ascii")
...
>>> @pack.register(list)
... def _(obj):
... return b"L" + b",".join(map(pack, obj))
...
>>> pack([1, 2, 3])
b'LI0x1,I0x2,I0x3'
>>> pack(42.)
AssertionError: Unsupported type: float
31 / 35
Модуль functools: мотивация для reduce
• Рассмотрим:
>>> sum([1, 2, 3, 4], start=0)
10
>>> (((0 + 1) + 2) + 3) + 4
10
• А что, если мы хотим использовать другую бинарную
операцию, например, умножение?
>>> ((1 * 2) * 3) * 4
24
• Функция reduce обобщает логику функции sum на
произвольную бинарную операцию.
>>> functools.reduce(lambda acc, x: acc * x,
... [1, 2, 3, 4])
24
32 / 35
Модуль functools: подробнее о reduce
• Функция reduce принимает три аргумента: бинарную
функцию, последовательность и опциональное начальное
значение.
• Вычисление reduce(op, xs, initial) можно схематично
представить как:
>>> op(op(op(op(init, xs[0]), xs[1]), xs[2]), ...)
>>> op(op(op(xs[0], xs[1]), xs[2]), ...)
• Несколько примеров:
>>> functools.reduce(lambda acc, d: 10 * acc + int(d),
... "1914", initial=0)
1914
>>> functools.reduce(merge, [[1, 2, 7], [5, 6], [0]])
[0, 1, 2, 5, 6, 7]
33 / 35
Модуль functools: reduce и философия
• Несмотря на свою популярность в функциональных
языках, в Python довольно сложно придумать полезный
пример использования reduce.
• Резюме про reduce:
• работает с любым объектом, поддерживающим протокол
итератора;
• работает слева направо;
• использует первый элемент последовательности, если
начальное значение не указано явно.
34 / 35
Модуль functools: резюме
• Модуль functools украшает будни любителя
функционального программирования.
• Мы поговорили про:
• lru_cache
• partial
• singledispatch
• reduce
35 / 35

Más contenido relacionado

La actualidad más candente

Лекция 6. Классы 1.
Лекция 6. Классы 1.Лекция 6. Классы 1.
Лекция 6. Классы 1.Roman Brovko
 
Лекция 4. Строки, байты, файлы и ввод/вывод.
 Лекция 4. Строки, байты, файлы и ввод/вывод. Лекция 4. Строки, байты, файлы и ввод/вывод.
Лекция 4. Строки, байты, файлы и ввод/вывод.Roman Brovko
 
Лекция 7. Исключения и менеджеры контекста.
Лекция 7. Исключения и менеджеры контекста.Лекция 7. Исключения и менеджеры контекста.
Лекция 7. Исключения и менеджеры контекста.Roman Brovko
 
Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.Roman Brovko
 
Красота и изящность стандартной библиотеки Python
Красота и изящность стандартной библиотеки PythonКрасота и изящность стандартной библиотеки Python
Красота и изящность стандартной библиотеки PythonPython Meetup
 
Pyton – пробуем функциональный стиль
Pyton – пробуем функциональный стильPyton – пробуем функциональный стиль
Pyton – пробуем функциональный стильPython Meetup
 
Быстрые конструкции в Python - Олег Шидловский, Python Meetup 26.09.2014
Быстрые конструкции в Python - Олег Шидловский, Python Meetup 26.09.2014Быстрые конструкции в Python - Олег Шидловский, Python Meetup 26.09.2014
Быстрые конструкции в Python - Олег Шидловский, Python Meetup 26.09.2014Python Meetup
 
Лекция #5. Введение в язык программирования Python 3
Лекция #5. Введение в язык программирования Python 3Лекция #5. Введение в язык программирования Python 3
Лекция #5. Введение в язык программирования Python 3Яковенко Кирилл
 
Декораторы в Python и их практическое использование
Декораторы в Python и их практическое использование Декораторы в Python и их практическое использование
Декораторы в Python и их практическое использование Sergey Schetinin
 
Производительность в Django
Производительность в DjangoПроизводительность в Django
Производительность в DjangoMoscowDjango
 
Python и его тормоза
Python и его тормозаPython и его тормоза
Python и его тормозаAlexander Shigin
 
Магия в Python: Дескрипторы. Что это?
Магия в Python: Дескрипторы. Что это?Магия в Python: Дескрипторы. Что это?
Магия в Python: Дескрипторы. Что это?PyNSK
 
Оптимизация производительности Python
Оптимизация производительности PythonОптимизация производительности Python
Оптимизация производительности PythonPyNSK
 
Мир Python функционалим с помощью библиотек
Мир Python  функционалим с помощью библиотекМир Python  функционалим с помощью библиотек
Мир Python функционалим с помощью библиотекPyNSK
 
Профилирование и отладка Django
Профилирование и отладка DjangoПрофилирование и отладка Django
Профилирование и отладка DjangoVladimir Rudnyh
 
9. java lecture library
9. java lecture library9. java lecture library
9. java lecture libraryMERA_school
 
C#. От основ к эффективному коду
C#. От основ к эффективному кодуC#. От основ к эффективному коду
C#. От основ к эффективному кодуVasiliy Deynega
 

La actualidad más candente (18)

Лекция 6. Классы 1.
Лекция 6. Классы 1.Лекция 6. Классы 1.
Лекция 6. Классы 1.
 
Лекция 4. Строки, байты, файлы и ввод/вывод.
 Лекция 4. Строки, байты, файлы и ввод/вывод. Лекция 4. Строки, байты, файлы и ввод/вывод.
Лекция 4. Строки, байты, файлы и ввод/вывод.
 
Лекция 7. Исключения и менеджеры контекста.
Лекция 7. Исключения и менеджеры контекста.Лекция 7. Исключения и менеджеры контекста.
Лекция 7. Исключения и менеджеры контекста.
 
Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.Лекция 5. Встроенные коллекции и модуль collections.
Лекция 5. Встроенные коллекции и модуль collections.
 
Красота и изящность стандартной библиотеки Python
Красота и изящность стандартной библиотеки PythonКрасота и изящность стандартной библиотеки Python
Красота и изящность стандартной библиотеки Python
 
Pyton – пробуем функциональный стиль
Pyton – пробуем функциональный стильPyton – пробуем функциональный стиль
Pyton – пробуем функциональный стиль
 
Быстрые конструкции в Python - Олег Шидловский, Python Meetup 26.09.2014
Быстрые конструкции в Python - Олег Шидловский, Python Meetup 26.09.2014Быстрые конструкции в Python - Олег Шидловский, Python Meetup 26.09.2014
Быстрые конструкции в Python - Олег Шидловский, Python Meetup 26.09.2014
 
Лекция #5. Введение в язык программирования Python 3
Лекция #5. Введение в язык программирования Python 3Лекция #5. Введение в язык программирования Python 3
Лекция #5. Введение в язык программирования Python 3
 
Декораторы в Python и их практическое использование
Декораторы в Python и их практическое использование Декораторы в Python и их практическое использование
Декораторы в Python и их практическое использование
 
Производительность в Django
Производительность в DjangoПроизводительность в Django
Производительность в Django
 
Python и его тормоза
Python и его тормозаPython и его тормоза
Python и его тормоза
 
Магия в Python: Дескрипторы. Что это?
Магия в Python: Дескрипторы. Что это?Магия в Python: Дескрипторы. Что это?
Магия в Python: Дескрипторы. Что это?
 
Оптимизация производительности Python
Оптимизация производительности PythonОптимизация производительности Python
Оптимизация производительности Python
 
Мир Python функционалим с помощью библиотек
Мир Python  функционалим с помощью библиотекМир Python  функционалим с помощью библиотек
Мир Python функционалим с помощью библиотек
 
Профилирование и отладка Django
Профилирование и отладка DjangoПрофилирование и отладка Django
Профилирование и отладка Django
 
9. java lecture library
9. java lecture library9. java lecture library
9. java lecture library
 
C#. От основ к эффективному коду
C#. От основ к эффективному кодуC#. От основ к эффективному коду
C#. От основ к эффективному коду
 
Decorators' recipes
Decorators' recipesDecorators' recipes
Decorators' recipes
 

Destacado

02 - Практика UML. Уровни приложения
02 - Практика UML. Уровни приложения02 - Практика UML. Уровни приложения
02 - Практика UML. Уровни приложенияRoman Brovko
 
01 - Практика UML. Нужен ли UML?
01 - Практика UML. Нужен ли UML?01 - Практика UML. Нужен ли UML?
01 - Практика UML. Нужен ли UML?Roman Brovko
 
13 - Практика UML. Переход к разработке
13 - Практика UML. Переход к разработке13 - Практика UML. Переход к разработке
13 - Практика UML. Переход к разработкеRoman Brovko
 
12 - Практика UML. Создание wireframe
12 - Практика UML. Создание wireframe12 - Практика UML. Создание wireframe
12 - Практика UML. Создание wireframeRoman Brovko
 
04 - Практика UML. Описание прецедентов
04 - Практика UML. Описание прецедентов04 - Практика UML. Описание прецедентов
04 - Практика UML. Описание прецедентовRoman Brovko
 
03 - Практика UML. Прецеденты
03 - Практика UML. Прецеденты03 - Практика UML. Прецеденты
03 - Практика UML. ПрецедентыRoman Brovko
 
09 - Практика UML. Use Case диаграммы
09 - Практика UML. Use Case диаграммы09 - Практика UML. Use Case диаграммы
09 - Практика UML. Use Case диаграммыRoman Brovko
 

Destacado (7)

02 - Практика UML. Уровни приложения
02 - Практика UML. Уровни приложения02 - Практика UML. Уровни приложения
02 - Практика UML. Уровни приложения
 
01 - Практика UML. Нужен ли UML?
01 - Практика UML. Нужен ли UML?01 - Практика UML. Нужен ли UML?
01 - Практика UML. Нужен ли UML?
 
13 - Практика UML. Переход к разработке
13 - Практика UML. Переход к разработке13 - Практика UML. Переход к разработке
13 - Практика UML. Переход к разработке
 
12 - Практика UML. Создание wireframe
12 - Практика UML. Создание wireframe12 - Практика UML. Создание wireframe
12 - Практика UML. Создание wireframe
 
04 - Практика UML. Описание прецедентов
04 - Практика UML. Описание прецедентов04 - Практика UML. Описание прецедентов
04 - Практика UML. Описание прецедентов
 
03 - Практика UML. Прецеденты
03 - Практика UML. Прецеденты03 - Практика UML. Прецеденты
03 - Практика UML. Прецеденты
 
09 - Практика UML. Use Case диаграммы
09 - Практика UML. Use Case диаграммы09 - Практика UML. Use Case диаграммы
09 - Практика UML. Use Case диаграммы
 

Similar a Лекция 3. Декораторы и модуль functools.

ITCrowd - Метапрограммирование
ITCrowd - МетапрограммированиеITCrowd - Метапрограммирование
ITCrowd - МетапрограммированиеITCrowd Almaty
 
Характерные черты функциональных языков программирования
Характерные черты функциональных языков программированияХарактерные черты функциональных языков программирования
Характерные черты функциональных языков программированияAlex.Kolonitsky
 
Web осень 2013 лекция 8
Web осень 2013 лекция 8Web осень 2013 лекция 8
Web осень 2013 лекция 8Technopark
 
Lambdas in java 8
Lambdas in java 8Lambdas in java 8
Lambdas in java 8chashnikov
 
Behat в PHP с использованием Behat и Mink
Behat в PHP с использованием Behat и MinkBehat в PHP с использованием Behat и Mink
Behat в PHP с использованием Behat и Minktyomo4ka
 
Лекция о языке программирования Haskell
Лекция о языке программирования HaskellЛекция о языке программирования Haskell
Лекция о языке программирования Haskellhusniyarova
 
Функциональное программирование на F#
Функциональное программирование на F#Функциональное программирование на F#
Функциональное программирование на F#akrakovetsky
 
8 встреча — Язык программирования Python (В. Ананьев)
8 встреча — Язык программирования Python (В. Ананьев)8 встреча — Язык программирования Python (В. Ананьев)
8 встреча — Язык программирования Python (В. Ананьев)Smolensk Computer Science Club
 
«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС
«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС
«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНСit-people
 
функции в Java script
функции в Java scriptфункции в Java script
функции в Java scriptViktor Andreev
 
"Вингардиум Левиоса”. Или основы декларативной магии (Матвеенко Сергей)
"Вингардиум Левиоса”. Или основы декларативной магии (Матвеенко Сергей)"Вингардиум Левиоса”. Или основы декларативной магии (Матвеенко Сергей)
"Вингардиум Левиоса”. Или основы декларативной магии (Матвеенко Сергей)IT-Доминанта
 
Зачем нужна Scala?
Зачем нужна Scala?Зачем нужна Scala?
Зачем нужна Scala?Vasil Remeniuk
 
Сверхоптимизация кода на Python
Сверхоптимизация кода на PythonСверхоптимизация кода на Python
Сверхоптимизация кода на Pythonru_Parallels
 
Сверхоптимизация кода на Python
Сверхоптимизация кода на PythonСверхоптимизация кода на Python
Сверхоптимизация кода на PythonCodeFest
 
C++ Базовый. Занятие 02.
C++ Базовый. Занятие 02.C++ Базовый. Занятие 02.
C++ Базовый. Занятие 02.Igor Shkulipa
 

Similar a Лекция 3. Декораторы и модуль functools. (20)

Основы Python. Функции
Основы Python. ФункцииОсновы Python. Функции
Основы Python. Функции
 
Scala on android
Scala on androidScala on android
Scala on android
 
ITCrowd - Метапрограммирование
ITCrowd - МетапрограммированиеITCrowd - Метапрограммирование
ITCrowd - Метапрограммирование
 
Характерные черты функциональных языков программирования
Характерные черты функциональных языков программированияХарактерные черты функциональных языков программирования
Характерные черты функциональных языков программирования
 
Scala - my path
Scala - my pathScala - my path
Scala - my path
 
Web осень 2013 лекция 8
Web осень 2013 лекция 8Web осень 2013 лекция 8
Web осень 2013 лекция 8
 
Lambdas in java 8
Lambdas in java 8Lambdas in java 8
Lambdas in java 8
 
Behat в PHP с использованием Behat и Mink
Behat в PHP с использованием Behat и MinkBehat в PHP с использованием Behat и Mink
Behat в PHP с использованием Behat и Mink
 
Лекция о языке программирования Haskell
Лекция о языке программирования HaskellЛекция о языке программирования Haskell
Лекция о языке программирования Haskell
 
Функциональное программирование на F#
Функциональное программирование на F#Функциональное программирование на F#
Функциональное программирование на F#
 
8 встреча — Язык программирования Python (В. Ананьев)
8 встреча — Язык программирования Python (В. Ананьев)8 встреча — Язык программирования Python (В. Ананьев)
8 встреча — Язык программирования Python (В. Ананьев)
 
Charming python sc2-8
Charming python sc2-8Charming python sc2-8
Charming python sc2-8
 
«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС
«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС
«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС
 
функции в Java script
функции в Java scriptфункции в Java script
функции в Java script
 
"Вингардиум Левиоса”. Или основы декларативной магии (Матвеенко Сергей)
"Вингардиум Левиоса”. Или основы декларативной магии (Матвеенко Сергей)"Вингардиум Левиоса”. Или основы декларативной магии (Матвеенко Сергей)
"Вингардиум Левиоса”. Или основы декларативной магии (Матвеенко Сергей)
 
Зачем нужна Scala?
Зачем нужна Scala?Зачем нужна Scala?
Зачем нужна Scala?
 
Пакет purrr
Пакет purrrПакет purrr
Пакет purrr
 
Сверхоптимизация кода на Python
Сверхоптимизация кода на PythonСверхоптимизация кода на Python
Сверхоптимизация кода на Python
 
Сверхоптимизация кода на Python
Сверхоптимизация кода на PythonСверхоптимизация кода на Python
Сверхоптимизация кода на Python
 
C++ Базовый. Занятие 02.
C++ Базовый. Занятие 02.C++ Базовый. Занятие 02.
C++ Базовый. Занятие 02.
 

Más de Roman Brovko

Individual task Networking
Individual task NetworkingIndividual task Networking
Individual task NetworkingRoman Brovko
 
Networking essentials lect3
Networking essentials lect3Networking essentials lect3
Networking essentials lect3Roman Brovko
 
Gl embedded starterkit_ethernet
Gl embedded starterkit_ethernetGl embedded starterkit_ethernet
Gl embedded starterkit_ethernetRoman Brovko
 
Networking essentials lect2
Networking essentials lect2Networking essentials lect2
Networking essentials lect2Roman Brovko
 
Networking essentials lect1
Networking essentials lect1Networking essentials lect1
Networking essentials lect1Roman Brovko
 
Bare metal training_07_spi_flash
Bare metal training_07_spi_flashBare metal training_07_spi_flash
Bare metal training_07_spi_flashRoman Brovko
 
Bare metal training_06_I2C
Bare metal training_06_I2CBare metal training_06_I2C
Bare metal training_06_I2CRoman Brovko
 
Bare metal training_05_uart
Bare metal training_05_uartBare metal training_05_uart
Bare metal training_05_uartRoman Brovko
 
Bare metal training_04_adc_temp_sensor
Bare metal training_04_adc_temp_sensorBare metal training_04_adc_temp_sensor
Bare metal training_04_adc_temp_sensorRoman Brovko
 
Bare metal training_03_timers_pwm
Bare metal training_03_timers_pwmBare metal training_03_timers_pwm
Bare metal training_03_timers_pwmRoman Brovko
 
Bare metal training_02_le_ds_and_buttons
Bare metal training_02_le_ds_and_buttonsBare metal training_02_le_ds_and_buttons
Bare metal training_02_le_ds_and_buttonsRoman Brovko
 
Bare metal training_01_hello_world
Bare metal training_01_hello_worldBare metal training_01_hello_world
Bare metal training_01_hello_worldRoman Brovko
 
Bare metal training_00_prerequisites
Bare metal training_00_prerequisitesBare metal training_00_prerequisites
Bare metal training_00_prerequisitesRoman Brovko
 
C language lect_23_advanced
C language lect_23_advancedC language lect_23_advanced
C language lect_23_advancedRoman Brovko
 
C language lect_22_advanced
C language lect_22_advancedC language lect_22_advanced
C language lect_22_advancedRoman Brovko
 
C language lect_21_advanced
C language lect_21_advancedC language lect_21_advanced
C language lect_21_advancedRoman Brovko
 
подготовка рабочего окружения
подготовка рабочего окруженияподготовка рабочего окружения
подготовка рабочего окруженияRoman Brovko
 
C language lect_20_advanced
C language lect_20_advancedC language lect_20_advanced
C language lect_20_advancedRoman Brovko
 
C language lect_19_basics
C language lect_19_basicsC language lect_19_basics
C language lect_19_basicsRoman Brovko
 

Más de Roman Brovko (20)

Individual task Networking
Individual task NetworkingIndividual task Networking
Individual task Networking
 
Networking essentials lect3
Networking essentials lect3Networking essentials lect3
Networking essentials lect3
 
Gl embedded starterkit_ethernet
Gl embedded starterkit_ethernetGl embedded starterkit_ethernet
Gl embedded starterkit_ethernet
 
Networking essentials lect2
Networking essentials lect2Networking essentials lect2
Networking essentials lect2
 
Networking essentials lect1
Networking essentials lect1Networking essentials lect1
Networking essentials lect1
 
Bare metal training_07_spi_flash
Bare metal training_07_spi_flashBare metal training_07_spi_flash
Bare metal training_07_spi_flash
 
Bare metal training_06_I2C
Bare metal training_06_I2CBare metal training_06_I2C
Bare metal training_06_I2C
 
Glesk worshop
Glesk worshopGlesk worshop
Glesk worshop
 
Bare metal training_05_uart
Bare metal training_05_uartBare metal training_05_uart
Bare metal training_05_uart
 
Bare metal training_04_adc_temp_sensor
Bare metal training_04_adc_temp_sensorBare metal training_04_adc_temp_sensor
Bare metal training_04_adc_temp_sensor
 
Bare metal training_03_timers_pwm
Bare metal training_03_timers_pwmBare metal training_03_timers_pwm
Bare metal training_03_timers_pwm
 
Bare metal training_02_le_ds_and_buttons
Bare metal training_02_le_ds_and_buttonsBare metal training_02_le_ds_and_buttons
Bare metal training_02_le_ds_and_buttons
 
Bare metal training_01_hello_world
Bare metal training_01_hello_worldBare metal training_01_hello_world
Bare metal training_01_hello_world
 
Bare metal training_00_prerequisites
Bare metal training_00_prerequisitesBare metal training_00_prerequisites
Bare metal training_00_prerequisites
 
C language lect_23_advanced
C language lect_23_advancedC language lect_23_advanced
C language lect_23_advanced
 
C language lect_22_advanced
C language lect_22_advancedC language lect_22_advanced
C language lect_22_advanced
 
C language lect_21_advanced
C language lect_21_advancedC language lect_21_advanced
C language lect_21_advanced
 
подготовка рабочего окружения
подготовка рабочего окруженияподготовка рабочего окружения
подготовка рабочего окружения
 
C language lect_20_advanced
C language lect_20_advancedC language lect_20_advanced
C language lect_20_advanced
 
C language lect_19_basics
C language lect_19_basicsC language lect_19_basics
C language lect_19_basics
 

Лекция 3. Декораторы и модуль functools.

  • 1. Лекция 3: Декораторы и модуль functools Сергей Лебедев sergei.a.lebedev@gmail.com 22 сентября 2015 г.
  • 2. Синтаксис использования декораторов • Декоратор — функция, которая принимает другую функцию и что-то возвращает. >>> @trace ... def foo(x): ... return 42 ... • Аналогичная по смыслу версия без синтаксического сахара >>> def foo(x): ... return 42 ... >>> foo = trace(foo) • Теперь понятно, что по имени foo будет доступно то, что вернула функция trace. Это и есть результат применения декоратора. • Возвращаемый объект может быть любого типа. 1 / 35
  • 4. Пример: @trace • Декоратор trace выводит на экран сообщение с информацией о вызове декорируемой функции. >>> def trace(func): ... def inner(*args, **kwargs): ... print(func.__name__, args, kwargs) ... return func(*args, **kwargs) ... return inner • Применим его к тождественной функции >>> @trace ... def identity(x): ... "I do nothing useful." ... return x ... >>> identity(42) identity (42, ) {} 42 2 / 35
  • 5. Пример: что дальше? • Проблема с help и атрибутами декорируемой функции. >>> help(identity) Help on function inner in module __main__: inner(*args, **kwargs) • Возможность глобально отключать trace без лишних телодвижений. • Явное указание файла при использовании trace >>> @trace(sys.stderr) ... def identity(x): ... return x • Использование sys.stdout для вывода по умолчанию. 3 / 35
  • 6. Декораторы и help: проблема >>> def identity(x): ... "I do nothing useful." ... return x ... >>> identity.__name__, identity.__doc__ ('identity', 'I do nothing useful as well.') >>> identity = trace(identity) >>> identity.__name__, identity.__doc__ ('inner, None) __module__ У любой функции в Python есть атрибут __module__, содержащий имя модуля, в котором функция была определена. Для функций, определённых в интерпретаторе, например: >>> identity.__module__ '__main__' 4 / 35
  • 7. Декораторы и help: решение в лоб • Давайте просто возьмём и установим “правильные” значения в атрибуты декорируемой функции: >>> def trace(func): ... def inner(*args, **kwargs): ... print(func.__name__, args, kwargs) ... return func(*args, **kwargs) ... inner.__module__ = func.__module__ ... inner.__name__ = func.__name__ ... inner.__doc__ = func.__doc__ ... return inner • Проверим >>> @trace ... def identity(x): ... "I do nothing useful." ... return x ... >>> identity.__name__, identity.__doc__ ('identity', 'I do nothing useful as well.') 5 / 35
  • 8. Декораторы и help: модуль functools • В модуле functools из стандартной библиотеки Python есть функция, реализующая логику копирования внутренних атрибутов >>> import functools >>> def trace(func): ... def inner(*args, **kwargs): ... print(func.__name__, args, kwargs) ... return func(*args, **kwargs) ... functools.update_wrapper(inner, func) ... return inner • То же самое можно сделать с помощью декоратора wraps >>> def trace(func): ... @functools.wraps(func) ... def inner(*args, **kwargs): ... print(func.__name__, args, kwargs) ... return func(*args, **kwargs) ... return inner 6 / 35
  • 9. Управление поведением trace • Заведём глобальную переменную trace_enabled и с её помощью будем отключать и включать trace. >>> trace_enabled = False >>> def trace(func): ... @functools.wraps(func) ... def inner(*args, **kwargs): ... print(func.__name__, args, kwargs) ... return func(*args, **kwargs) ... return inner if trace_enabled else func • Если trace выключен, то результатом применения декоратора является сама функция func — никаких дополнительных действий в момент её исполнения производиться не будет. 7 / 35
  • 10. Декораторы с аргументами: синтаксис • Напоминание: >>> @trace ... def identity(x): ... return x ≡ >>> def identity(x): ... return x ... >>> identity = trace(identity) • Для декораторов с аргументами эквивалентность сохраняется >>> @trace(sys.stderr) ... def identity(x): ... return x ≡ >>> def identity(x): ... return x ... >>> deco = trace(sys.stderr) >>> identity = deco(identity) 8 / 35
  • 11. Декораторы с аргументами: реализация >>> def trace(handle): ... def decorator(func): ... @functools.wraps(func) ... def inner(*args, **kwargs): ... print(func.__name__, args, kwargs, ... file=handle) ... return func(*args, **kwargs) ... return inner ... return decorator ... 9 / 35
  • 12. Декораторы с аргументами: @with_arguments • Можно обобщить логику декоратора с аргументами в виде декоратора with_arguments >>> def with_arguments(deco): ... @functools.wraps(deco) ... def wrapper(*dargs, **dkwargs): ... def decorator(func): ... result = deco(func, *dargs, **dkwargs) ... functools.update_wrapper(result, func) ... return result ... return decorator ... return wrapper • Что происходит: 1. with_arguments принимает декоратор deco, 2. заворачивает его во wrapper, так как deco — декоратор с аргументами, а затем в decorator. 3. decorator конструирует новый декоратор с помощью deco и копирует в него внутренние атрибуты функции func. 10 / 35
  • 13. Декораторы с аргументами: человечный trace >>> @with_arguments ... def trace(func, handle): ... def inner(*args, **kwargs): ... print(func.__name__, args, kwargs, file=handle) ... return func(*args, **kwargs) ... return inner ... >>> @trace(sys.stderr) ... def identity(x): ... return x Вопросы? 11 / 35
  • 14. Декораторы с опциональными аргументами: наивная версия • Почему бы просто не указать аргумент по умолчанию для параметра handle? >>> @with_arguments ... def trace(func, handle=sys.stdout): ... @functools.wraps(func) ... def inner(*args, **kwargs): ... print(func.__name__, args, kwargs, ... file=handle) ... return func(*args, **kwargs) ... return inner • Попробуем применить новую версию декоратора trace к функции identity >>> @trace ... def identity(x): ... return x ... >>> identity(42) <function trace.<locals>.inner at 0x10b3969d8> 12 / 35
  • 15. Декораторы с опциональными аргументами: работающая версия >>> @trace() ... def identity(x): ... return x ... >>> identity(42) identity (42,) {} 42 Вопрос Можно ли как-нибудь избавиться от лишних скобок? 13 / 35
  • 16. Декораторы с опциональными аргументами: магическая версия >>> def trace(func=None, *, handle=sys.stdout): ... # со скобками ... if func is None: ... return lambda func: trace(func, handle=handle) ... ... # без скобок ... @functools.wraps(func) ... def inner(*args, **kwargs): ... print(func.__name__, args, kwargs) ... return func(*args, **kwargs) ... return inner Вопрос Зачем требовать, чтобы аргументы декоратора были только ключевыми? 14 / 35
  • 17. “Теория” декораторов: резюме • Декоратор — способ модифицировать поведение функции, сохраняя читаемость кода. • Декораторы бывают: • без аргументов @trace • с аргументами @trace(sys.stderr) • с опциональными аргументами. 15 / 35
  • 19. Использование декораторов: @timethis >>> def timethis(func=None, *, n_iter=100): ... if func is None: ... return lambda func: timethis(func, n_iter=n_iter) ... ... @functools.wraps(func) ... def inner(*args, **kwargs): ... print(func.__name__, end=" ... ") ... acc = float("inf") ... for i in range(n_iter): ... tick = time.perf_counter() ... result = func(*args, **kwargs) ... acc = min(acc, time.perf_counter() - tick) ... print(acc) ... return result ... return inner ... >>> result = timethis(sum)(range(10 ** 6)) sum ... 0.026534789009019732 16 / 35
  • 20. Использование декораторов: @profiled >>> def profiled(func): ... @functools.wraps(func) ... def inner(*args, **kwargs): ... inner.ncalls += 1 ... return func(*args, **kwargs) ... ... inner.ncalls = 0 ... return inner ... >>> @profiled ... def identity(x): ... return x ... >>> identity(42) 42 >>> identity.ncalls 1 17 / 35
  • 21. Использование декораторов: @once >>> def once(func): ... @functools.wraps(func) ... def inner(*args, **kwargs): ... if not inner.called: ... func(*args, **kwargs) ... inner.called = True ... inner.called = False ... return inner ... >>> @once ... def initialize_settings(): ... print("Settings initialized.") ... >>> initialize_settings() Settings initialized. >>> initialize_settings() Вопрос Как модифицировать декоратор @once, чтобы он поддерживал функции, возвращающие не None значения? 18 / 35
  • 22. Использование декораторов: @memoized • Мемоизация — сохранение результатов выполнения функции для предотвращения избыточных вычислений. • Напишем декоратор для автоматической мемоизации “любой” функции. >>> def memoized(func): ... cache = {} ... ... @functools.wraps(func) ... def inner(*args, **kwargs): ... key = args, kwargs ... if key not in cache: ... cache[key] = func(*args, **kwargs) ... return cache[key] ... return inner 19 / 35
  • 23. Использование декораторов: @memoized и функция Аккермана >>> @memoized ... def ackermann(m, n): ... if not m: ... return n + 1 ... elif not n: ... return ackermann(m - 1, 1) ... else: ... return ackermann(m - 1, ackerman(m, n - 1)) ... >>> ackermann(3, 4) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 6, in inner TypeError: unhashable type: 'dict' Вопрос Что делать? 20 / 35
  • 24. Использование декораторов: снова @memoized • Частное решение проблемы1: >>> def memoized(func): ... cache = {} ... ... @functools.wraps(func) ... def inner(*args, **kwargs): ... key = args + tuple(sorted(kwargs.items())) ... if key not in cache: ... cache[key] = func(*args, **kwargs) ... return cache[key] ... return inner ... >>> ackermann(3, 4) 125 • Нет универсального и быстрого решения. Можно сериализовывать аргументы в строку, например, через str или, что более осмысленно, через pickle. 1 Кстати, почему частное? 21 / 35
  • 25. Использование декораторов: @deprecated >>> def deprecated(func): ... code = func.__code__ ... warnings.warn_explicit( ... func.__name__ + " is deprecated.", ... category=DeprecationWarning, ... filename=code.co_filename, ... lineno=code.co_firstlineno + 1) ... return func ... >>> @deprecated ... def identity(x): ... return x ... <stdin>:2: DeprecationWarning: identity is deprecated. 22 / 35
  • 26. Использование декораторов: контракты pre и post • Контрактное программирование — способ проектирования программ, основывающийся на формальном описании интерфейсов в терминах предусловий, постусловий и инвариантов. • В Python контрактное программирование можно реализовать в виде библиотеки декораторов2: >>> @pre(lambda x: x >= 0, "negative argument") ... def checked_log(x): ... pass ... >>> is_not_nan = post(lambda r: not math.isnan(r), ... "not a number") >>> @is_not_nan ... def something_useful(): ... pass ... 2 https://pypi.python.org/pypi/contracts 23 / 35
  • 27. Использование декораторов: реализация @pre >>> def pre(cond, message): ... def wrapper(func): ... @functools.wraps(func) ... def inner(*args, **kwargs): ... assert cond(*args, **kwargs), message ... return func(*args, **kwargs) ... return inner ... return wrapper ... >>> @pre(lambda x: r >= 0, "negative argument") ... def checked_log(x): ... return math.log(x) ... >>> checked_log(-42) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in inner AssertionError: negative argument 24 / 35
  • 28. Использование декораторов: реализация @post >>> def post(cond, message): ... def wrapper(func): ... @functools.wraps(func) ... def inner(*args, **kwargs): ... result = func(*args, **kwargs) ... assert cond(result), message ... return result ... return inner ... return wrapper ... >>> @post(lambda x: not math.isnan(x), "not a number") ... def something_useful(): ... return float("nan") ... >>> something_useful() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 6, in inner AssertionError: not a number 25 / 35
  • 29. Цепочки декораторов • Синтаксис Python разрешает одновременное применение нескольких декораторов. • Порядок декораторов имеет значение: >>> def square(func): ... return lambda x: func(x * x) ... >>> def addsome(func): ... return lambda x: func(x + 42) ... >>> @square ... @addsome ... def identity(x): ... return x ... >>> identity(2) 46 >>> @addsome ... @square ... def identity(x): ... return x ... >>> identity(2) 1936 26 / 35
  • 30. Использование декораторов: резюме • Декораторы в мире Python вездесущи и полезны. • Больше примеров можно найти по ссылке3 и практически в любой библиотеке на Python. 3 https://wiki.python.org/moin/PythonDecoratorLibrary 27 / 35
  • 32. Модуль functools: lru_cache • Родственник уже рассмотренного memoized, сохраняющий фиксированное количество последних вызовов. • Познакомим lru_cache с функцией Аккермана >>> @functools.lru_cache(maxsize=64) ... def ackermann(m, n): ... # ... ... >>> ackermann(3, 4) 125 >>> ackermann.cache_info() CacheInfo(hits=65, misses=315, maxsize=64, currsize=64) • Можно не ограничивать количество сохраняемых вызовов, тогда мы получим в точности поведение memoized4: >>> @functools.lru_cache(maxsize=None) ... def ackermann(m, n): ... # ... ... 4 Почему использовать None в качестве значения по умолчанию для maxsize — плохая идея? 28 / 35
  • 33. Модуль functools: partial • С помощью partial можно зафиксировать часть позиционных и ключевых аргументов в функции. • Выполнившие домашнее задание узнают в partial расширение функции curry. • Пример: >>> f = functools.partial(sorted, key=lambda p: p[1]) >>> f([("a", 4), ("b", 2)]) [('b', 2), ('a', 4)] >>> g = functools.partial(sorted, [2, 3, 1, 4]) >>> g() [1, 2, 3, 4] 29 / 35
  • 34. Модуль functools: обобщённые функции5 • Функция len называется обобщённой, так как её реализация может быть специализирована для конкретного типа. >>> len([1, 2, 3, 4]) 4 >>> len({1, 2, 3, 4}) 4 >>> len(range(4)) 4 • Примеры обобщённых функций в Python: >>> str([1, 2, 3, 4]) '[1, 2, 3, 4]' >>> hash((1, 2, 3, 4)) 485696759010151909 >>> sum([[1], [2]], []) [1, 2] • Как реализовать свою обобщённую функцию? 5 http://python.org/dev/peps/pep-0443 30 / 35
  • 35. Модуль functools: singledispatch • В качестве примера реализуем функцию pack, которая сериализует объект в компактное строковое представление >>> @functools.singledispatch ... def pack(obj): ... type_name = type(obj).__name__ ... assert False, "Unsupported type: " + type_name • Научим функцию pack сериализовывать числа и списки >>> @pack.register(int) ... def _(obj): ... return b"I" + hex(obj).encode("ascii") ... >>> @pack.register(list) ... def _(obj): ... return b"L" + b",".join(map(pack, obj)) ... >>> pack([1, 2, 3]) b'LI0x1,I0x2,I0x3' >>> pack(42.) AssertionError: Unsupported type: float 31 / 35
  • 36. Модуль functools: мотивация для reduce • Рассмотрим: >>> sum([1, 2, 3, 4], start=0) 10 >>> (((0 + 1) + 2) + 3) + 4 10 • А что, если мы хотим использовать другую бинарную операцию, например, умножение? >>> ((1 * 2) * 3) * 4 24 • Функция reduce обобщает логику функции sum на произвольную бинарную операцию. >>> functools.reduce(lambda acc, x: acc * x, ... [1, 2, 3, 4]) 24 32 / 35
  • 37. Модуль functools: подробнее о reduce • Функция reduce принимает три аргумента: бинарную функцию, последовательность и опциональное начальное значение. • Вычисление reduce(op, xs, initial) можно схематично представить как: >>> op(op(op(op(init, xs[0]), xs[1]), xs[2]), ...) >>> op(op(op(xs[0], xs[1]), xs[2]), ...) • Несколько примеров: >>> functools.reduce(lambda acc, d: 10 * acc + int(d), ... "1914", initial=0) 1914 >>> functools.reduce(merge, [[1, 2, 7], [5, 6], [0]]) [0, 1, 2, 5, 6, 7] 33 / 35
  • 38. Модуль functools: reduce и философия • Несмотря на свою популярность в функциональных языках, в Python довольно сложно придумать полезный пример использования reduce. • Резюме про reduce: • работает с любым объектом, поддерживающим протокол итератора; • работает слева направо; • использует первый элемент последовательности, если начальное значение не указано явно. 34 / 35
  • 39. Модуль functools: резюме • Модуль functools украшает будни любителя функционального программирования. • Мы поговорили про: • lru_cache • partial • singledispatch • reduce 35 / 35