freeCodeCamp/guide/russian/python/nested-functions/index.md

105 lines
24 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
title: Nested Functions in Python
localeTitle: Вложенные функции в Python
---
### Пространства имен
Параметры функции, а также любые переменные, которые связаны (посредством присвоения или другими операторами привязки, такими как def) в теле функции, составляют локальное пространство имен функции, также известное как локальная область. Каждая из этих переменных известна как локальная переменная функции.
Переменные, которые не являются локальными, известны как глобальные переменные (при отсутствии определений вложенных функций, которые мы обсудим в ближайшее время). Глобальные переменные являются атрибутами объекта модуля, как описано в «Атрибутах объектов модуля» на стр. 140. Всякий раз, когда локальная переменная функции имеет то же имя, что и глобальная переменная, это имя в теле функции относится к локальной переменной, а не глобальный. Мы выражаем это, говоря, что локальная переменная скрывает глобальную переменную с тем же именем во всем теле функции.
### Глобальное заявление
По умолчанию любая переменная, связанная внутри тела функции, является локальной переменной функции. Если функции необходимо перестроить некоторые глобальные переменные, первая утверждение функции должно быть:
глобальные идентификаторы
где идентификаторы - один или несколько идентификаторов, разделенных запятыми (,). Идентификаторы, перечисленные в глобальном заявлении, относятся к глобальным переменным (т. Е. Атрибутам объекта модуля), которые функция должна перестраивать. Например, счетчик функций, который мы видели в «Другие атрибуты объектов функций» на стр. 73, может быть реализован с использованием глобальной и глобальной переменных, а не атрибута объекта функции:
\_count = 0 def counter (): глобальный \_count \_count + = 1 return \_count
Без глобального оператора функция счетчика вызовет исключение UnboundLocal-Error, потому что \_count тогда будет неинициализированной (несвязанной) локальной переменной. Хотя глобальное заявление допускает такой вид программирования, этот стиль часто неэлегантен и нецелесообразен. Как я упоминал ранее, когда вы хотите сгруппировать какое-то состояние и какое-то поведение, объектно-ориентированные механизмы, описанные в главе 5, обычно лучше всего.
Не используйте global, если тело функции просто использует глобальную переменную (включая мутацию объекта, привязанного к этой переменной, если объект изменен). Используйте глобальный оператор только в том случае, если тело функции восстанавливает глобальную переменную (обычно назначая имя переменной). В отношении стиля, не используйте глобальные, если это строго необходимо, так как его присутствие заставит читателей вашей программы предполагать, что инструкция существует для какой-то полезной цели. В частности, никогда не используйте глобальный, кроме как первый оператор в теле функции.
{mospagebreak title = Вложенные функции и вложенные области}
Обозначение def внутри тела функции определяет вложенную функцию, а функция, тело которой включает def, называется внешней функцией вложенной. Код в теле вложенной функции может обращаться к (но не восстанавливать) локальные переменные внешней функции, также называемой свободными переменными вложенной функции.
Самый простой способ позволить вложенной функции получить доступ к значению, часто не полагаться на вложенные области, а скорее явно передавать это значение как один из аргументов функции. При необходимости значение аргумента может быть привязано, если вложенная функция определена с использованием значения по умолчанию для необязательного аргумента. Например:
def percent1 (a, b, c): def pc (x, total = a + b + c): return (x \* 100.0) / total print "Проценты:", pc (a), pc (b), pc (c)
Вот те же функции, что и вложенные области:
def percent2 (a, b, c): def pc (x): return (x \* 100.0) / (a + b + c) print "Проценты:", pc (a), pc (b), pc (c)
В этом конкретном случае процент 1 имеет крошечное преимущество: вычисление a + b + c происходит только один раз, а внутренняя функция comp2 compc трижды повторяет вычисление. Однако, если внешняя функция перепроверяет свои локальные переменные между вызовами вложенной функции, может потребоваться повторение вычисления. Поэтому целесообразно осознавать оба подхода и выбирать наиболее подходящий вариант в каждом случае.
Вложенная функция, которая обращается к значениям из внешних локальных переменных, также известна как замыкание. В следующем примере показано, как построить закрытие:
def make\_adder (augend): def add (добавить): return addend + augend return add
Закрытие является исключением из общего правила, согласно которому объектно-ориентированные механизмы, описанные в главе 5, являются наилучшим способом объединения данных и кода. Когда вам нужно специально создавать вызываемые объекты, с некоторыми параметрами, зафиксированными во время построения объекта, замыкания могут быть проще и эффективнее классов. Например, результат make\_adder (7) - это функция, которая принимает один аргумент и добавляет 7 к этому аргументу. Внешняя функция, возвращающая замыкание, является «фабрикой» для членов семейства функций, отличающихся некоторыми параметрами, такими как значение аргумента augend в предыдущем примере и часто может помочь избежать дублирования кода.
### лямбда-выражения
Если тело функции является единственным выражением выражения return, вы можете заменить функцию специальной формой выражения лямбда:
Параметры лямбда: выражение
Выражение лямбда является анонимным эквивалентом нормальной функции, тело которой является единственным оператором return. Обратите внимание, что синтаксис лямбда не использует ключевое слово return. Вы можете использовать выражение лямбда везде, где вы могли бы использовать ссылку на функцию. lambda иногда может быть удобно, если вы хотите использовать простую функцию в качестве аргумента или возвращаемого значения. Вот пример, который использует лямбда-выражение в качестве аргумента функции встроенного фильтра (см. Фильтр на стр. 161):
aList = \[1, 2, 3, 4, 5, 6, 7, 8, 9\] низкий = 3 высокий = 7 фильтр (lambda x, l = низкий, h = высокий: h> x> l, aList) # возвращает: \[4, 5, 6\]
В качестве альтернативы вы всегда можете использовать локальную команду def, которая дает объекту функции имя. Затем вы можете использовать это имя в качестве аргумента или возвращаемого значения. Вот тот же пример фильтра, в котором используется локальная команда def:
aList = \[1, 2, 3, 4, 5, 6, 7, 8, 9\] низкий = 3 высокий = 7 def в _пределах (значение, l = низкий, h = высокий): return h> value> l фильтр (в пределах_ границ, aList) # возвращает: \[4, 5, 6\]
Хотя лямбда иногда может быть полезна, многие пользователи Python предпочитают def, что является более общим, и могут сделать ваш код более читаемым, если вы выберете разумное имя для этой функции.
{mospagebreak title = Генераторы}
Когда тело функции содержит одно или несколько вхождений выхода ключевого слова, функция называется генератором. Когда вы вызываете генератор, тело функции не выполняется. Вместо этого вызов генератора возвращает специальный объект итератора, который обертывает тело функции, его локальные переменные (включая его параметры) и текущую точку выполнения, которая изначально является началом функции.
Когда вызывается следующий метод этого объекта итератора, тело функции выполняет следующий оператор yield, который принимает форму:
выходное выражение
Когда выполняется оператор yield, выполнение функции «заморожено» с текущей точкой выполнения и локальными переменными нетронутыми, а результат следующего результата возвращается в результате следующего метода. Когда снова вызывается снова, выполнение тела функции возобновляется там, где оно было остановлено, снова до следующего оператора yield. Если тело функции заканчивается или выполняет оператор return, итератор вызывает исключение StopIteration, чтобы указать, что итерация завершена. Операторы return в генераторе не могут содержать выражения.
Генератор - очень удобный способ создания итератора. Поскольку наиболее распространенный способ использования итератора - это цикл с ним с помощью инструкции for, вы обычно вызываете генератор следующим образом:
для avariable в somegenerator (аргументы):
Например, скажем, что вы хотите, чтобы последовательность чисел подсчитывалась от 1 до N, а затем до 1 снова. Генератор может помочь:
def updown (N): для x в xrange (1, N): выход x для x в xrange (N, 0, -1): получаем x для i в updown (3): print i # prints: 1 2 3 2 1
Вот генератор, который работает как встроенная функция xrange, но возвращает последовательность значений с плавающей запятой вместо последовательности целых чисел:
def frange (start, stop, step = 1.0): в то время как start <stop: начало выпуска start + = step
Этот пример frange лишь немного похож на xrange, потому что для простоты он приводит к тому, что аргументы начинаются и прекращаются, и молча предполагает, что шаг положительный.
Генераторы более гибкие, чем функции, которые возвращают списки. Генератор может построить неограниченный итератор, то есть тот, который возвращает бесконечный поток результатов (использовать только в циклах, которые завершаются другими способами, например, с помощью инструкции break). Кроме того, итератор, созданный генератором, выполняет ленивую оценку: итератор вычисляет каждый следующий элемент только тогда, когда и когда это необходимо, как раз вовремя, в то время как эквивалентная функция выполняет все вычисления заранее и может потребовать больших объемов памяти для хранения списка результатов. Поэтому, если вам нужна только возможность итерации по вычисленной последовательности, часто лучше всего вычислить последовательность в генераторе, а не в функции, которая возвращает список. Если вызывающему абоненту нужен список всех элементов, созданных некоторым ограниченным генератором G (аргументы), вызывающий может просто использовать следующий код:
result\_list = list (G (аргументы))
### Выражения генератора
Python 2.4 вводит еще более простой способ кодирования особенно простых генераторов: генераторные выражения, обычно известные как genexps. Синтаксис гена xp аналогичен синтаксису определения списка (как описано в «Пояснениях списков» на стр. 67), за исключением того, что ген xp заключен в круглые скобки (()) вместо скобок (\[\]); семантика гена xp совпадает с семантикой соответствующего понимания списка, за исключением того, что ген xp производит итератор, дающий по одному элементу за раз, в то время как понимание списка создает список всех результатов в памяти (поэтому, используя ген xp, когда соответствующий, сохраняет память). Например, чтобы суммировать квадраты всех однозначных целых чисел, в любом современном Python, вы можете кодировать sum (\[x _x для x в xrange (10)\]); в Python 2.4 вы можете выразить эту функциональность еще лучше, закодировав ее как сумму (x_ x для x в xrange (10)) (точно так же, но опуская скобки), и получите точно такой же результат, потребляя меньше памяти. Обратите внимание, что круглые скобки, которые указывают на вызов функции, также «делают двойной долг» и заключают в себе ген xp (нет необходимости в дополнительных скобках).
{mospagebreak title = Генераторы в Python 2.5}
В Python 2.5 генераторы еще более расширены, с возможностью получения значения (или исключения) обратно от вызывающего, поскольку каждый выход выполняется. Эти расширенные функции позволяют генераторам в 2.5 реализовать полноценные совместные подпрограммы, как описано в http://www.python.org/peps/pep-0342.html. Главное изменение заключается в том, что в 2.5 доход не является выражением, а выражением, поэтому он имеет значение. Когда генератор возобновляется, вызывая его следующий метод, значение соответствующего урона равно None. Чтобы передать значение x в некоторый генератор g (так что g принимает x как значение выхода, на котором оно приостановлено) вместо вызова g.next () вызывающий вызов g.send (x) (вызов g.send (Нет) так же, как вызов g.next ()). Кроме того, годовая доходность без аргументов в Python 2.5 становится законной и эквивалентна выводу None.
Другие улучшения Python 2.5 для генераторов связаны с исключениями и описаны в «Улучшениях генератора» на стр. 126.
### Рекурсия
Python поддерживает рекурсию (т. Е. Функция Python может вызывать себя), но существует ограничение на глубину рекурсии. По умолчанию Python прерывает рекурсию и вызывает исключение RecursionLimitExceeded (см. «Стандартные классы исключений» на стр. 130), когда обнаруживает, что стек рекурсивных вызовов прошел на глубину 1000. Вы можете изменить предел рекурсии с помощью функции setrecursionlimit модуля sys, указанной в setrecursionlimit на стр. 171.
Однако изменение предела рекурсии не дает вам неограниченной рекурсии; абсолютный максимальный предел зависит от платформы, на которой работает ваша программа, особенно в базовой операционной системе и библиотеке времени выполнения C, но обычно это несколько тысяч уровней. Если рекурсивные вызовы становятся слишком глубокими, ваша программа вылетает из строя. Такая рекурсия бегства после вызова setrecursionlimit, который превосходит возможности платформы, является одним из немногих способов, с помощью которых программа Python может аварийно завершать работу, - это действительно крушение, жесткое, без обычной безопасности механизмов исключения Python. Поэтому будьте осторожны, пытаясь исправить программу, которая получает исключения RecursionLimitExceeded, повышая слишком высокий предел рекурсии с помощью setrecursionlimit. Чаще всего вам лучше советовать искать способы удаления рекурсии или, более конкретно, ограничить глубину рекурсии, которую требуется вашей программе.
Читатели, знакомые с языками Lisp, Scheme или функционального программирования, должны, в частности, знать, что Python не реализует оптимизацию «устранения хвостового вызова», что так важно на этих языках. В Python любой вызов, рекурсивный или нет, имеет одинаковую стоимость как по времени, так и по пространству памяти, зависящему только от количества аргументов: стоимость не изменяется, является ли вызов «хвостом» (что означает вызов - это последняя операция, выполняемая вызывающим абонентом) или любой другой, не входящий вызов.