HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>В первой части серии публикаций о продвинутых возможностях Python мы познакомились с<a>итераторами, генераторами и модулем itertools</a>. В сегодняшней публикации речь пойдёт о замыканиях, декораторах и модуле functools.</p>
1 <p>В первой части серии публикаций о продвинутых возможностях Python мы познакомились с<a>итераторами, генераторами и модулем itertools</a>. В сегодняшней публикации речь пойдёт о замыканиях, декораторах и модуле functools.</p>
2 <h2>Содержание</h2>
2 <h2>Содержание</h2>
3 <ul><li><a>Декораторы</a></li>
3 <ul><li><a>Декораторы</a></li>
4 <li><a>Декораторы класса</a></li>
4 <li><a>Декораторы класса</a></li>
5 <li><a>Несколько примеров из Flask</a></li>
5 <li><a>Несколько примеров из Flask</a></li>
6 <li><a>Дополнительное чтение</a></li>
6 <li><a>Дополнительное чтение</a></li>
7 <li><a>Приложение: замыкания</a></li>
7 <li><a>Приложение: замыкания</a></li>
8 </ul><h2>Декораторы</h2>
8 </ul><h2>Декораторы</h2>
9 <p>Декоратор - паттерн проектирования, при использовании которого класс или функция изменяет или дополняет функциональность другого класса или функции без использования наследования или прямого изменения исходного кода. В Python декораторы представляют собой функции или любые вызываемые объекты, которые принимают на вход набор необязательных аргументов и функцию или класс и возвращают функцию или класс. Их можно использовать для реализации паттерна проектирования декоратора или для решения других задач. Декораторы классов появились в Python 2.6.</p>
9 <p>Декоратор - паттерн проектирования, при использовании которого класс или функция изменяет или дополняет функциональность другого класса или функции без использования наследования или прямого изменения исходного кода. В Python декораторы представляют собой функции или любые вызываемые объекты, которые принимают на вход набор необязательных аргументов и функцию или класс и возвращают функцию или класс. Их можно использовать для реализации паттерна проектирования декоратора или для решения других задач. Декораторы классов появились в Python 2.6.</p>
10 <p>Кстати, если вы не знакомы с замыканиями Python, прежде чем читать дальше ознакомьтесь с дополнением о замыканиях в конце этой статьи. Концепцию декораторов сложно понять, если вы не знакомы с замыканиями.</p>
10 <p>Кстати, если вы не знакомы с замыканиями Python, прежде чем читать дальше ознакомьтесь с дополнением о замыканиях в конце этой статьи. Концепцию декораторов сложно понять, если вы не знакомы с замыканиями.</p>
11 <p>В Python декораторы применяются к функции или классу с помощью символа @. В качестве первого примера давайте используем простой декоратор, который регистрирует вызовы функций. В этом примере декоратор принимает формат времени в качестве аргумента и печатает лог перед и после выполнения декорированной функции с временем исполнения. Это может быть кстати, когда вы сравниваете эффективность разных реализаций алгоритма или разных алгоритмов.</p>
11 <p>В Python декораторы применяются к функции или классу с помощью символа @. В качестве первого примера давайте используем простой декоратор, который регистрирует вызовы функций. В этом примере декоратор принимает формат времени в качестве аргумента и печатает лог перед и после выполнения декорированной функции с временем исполнения. Это может быть кстати, когда вы сравниваете эффективность разных реализаций алгоритма или разных алгоритмов.</p>
12 <p>Посмотрите на пример использования. Здесь функции add1 и add2 оформлены с помощью logged, а также дан пример вывода. Заметьте, что формат времени хранится в замыкании возвращаемых функций с декоратором. Поэтому понимание замыканий необходимо для понимания декораторов Python.</p>
12 <p>Посмотрите на пример использования. Здесь функции add1 и add2 оформлены с помощью logged, а также дан пример вывода. Заметьте, что формат времени хранится в замыкании возвращаемых функций с декоратором. Поэтому понимание замыканий необходимо для понимания декораторов Python.</p>
13 <p>Также обратите внимание, как имя возвращаемой функции заменяется именем оригинальной функции в случае, если оно используется позже. Python не делает этого по умолчанию.</p>
13 <p>Также обратите внимание, как имя возвращаемой функции заменяется именем оригинальной функции в случае, если оно используется позже. Python не делает этого по умолчанию.</p>
14 <p>Если вы достаточно внимательны, то заметите, что мы заботимся, чтобы у возвращаемой функции был правильно указан __name__, но не заботимся о __doc__ или __module__. Поэтому если у функции add есть строка документации, она потеряется. Как можно этого избежать? Мы могли бы справиться с проблемой так же, как при обработке __name__. Но выполнять такие операции с каждым декоратором утомительно. Поэтому в модуле functools есть декоратор wraps, который срабатывает именно в таком сценарии. Использование декоратора внутри другого декоратора может показаться странным. Но если вы думаете о декораторах как о функциях, которые принимают функции в качестве параметров и возвращают функции, всё становится на места. Декоратор wraps используется в следующих примерах вместо ручной обработки __name__ и других подобных атрибутов.</p>
14 <p>Если вы достаточно внимательны, то заметите, что мы заботимся, чтобы у возвращаемой функции был правильно указан __name__, но не заботимся о __doc__ или __module__. Поэтому если у функции add есть строка документации, она потеряется. Как можно этого избежать? Мы могли бы справиться с проблемой так же, как при обработке __name__. Но выполнять такие операции с каждым декоратором утомительно. Поэтому в модуле functools есть декоратор wraps, который срабатывает именно в таком сценарии. Использование декоратора внутри другого декоратора может показаться странным. Но если вы думаете о декораторах как о функциях, которые принимают функции в качестве параметров и возвращают функции, всё становится на места. Декоратор wraps используется в следующих примерах вместо ручной обработки __name__ и других подобных атрибутов.</p>
15 <p>Следующий пример немного сложнее. Давайте напишем декоратор, который кэширует результат вызова функции в течение указанного в секундах времени. Код ожидает, что переданные в функцию аргументы - хэшируемые объекты (hashable objects), потому что мы используем кортеж с аргументами args в качестве первого параметра и замороженный набор элементов в kwargs в качестве второго параметра, который выступает ключом кэша. У каждой функции будет уникальный кэш dict, который хранится в замыкании функции.</p>
15 <p>Следующий пример немного сложнее. Давайте напишем декоратор, который кэширует результат вызова функции в течение указанного в секундах времени. Код ожидает, что переданные в функцию аргументы - хэшируемые объекты (hashable objects), потому что мы используем кортеж с аргументами args в качестве первого параметра и замороженный набор элементов в kwargs в качестве второго параметра, который выступает ключом кэша. У каждой функции будет уникальный кэш dict, который хранится в замыкании функции.</p>
16 <p>Вот как это используется. Мы применяем декоратор к наивному и неэффективному калькулятору чисел Фибоначчи. Декоратор кэша эффективно применяет к коду паттерн мемоизации. Обратите внимание, что в замыкании fib находятся кэш dict, ссылка на исходную функцию fib, значение аргумента logged, а также значение аргумента timeout. dump_closure описывается в конце статьи после раздела о замыканиях.</p>
16 <p>Вот как это используется. Мы применяем декоратор к наивному и неэффективному калькулятору чисел Фибоначчи. Декоратор кэша эффективно применяет к коду паттерн мемоизации. Обратите внимание, что в замыкании fib находятся кэш dict, ссылка на исходную функцию fib, значение аргумента logged, а также значение аргумента timeout. dump_closure описывается в конце статьи после раздела о замыканиях.</p>
17 <h2>Декораторы класса</h2>
17 <h2>Декораторы класса</h2>
18 <p>В предыдущем разделе мы рассмотрели декораторы функций и некоторые необычные способы их применения. Теперь давайте рассмотрим декораторы классов. В данном случае декоратор принимает на вход класс (объект с типом type в Python) и возвращает модифицированный класс.</p>
18 <p>В предыдущем разделе мы рассмотрели декораторы функций и некоторые необычные способы их применения. Теперь давайте рассмотрим декораторы классов. В данном случае декоратор принимает на вход класс (объект с типом type в Python) и возвращает модифицированный класс.</p>
19 <p>Первый пример - простая математика. Дано частично упорядоченное множество P. Мы определяем Pd как дуальность P, исключительно если P(x,y)⟺Pd(y,x). Другими словами, речь идёт об обратном порядке. Как можно реализовать это с помощью Python? Предположим, класс определяет порядок с помощью методов __lt__, __le__ и так далее. Тогда мы можем написать декоратор класса, который заменяет каждую функцию её дуальностью.</p>
19 <p>Первый пример - простая математика. Дано частично упорядоченное множество P. Мы определяем Pd как дуальность P, исключительно если P(x,y)⟺Pd(y,x). Другими словами, речь идёт об обратном порядке. Как можно реализовать это с помощью Python? Предположим, класс определяет порядок с помощью методов __lt__, __le__ и так далее. Тогда мы можем написать декоратор класса, который заменяет каждую функцию её дуальностью.</p>
20 <p>Вот как это можно применить к str, чтобы создать новый класс rstr, в котором используется обратный лексикографический порядок.</p>
20 <p>Вот как это можно применить к str, чтобы создать новый класс rstr, в котором используется обратный лексикографический порядок.</p>
21 <p>Давайте посмотрим на более сложный пример. Предположим, мы хотим применить декоратор logged из предыдущего примера ко всем методам в классе. Это можно сделать вручную: просто добавить декоратор в каждый метод. Также можно автоматизировать процесс с помощью декоратора класса. Прежде чем сделать это, автор улучшил декоратор logged из предыдущего раздела. Теперь в нём используется атрибут wraps из модуля functools вместо ручной работы с __name__. Также здесь в возвращаемую функцию добавлен атрибут _logged_decorator. Его значение True, он применяется, чтобы избежать двойного декорирования функции. Это удобно, когда мы применяем декоратор к классам, которые должны наследовать методы от других классов. Наконец, добавлен аргумент name_prefix, который делает возможной кастомизацию сообщений лога.</p>
21 <p>Давайте посмотрим на более сложный пример. Предположим, мы хотим применить декоратор logged из предыдущего примера ко всем методам в классе. Это можно сделать вручную: просто добавить декоратор в каждый метод. Также можно автоматизировать процесс с помощью декоратора класса. Прежде чем сделать это, автор улучшил декоратор logged из предыдущего раздела. Теперь в нём используется атрибут wraps из модуля functools вместо ручной работы с __name__. Также здесь в возвращаемую функцию добавлен атрибут _logged_decorator. Его значение True, он применяется, чтобы избежать двойного декорирования функции. Это удобно, когда мы применяем декоратор к классам, которые должны наследовать методы от других классов. Наконец, добавлен аргумент name_prefix, который делает возможной кастомизацию сообщений лога.</p>
22 <p>Теперь можно написать декоратор класса.</p>
22 <p>Теперь можно написать декоратор класса.</p>
23 <p>Вот как он будет использоваться. Обратите внимание, как здесь обрабатываются переопределённые методы и наследование.</p>
23 <p>Вот как он будет использоваться. Обратите внимание, как здесь обрабатываются переопределённые методы и наследование.</p>
24 <p>Наш первый пример декораторов класса должен был изменять порядок методов класса. Похожий декоратор, но более полезный, может принимать один из __lt__, __le__, __gt__ или __ge__ и __eq__, и реализовывать остальные для полного упорядочивания класса. Это именно то, что делает декоратор functools.total_ordering. Подробности<a>в документации</a>.</p>
24 <p>Наш первый пример декораторов класса должен был изменять порядок методов класса. Похожий декоратор, но более полезный, может принимать один из __lt__, __le__, __gt__ или __ge__ и __eq__, и реализовывать остальные для полного упорядочивания класса. Это именно то, что делает декоратор functools.total_ordering. Подробности<a>в документации</a>.</p>
25 <h2>Несколько примеров из Flask</h2>
25 <h2>Несколько примеров из Flask</h2>
26 <p>Рассмотрим несколько интересных примеров использования декораторов в<a>Flask</a>.</p>
26 <p>Рассмотрим несколько интересных примеров использования декораторов в<a>Flask</a>.</p>
27 <p>Представьте, что хотите, чтобы некоторые функции выводили предупреждающие сообщения, если они вызываются при определённых обстоятельствах в режиме отладки. Вместо того, чтобы вручную добавлять код в начало каждой функции, можно использовать декоратор. Это то, что делает декоратор, который можно найти в файле app.py Flask.</p>
27 <p>Представьте, что хотите, чтобы некоторые функции выводили предупреждающие сообщения, если они вызываются при определённых обстоятельствах в режиме отладки. Вместо того, чтобы вручную добавлять код в начало каждой функции, можно использовать декоратор. Это то, что делает декоратор, который можно найти в файле app.py Flask.</p>
28 <p>Более интересный пример - декоратор Flask route, который определяется в классе Flask. Заметьте, что декоратор может быть методом класса. В этом случае в качестве первого параметра используется self. Полный код смотрите в файле app.py. Обратите внимание, декоратор просто регистрирует декорированную функцию как обработчик URL с помощью вызова функции add_url_rule.</p>
28 <p>Более интересный пример - декоратор Flask route, который определяется в классе Flask. Заметьте, что декоратор может быть методом класса. В этом случае в качестве первого параметра используется self. Полный код смотрите в файле app.py. Обратите внимание, декоратор просто регистрирует декорированную функцию как обработчик URL с помощью вызова функции add_url_rule.</p>
29 <h2>Дополнительное чтение</h2>
29 <h2>Дополнительное чтение</h2>
30 <p>Много информации о декораторах вы найдёте на официальной<a>вики-странице</a>Python. Также можно посмотреть замечательное видео Дэвида Безли<a>о метапрограммировании в Python 3</a>.</p>
30 <p>Много информации о декораторах вы найдёте на официальной<a>вики-странице</a>Python. Также можно посмотреть замечательное видео Дэвида Безли<a>о метапрограммировании в Python 3</a>.</p>
31 <h2>Приложение: замыкания</h2>
31 <h2>Приложение: замыкания</h2>
32 <p>Замыкание - это комбинация функции и множества ссылок на переменные в области видимости функции. Последнее иногда называют ссылочной средой. Замыкание позволяет выполнять функцию за пределами области видимости. В Python ссылочная среда хранится в виде набора ячеек. Доступ к ним можно получить с помощью атрибутов func_closure или __closure__. В Python 3 используется только __closure__.</p>
32 <p>Замыкание - это комбинация функции и множества ссылок на переменные в области видимости функции. Последнее иногда называют ссылочной средой. Замыкание позволяет выполнять функцию за пределами области видимости. В Python ссылочная среда хранится в виде набора ячеек. Доступ к ним можно получить с помощью атрибутов func_closure или __closure__. В Python 3 используется только __closure__.</p>
33 <p>Важно понимать, что речь идёт просто о ссылках, а не о глубоких копиях объектов. Конечно, неважно, являются ли объекты неизменяемыми, но для изменяемых объектов, например, списков, это важно. Это иллюстрирует пример ниже. Обратите внимание, у функций также есть __globals__, где хранится глобальное ссылочное окружение, для которого была определена функция. Посмотрите на простой пример:</p>
33 <p>Важно понимать, что речь идёт просто о ссылках, а не о глубоких копиях объектов. Конечно, неважно, являются ли объекты неизменяемыми, но для изменяемых объектов, например, списков, это важно. Это иллюстрирует пример ниже. Обратите внимание, у функций также есть __globals__, где хранится глобальное ссылочное окружение, для которого была определена функция. Посмотрите на простой пример:</p>
34 <p>Ещё один пример, более сложный. Убедитесь, что понимаете, почему код работает именно так.</p>
34 <p>Ещё один пример, более сложный. Убедитесь, что понимаете, почему код работает именно так.</p>
35 <p>Наконец, вот пример метода dump_closure, который использовался выше.</p>
35 <p>Наконец, вот пример метода dump_closure, который использовался выше.</p>
36 <p><em>Адаптированный перевод статьи<a>A Study of Python's More Advanced Features Part II: Closures, Decorators and functools by Sahand Saba</a>. Мнение автора оригинальной публикации может не совпадать с мнением администрации "Хекслета".</em></p>
36 <p><em>Адаптированный перевод статьи<a>A Study of Python's More Advanced Features Part II: Closures, Decorators and functools by Sahand Saba</a>. Мнение автора оригинальной публикации может не совпадать с мнением администрации "Хекслета".</em></p>