Внутренние функции Python: Для чего они нужны?
Оглавление
- Создание внутренних функций Python
- Использование внутренних функций: Основы
- Удерживающее состояние С внутренними функциями: Затворы
- Добавление поведения с помощью внутренних функций: Декораторы
- Заключение
Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Внутренних функций Python
Внутренние функции, также известные как вложенные функции - это функции которые вы определяете внутри других функций. В Python функции такого типа имеют прямой доступ к переменным и именам, определенным во включающей функции. Внутренние функции имеют множество применений, в первую очередь в качестве фабрик замыкания и функций декоратора.
В этом руководстве вы узнаете, как:
- Обеспечьте инкапсуляцию и скройте свои функции от внешнего доступа
- Напишите вспомогательных функций для облегчения повторного использования кода
- Создать функции фабрики замыканий, которые сохраняют состояние между вызовами
- Код функции декоратора для добавления поведения к существующим функциям
Бесплатный бонус: Нажмите здесь, чтобы получить шпаргалку по Python и изучить основы Python 3, такие как работа с типами данных, словари, списки и функции Python.
Создание внутренних функций Python
Функция, определенная внутри другой функции, называется внутренней функцией или вложенной функцией. В Python функция такого типа может обращаться к именам во включающей функции. Вот пример того, как создать внутреннюю функцию в Python:
>>> def outer_func(): ... def inner_func(): ... print("Hello, World!") ... inner_func() ... >>> outer_func() Hello, World!предварительно> кодовый блок>В этом коде вы определяете
inner_func()внутриouter_func(), чтобы вывести сообщениеHello, World!на экран. Для этого вы вызываетеinner_func()в последней строкеouter_func(). Это самый быстрый способ написать внутреннюю функцию в Python. Однако внутренние функции предоставляют множество интересных возможностей, выходящих за рамки того, что вы видите в этом примере.Основной особенностью внутренних функций является их способность обращаться к переменным и объектам из заключенной в них функции даже после того, как эта функция вернулась. Заключающая функция предоставляет пространство имен, доступное для внутренней функции:
>>> def outer_func(who): ... def inner_func(): ... print(f"Hello, {who}") ... inner_func() ... >>> outer_func("World!") Hello, World!предварительно> кодовый блок>Теперь вы можете передать строку в качестве аргумента
outer_func(), иinner_func()получит доступ к этому аргументу через имяwho. Это имя, однако, определено в локальной области изouter_func(). Имена, которые вы определяете в локальной области видимости внешней функции, называются нелокальными именами. Они нелокальны с точки зренияinner_func().Вот пример того, как создать и использовать более сложную внутреннюю функцию:
>>> def factorial(number): ... # Validate input ... if not isinstance(number, int): ... raise TypeError("Sorry. 'number' must be an integer.") ... if number < 0: ... raise ValueError("Sorry. 'number' must be zero or positive.") ... # Calculate the factorial of number ... def inner_factorial(number): ... if number <= 1: ... return 1 ... return number * inner_factorial(number - 1) ... return inner_factorial(number) ... >>> factorial(4) 24предварительно> кодовый блок>В
factorial()вы сначала проверяете входные данные, чтобы убедиться, что ваш пользователь вводит целое число, равное нулю или превышающее его. Затем вы определяете рекурсивную внутреннюю функцию с именемinner_factorial(), которая выполняет вычисление факториала и возвращает результат. Заключительным шагом является вызовinner_factorial().Примечание: Для более подробного обсуждения рекурсии и рекурсивных функций ознакомьтесь с Рекурсивное мышление в Python и Рекурсия в Python: введение.
Основное преимущество использования этого шаблона заключается в том, что, выполняя всю проверку аргументов во внешней функции, вы можете безопасно пропустить проверку ошибок во внутренней функции и сосредоточиться на текущих вычислениях.
Использование внутренних функций: Основы
Варианты использования внутренних функций Python разнообразны. Вы можете использовать их, чтобы обеспечить инкапсуляцию и скрыть свои функции от внешнего доступа, вы можете написать вспомогательные внутренние функции, а также создать замыкания и декораторы. В этом разделе вы узнаете о двух предыдущих вариантах использования внутренних функций, а в последующих разделах вы узнаете, как создавать функции фабрики замыканий и декораторы.
Обеспечение инкапсуляции
Распространенный вариант использования внутренних функций возникает, когда вам нужно защитить или скрыть данную функцию от всего, что происходит за ее пределами, чтобы функция была полностью скрыта от глобальной области . Такое поведение обычно известно как инкапсуляция.
Вот пример, который подчеркивает эту концепцию:
>>> def increment(number): ... def inner_increment(): ... return number + 1 ... return inner_increment() ... >>> increment(10) 11 >>> # Call inner_increment() >>> inner_increment() Traceback (most recent call last): File "<input>", line 1, in <module> inner_increment() NameError: name 'inner_increment' is not definedпредварительно> кодовый блок>В этом примере вы не можете получить доступ к
inner_increment()напрямую. Если вы попытаетесь это сделать, то получитеNameError. Это потому, чтоincrement()полностью скрываетinner_increment(), не позволяя вам получить к нему доступ из глобальной области видимости.Создание вспомогательных внутренних функций
Иногда у вас есть функция, которая выполняет один и тот же фрагмент кода в нескольких местах своего тела. Например, предположим, что вы хотите написать функцию для обработки CSV-файла, содержащего информацию о точках доступа Wi-Fi в Нью-Йорке. Чтобы узнать общее количество точек доступа в Нью-Йорке, а также компанию, предоставляющую большинство из них, вы создаете следующий скрипт:
# hotspots.py import csv from collections import Counter def process_hotspots(file): def most_common_provider(file_obj): hotspots = [] with file_obj as csv_file: content = csv.DictReader(csv_file) for row in content: hotspots.append(row["Provider"]) counter = Counter(hotspots) print( f"There are {len(hotspots)} Wi-Fi hotspots in NYC.\n" f"{counter.most_common(1)[0][0]} has the most with " f"{counter.most_common(1)[0][1]}." ) if isinstance(file, str): # Got a string-based filepath file_obj = open(file, "r") most_common_provider(file_obj) else: # Got a file object most_common_provider(file)предварительно> кодовый блок>Здесь
process_hotspots()принимаетfileв качестве аргумента. Функция проверяет, является лиfileстроковым путем к физическому файлу или файловым объектом. Затем он вызывает вспомогательную внутреннюю функциюmost_common_provider(), которая принимает файловый объект и выполняет следующие операции:
- Считайте содержимое файла с помощью генератора, который выдает словарей, используя
csv.DictReader.- Создайте список поставщиков услуг Wi-Fi.
- Подсчитайте количество точек доступа Wi-Fi у каждого провайдера, используя
collections.Counterobject.- Распечатайте сообщение с полученной информацией.
Если вы запустите функцию, то получите следующий результат:
>>> from hotspots import process_hotspots >>> file_obj = open("./NYC_Wi-Fi_Hotspot_Locations.csv", "r") >>> process_hotspots(file_obj) There are 3319 Wi-Fi hotspots in NYC. LinkNYC - Citybridge has the most with 1868. >>> process_hotspots("./NYC_Wi-Fi_Hotspot_Locations.csv") There are 3319 Wi-Fi hotspots in NYC. LinkNYC - Citybridge has the most with 1868.предварительно> кодовый блок>Независимо от того, вызываете ли вы
process_hotspots(), используя путь к файлу на основе строки или файловый объект, вы получите один и тот же результат.Использование внутренних и частных вспомогательных функций
Обычно вы создаете вспомогательные внутренние функции, такие как
most_common_provider(), когда хотите обеспечить инкапсуляцию. Вы также можете создавать внутренние функции, если считаете, что не собираетесь вызывать их где-либо еще, кроме содержащей функции.Хотя написание вспомогательных функций как внутренних позволяет достичь желаемого результата, вам, вероятно, будет лучше, если вы будете извлекать их как функции верхнего уровня. В этом случае вы могли бы использовать начальное подчеркивание (
_) в названии функции, чтобы указать, что она является частной для текущего модуля или класса. Это позволит вам получить доступ к вашим вспомогательным функциям из любого другого места текущего модуля или класса и повторно использовать их по мере необходимости.Преобразование внутренних функций в частные функции верхнего уровня может сделать ваш код более чистым и удобочитаемым. Такой подход позволяет создавать функции, в которых, следовательно, применяется принцип единой ответственности.
Сохранение состояния с помощью внутренних функций: Замыкания
В Python функции являются первоклассными гражданами. Это означает, что они находятся на одном уровне с любыми другими объектами, такими как числа, строки, списки, кортежи, модули и так далее. Вы можете динамически создавать или уничтожать их, сохранять в структурах данных, передавать их в качестве аргументов другим функциям, использовать их в качестве возвращаемых значений и так далее.
Вы также можете создавать функции более высокого порядка в Python. Функции более высокого порядка - это функции, которые работают с другими функциями, используя их в качестве аргументов, возвращая их или и то, и другое вместе.
Все примеры внутренних функций, которые вы видели до сих пор, были обычными функциями, которые просто оказались вложенными в другие функции. Если вам не нужно скрывать свои функции от внешнего мира, нет особой причины для их вложенности. Вы могли бы определить эти функции как частные функции верхнего уровня, и все было бы в порядке.
В этом разделе вы узнаете о функциях фабрики замыканий. Замыкания - это динамически создаваемые функции, которые возвращаются другими функциями. Их главная особенность заключается в том, что они имеют полный доступ к переменным и именам, определенным в локальном пространстве имен, в котором было создано замыкание, даже если заключающая функция вернулась и завершила выполнение.
В Python, когда вы возвращаете внутренний объект function, интерпретатор упаковывает функцию вместе с содержащей ее средой или замыканием. Объект function сохраняет моментальный снимок всех переменных и имен, определенных в содержащей его области видимости. Чтобы определить замыкание, вам нужно выполнить три шага:
- Создайте внутреннюю функцию.
- Ссылайтесь на переменные из заключающей их функции.
- Возвращает внутреннюю функцию.
Обладая этими базовыми знаниями, вы можете сразу приступить к созданию своих замыканий и воспользоваться их главной особенностью: сохранение состояния между вызовами функций.
Сохранение состояния при закрытии
Замыкание приводит к тому, что внутренняя функция сохраняет состояние своего окружения при вызове. Замыкание - это не сама внутренняя функция, а внутренняя функция вместе с окружающим ее окружением. Замыкание фиксирует локальные переменные и name в содержащей их функции и сохраняет их неизменными.
Рассмотрим следующий пример:
1# powers.py 2 3def generate_power(exponent): 4 def power(base): 5 return base ** exponent 6 return powerпредварительно> кодовый блок>Вот что происходит в этой функции:
- Строка 3 создает
generate_power(), которая является функцией фабрики замыкания. Это означает, что он создает новое замыкание при каждом вызове, а затем возвращает его вызывающей стороне.- Строка 4 определяет
power(), которая является внутренней функцией, принимающей единственный аргументbaseи возвращающей результат выраженияbase ** exponent.- Строка 6 возвращает
powerкак объект функции, не вызывая его.Откуда
power()берется значениеexponent? Вот тут-то и вступает в действие замыкание. В этом примереpower()получает значениеexponentиз внешней функцииgenerate_power(). Вот что делает Python, когда вы вызываетеgenerate_power():
- Определите новый экземпляр
power(), который принимает один аргументbase.- Сделайте снимок окружающего состояния
power(), которое включает в себяexponentс его текущим значением.- Верните
power()вместе со всем окружающим его состоянием.Таким образом, когда вы вызываете экземпляр
power(), возвращаемыйgenerate_power(), вы увидите, что функция запоминает значениеexponent:>>> from powers import generate_power >>> raise_two = generate_power(2) >>> raise_three = generate_power(3) >>> raise_two(4) 16 >>> raise_two(5) 25 >>> raise_three(4) 64 >>> raise_three(5) 125предварительно> кодовый блок>В этих примерах
raise_two()запоминает, чтоexponent=2, аraise_three()запоминает, чтоexponent=3. Обратите внимание, что оба замыкания запоминают свои соответствующиеexponentмежду вызовами.Теперь рассмотрим другой пример:
>>> def has_permission(page): ... def permission(username): ... if username.lower() == "admin": ... return f"'{username}' has access to {page}." ... else: ... return f"'{username}' doesn't have access to {page}." ... return permission ... >>> check_admin_page_permision = has_permission("Admin Page") >>> check_admin_page_permision("admin") "'admin' has access to Admin Page." >>> check_admin_page_permision("john") "'john' doesn't have access to Admin Page."предварительно> кодовый блок>Внутренняя функция проверяет, есть ли у данного пользователя правильные разрешения для доступа к данной странице. Вы можете быстро изменить это, чтобы захватить пользователя во время сеанса и проверить, есть ли у него правильные учетные данные для доступа к определенному маршруту.
Вместо того, чтобы проверять, соответствует ли пользователь
"admin", вы могли бы запросить базу данных SQL, чтобы проверить разрешение, а затем вернуть правильное представление в зависимости от того, указаны ли учетные данные правильно.Обычно вы создаете замыкания, которые не изменяют свое замкнутое состояние, или замыкания со статическим замкнутым состоянием , как вы видели в приведенных выше примерах. Однако вы также можете создавать замыкания, которые изменяют свое заключенное состояние, используя изменяемые объекты, такие как словари, наборы или списки.
Предположим, вам нужно вычислить среднее значение для набора данных. Данные поступают в виде потока последовательных измерений анализируемого параметра, и вам нужно, чтобы ваша функция сохраняла предыдущие измерения между вызовами. В этом случае вы можете запрограммировать фабричную функцию закрытия следующим образом:
>>> def mean(): ... sample = [] ... def inner_mean(number): ... sample.append(number) ... return sum(sample) / len(sample) ... return inner_mean ... >>> sample_mean = mean() >>> sample_mean(100) 100.0 >>> sample_mean(105) 102.5 >>> sample_mean(101) 102.0 >>> sample_mean(98) 101.0предварительно> кодовый блок>Замыкание, назначенное
sample_mean, сохраняет состояниеsampleмежду последовательными вызовами. Даже если вы определяетеsampleвmean(), оно все равно доступно в замыкании, поэтому вы можете его изменить. В этом случаеsampleработает как своего рода динамическое состояние замыкания.Изменение состояния закрытия
Обычно переменные замыкания полностью скрыты от внешнего мира. Однако вы можете предоставить для них внутренние функции получателя и установщика:
>>> def make_point(x, y): ... def point(): ... print(f"Point({x}, {y})") ... def get_x(): ... return x ... def get_y(): ... return y ... def set_x(value): ... nonlocal x ... x = value ... def set_y(value): ... nonlocal y ... y = value ... # Attach getters and setters ... point.get_x = get_x ... point.set_x = set_x ... point.get_y = get_y ... point.set_y = set_y ... return point ... >>> point = make_point(1, 2) >>> point.get_x() 1 >>> point.get_y() 2 >>> point() Point(1, 2) >>> point.set_x(42) >>> point.set_y(7) >>> point() Point(42, 7)предварительно> кодовый блок>Здесь
make_point()возвращает замыкание, представляющее объектpoint. К этому объекту присоединены функции получения и установки. Вы можете использовать эти функции для получения доступа на чтение и запись к переменнымxиy, которые определены во включающей области и поставляются с закрытием.Несмотря на то, что эта функция создает замыкания, которые могут работать быстрее, чем эквивалентный класс, вы должны знать, что этот метод не предоставляет основных функций, включая наследование, свойства, дескрипторы и классовые и статические методы. Если вы хотите глубже изучить эту технику, ознакомьтесь с Простым инструментом для моделирования классов с использованием замыканий и вложенных областей (рецепт на Python).
Добавление поведения с помощью внутренних функций: Декораторы
Python декораторы - еще один популярный и удобный вариант использования внутренних функций, особенно для замыканий. Декораторы - это функции более высокого порядка, которые принимают вызываемый объект (функцию, метод, класс) в качестве аргумента и возвращает другой вызываемый объект.
Вы можете использовать функции декоратора, чтобы динамически добавлять обязанности к существующему вызываемому объекту и прозрачно расширять его поведение, не затрагивая и не изменяя исходный вызываемый объект.
Примечание: Для получения более подробной информации о вызываемых объектах Python ознакомьтесь с Стандартной иерархией типов в документации по Python и прокрутите вниз до “Вызываемые типы”.
Чтобы создать декоратор, вам просто нужно определить вызываемый объект (функцию, метод или класс), который принимает функциональный объект в качестве аргумента, обрабатывает его и возвращает другой функциональный объект с добавленным поведением.
Как только у вас будет настроена функция декоратора, вы сможете применить ее к любому вызываемому объекту. Для этого вам нужно использовать символ at (
@) перед именем декоратора, а затем поместить его в отдельную строку непосредственно перед оформленным вызываемым объектом:@decorator def decorated_func(): # Function body... passпредварительно> кодовый блок>Этот синтаксис позволяет
decorator()автоматически приниматьdecorated_func()в качестве аргумента и обрабатывать его в своем теле. Эта операция является сокращением для следующего назначения:decorated_func = decorator(decorated_func)предварительно> кодовый блок>Вот пример того, как создать функцию декоратора, чтобы добавить новую функциональность к существующей функции:
>>> def add_messages(func): ... def _add_messages(): ... print("This is my first decorator") ... func() ... print("Bye!") ... return _add_messages ... >>> @add_messages ... def greet(): ... print("Hello, World!") ... >>> greet() This is my first decorator Hello, World! Bye!предварительно> кодовый блок>В этом случае вы используете
@add_messagesдля оформленияgreet(). Это добавляет новые функциональные возможности к функции оформления. Теперь, когда вы вызываетеgreet(), вместо того, чтобы просто печататьHello, World!, ваша функция выводит два новых сообщения.Варианты использования декораторов Python разнообразны. Вот некоторые из них:
Обычной практикой для отладки кода на Python является вставка вызовов в
print()для проверки значений переменных, подтверждения выполнения блока кода и так далее. Добавление и удаление вызовов вprint()может вызвать раздражение, и вы рискуете забыть о некоторых из них. Чтобы предотвратить эту ситуацию, вы можете написать декоратор следующим образом:>>> def debug(func): ... def _debug(*args, **kwargs): ... result = func(*args, **kwargs) ... print( ... f"{func.__name__}(args: {args}, kwargs: {kwargs}) -> {result}" ... ) ... return result ... return _debug ... >>> @debug ... def add(a, b): ... return a + b ... >>> add(5, 6) add(args: (5, 6), kwargs: {}) -> 11 11предварительно> кодовый блок>В этом примере представлен
debug(), который представляет собой средство оформления, принимающее функцию в качестве аргумента и выводящее ее подпись с текущим значением каждого аргумента и соответствующим возвращаемым значением. Вы можете использовать этот средство оформления для отладки своих функций. Как только вы получите желаемый результат, вы можете удалить вызов декоратора@debug, и ваша функция будет готова к следующему шагу.Примечание: Если вам интересно глубже разобраться в том, как
*argsи**kwargsработают в Python, ознакомьтесь с Python args и kwargs: Демистифицированы.Вот последний пример того, как создать декоратор. На этот раз вы повторно реализуете
generate_power()в качестве функции декоратора:>>> def generate_power(exponent): ... def power(func): ... def inner_power(*args): ... base = func(*args) ... return base ** exponent ... return inner_power ... return power ... >>> @generate_power(2) ... def raise_two(n): ... return n ... >>> raise_two(7) 49 >>> @generate_power(3) ... def raise_three(n): ... return n ... >>> raise_three(5) 125предварительно> кодовый блок>Эта версия
generate_power()дает те же результаты, что и в исходной реализации. В этом случае вы используете как замыкание для запоминанияexponent, так и декоратор, который возвращает измененную версию функции ввода,func().Здесь декоратору нужно принять аргумент (
exponent), поэтому вам нужно иметь два вложенных уровня внутренних функций. Первый уровень представленpower(), который принимает оформленную функцию в качестве аргумента. Второй уровень представленinner_power(), который преобразует аргументexponentвargs, производит окончательный расчет мощности и возвращает результат.Заключение
Если вы определяете функцию внутри другой функции, то вы создаете внутреннюю функцию, также известную как вложенная функция. В Python внутренние функции имеют прямой доступ к переменным и именам, которые вы определяете во включающей функции. Это предоставляет механизм для создания вспомогательных функций, замыканий и декораторов.
В этом руководстве вы узнали, как:
- Обеспечить инкапсуляцию путем вложения функций в другие функции
- Напишите вспомогательных функций для повторного использования фрагментов кода
- Реализовать функции фабрики замыканий, сохраняющие состояние между вызовами
- Создание функций декоратора для предоставления новых функциональных возможностей
Теперь вы готовы воспользоваться преимуществами использования внутренних функций в вашем собственном коде. Если у вас есть какие-либо вопросы или комментарии, не забудьте поделиться ими в разделе комментариев ниже.
<статус завершения статьи-slug="внутренние функции-для чего-они-хороши" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/articles/внутренние функции-для чего-они"-годится для/закладки/" data-api-article-completion-status-url="/api/v1/articles/inner-functions-what-are-they-good-for/completion_status/"> статус завершения> <кнопка поделиться bluesky-text="Интересная статья на #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей о Python:%0A%0APython Внутренние функции: для чего они нужны?" email-subject="Статья о Python для вас" twitter-text="Интересная #Статья о Python от @realpython:" url="https://realpython.com/inner-functions-what-are-they-good-for /" url-title="Внутренние функции Python: для чего они нужны?"> кнопка поделиться>Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Внутренних функций Python
Back to Top