Математический анализ. 3е издание
Скачать 4.86 Mb.
|
Примеры вложенных областей видимости Ниже приводится пример вложенной области видимости: def f1(): x = 88 def f2(): print x f2() f1() # Выведет 88 Прежде всего – это вполне допустимый программный код на языке Py thon: инструкция def – это обычная исполняемая инструкция, которая 412 Глава 16. Области видимости и аргументы может появляться в любом месте программы, где могут появляться любые другие инструкции, включая вложение в другую инструкцию def . В этом примере вложенная инструкция def исполняется в момент вызова функции f1 – она создает функцию и связывает ее с именем f2, которое является локальным и размещается в локальной области ви димости функции f1. В некотором смысле f2 – это временная функция, которая существует только во время работы (и видима только для про граммного кода) объемлющей функции f1. Однако, обратите внимание, что происходит внутри функции f2: когда производится вывод переменной x, она ссылается на переменную x в локальной области видимости объемлющей функции f1. Функции имеют возможность обращаться к именам, которые физически распо лагаются в любых объемлющих инструкциях def, и имя x в функции f2 автоматически отображается на имя x в функции f1 в соответствии с правилом поиска LEGB. Это правило поиска в объемлющих областях видимости выполняется, даже если объемлющая функция фактически уже вернула управле ние. Например, следующий фрагмент определяет функцию, которая создает и возвращает другую функцию: def f1(): x = 88 def f2(): print x return f2 action = f1() # Создает и возвращает функцию action() # Вызов этой функции: выведет 88 В этом фрагменте при вызове action фактически запускается функция, созданная во время исполнения функции f1. Функция f2 помнит пере менную x в области видимости объемлющей функции f1, которая уже неактивна. Фабричные функции В зависимости от того, кому задается вопрос о том, как называется та кое поведение, можно услышать такие термины, как замыкание или фабричная функция, – объект функции, который сохраняет значения в объемлющих областях видимости, даже тогда, когда эти области мо гут прекратить свое существование. Классы (описываются в шестой части книги) обычно лучше подходят для сохранения состояния, пото му что они позволяют делать это явно, посредством присваивания зна чений атрибутам, тем не менее, подобные функции обеспечивают дру гую альтернативу. Например, фабричные функции иногда используются в программах, когда необходимо создавать обработчики событий прямо в процессе исполнения, в соответствии со сложившимися условиями (например, Области видимости и вложенные функции 413 когда желательно запретить пользователю вводить данные). Рассмот рим в качестве примера следующую функцию: >>> def maker(N): ... def action(X): ... return X ** N ... return action Здесь определяется внешняя функция, которая просто создает и воз вращает вложенную функцию, не вызывая ее. Если вызвать внешнюю функцию: >>> f = maker(2) # Запишет 2 в N >>> f она вернет ссылку на созданную ею внутреннюю функцию, созданную при выполнении вложенной инструкции def. Если теперь вызвать то, что было получено от внешней функции: >>> f(3) # Запишет 3 в X, в N попрежнему хранится число 2 9 >>> f(4) # 4 ** 2 16 будет вызвана вложенная функция, с именем action внутри функции maker . Самое необычное здесь то, что вложенная функция продолжает хранить число 2, значение переменной N в функции maker даже при том, что к моменту вызова функции action функция maker уже завер шила свою работу и вернула управление. В действительности имя N из объемлющей локальной области видимости сохраняется как информа ция о состоянии, присоединенная к функции action, и мы получаем об ратно значение аргумента, возведенное в квадрат. Теперь, если снова вызвать внешнюю функцию, мы получим новую вложенную функцию уже с другой информацией о состоянии, присо единенной к ней, – в результате вместо квадрата будет вычисляться куб аргумента, но ранее сохраненная функция попрежнему будет воз вращать квадрат аргумента: >>> g = maker(3) >>> g(3) # 3 ** 3 27 >>> f(3) # 3 ** 2 9 Это довольно сложный прием, который вам вряд ли часто придется час то встречать на практике, впрочем, он распространен среди программи стов, обладающих опытом работы с функциональными языками про граммирования (и иногда его можно встретить в выражениях lambda, как будет описано далее). Вообще классы, которые будут обсуждаться позднее, лучше подходят на роль «памяти», как в данном случае, пото 414 Глава 16. Области видимости и аргументы му что они обеспечивают явное сохранение информации. Помимо клас сов основными средствами хранения информации о состоянии функ ций в языке Python являются глобальные переменные, объемлющие области видимости, как в данном случае, и аргументы по умолчанию. Сохранение состояния объемлющей области видимости с помощью аргументов по умолчанию В предыдущих версиях Python такой программный код, как в преды дущем разделе, терпел неудачу изза отсутствия вложенных областей видимости в инструкциях def – при обращении к переменной внутри функции f2 поиск производился сначала в локальной области видимо сти (f2), затем в глобальной (программный код за пределами f1) и за тем во встроенной области видимости. Области видимости объемлю щих функций не просматривались, что могло приводить к ошибке. Чтобы разрешить ситуацию, программисты обычно использовали зна чения аргументов по умолчанию для передачи (сохранения) объектов, расположенных в объемлющей области видимости: def f1(): x = 88 def f2(x=x): print x f2() f1() # Выведет 88 Этот фрагмент будет работать во всех версиях Python, и такой подход попрежнему можно встретить в существующих программах на языке Python. Аргументы со значениями по умолчанию мы рассмотрим ниже, в этой же главе. А пока в двух словах замечу, что конструкция arg = val в заголовке инструкции def означает, что аргумент arg по умолчанию будет иметь значение val, если функции не передается какоголибо другого значения. В измененной версии f2 запись x=x означает, что аргумент x по умолча нию будет иметь значение переменной x объемлющей области видимо сти. Поскольку значение для второго имени x вычисляется еще до то го, как интерпретатор Python войдет во вложенную инструкцию def, оно все еще ссылается на имя x в функции f1. В результате в значении по умолчанию запоминается значение переменной x в функции f1 (т. е. объект 88). Все это довольно сложно и полностью зависит от того, когда вычисля ется значение по умолчанию. Фактически поиск во вложенных облас тях видимости был добавлен в Python, чтобы избавиться от такого спо соба использования значений по умолчанию – сейчас Python автома тически сохраняет любые значения в объемлющей области видимости для последующего использования во вложенных инструкциях def. Безусловно, наилучшей рекомендацией будет просто избегать вложе ния инструкций def в другие инструкции def, так как это существенно Области видимости и вложенные функции 415 упростит программы. Ниже приводится фрагмент, который является эквивалентом предшествующего примера, в котором просто отсутству ет понятие вложенности. Обратите внимание, что вполне допустимо вызывать функцию, определение которой в тексте программы нахо дится ниже функции, откуда производится вызов, как в данном слу чае, при условии, что вторая инструкция def будет исполнена до того, как первая функция попытается вызвать ее, – программный код внут ри инструкции def не выполняется, пока не будет произведен фактиче ский вызов функции: >>> def f1(): ... x = 88 ... f2(x) >>> def f2(x): ... print x >>> f1() 88 При использовании такого способа можно забыть о концепции вложен ных областей видимости в языке Python, если вам не потребуется соз давать фабричные функции, обсуждавшиеся выше, – по крайней мере при использовании инструкций def. Выражения lambda, которые прак тически всегда вкладываются в инструкции def, часто используют вло женные области видимости, как описывается в следующем разделе. Вложенные области видимости и lambdaвыражения Несмотря на то, что вложенные области видимости на практике редко используются непосредственно для инструкций def, вам наверняка придется столкнуться с областями видимости вложенных функций, когда вы начнете использовать выражения lambda. Мы не будем под робно рассматривать эти выражения до главы 17, но в двух словах за мечу, что это выражение генерирует новую функцию, которая будет вызываться позднее, и оно очень похоже на инструкцию def. Посколь ку lambda – это выражение, оно может использоваться там, где не до пускается использование инструкции def, например, в литералах спи сков и словарей. Подобно инструкции def, выражение lambda сопровождается появлени ем новой локальной области видимости. Благодаря наличию возмож ности поиска имен в объемлющей области видимости выражения lambda способны обращаться ко всем переменным, которые присутст вуют в функциях, где находятся эти выражения. Таким образом, в на стоящее время следующий программный код будет работать исключи тельно благодаря тому, что в настоящее время действуют правила по иска во вложенных областях видимости: def func(): x = 4 416 Глава 16. Области видимости и аргументы action = (lambda n: x ** n) # Запоминается x из объемлющей # инструкции def return action x = func() print x(2) # Выведет 16, 4 ** 2 До того как появилось понятие областей видимости вложенных функ ций, программисты использовали значения по умолчанию для переда чи значений из объемлющей области видимости в выражения lambda точно так же, как и в случае с инструкциями def. Например, следую щий фрагмент будет работать во всех версиях Python: def func(): x = 4 action = (lambda n, x=x: x ** n) # Передача x вручную Поскольку lambda – это выражения, они естественно (и даже обычно) вкладываются внутрь инструкций def. Следовательно, именно они из влекли наибольшую выгоду от добавления областей видимости объем лющих функций в правила поиска имен – в большинстве случаев отпа дает необходимость передавать в выражения lambda аргументы со зна чениями по умолчанию. Области видимости и значения по умолчанию применительно к переменным цикла Существует одно известное исключение из правила, которое я только что дал: если lambdaвыражение или инструкция def вложены в цикл внутри другой функции и вложенная функция ссылается на перемен ную из объемлющей области видимости, которая изменяется в цикле, все функции, созданные в этом цикле, будут иметь одно и то же значе ние – значение, которое имела переменная на последней итерации. Например, ниже предпринята попытка создать список функций, каж дая из которых запоминает текущее значение переменной i из объем лющей области видимости: >>> def makeActions(): ... acts = [] ... for i in range(5): # Сохранить каждое значение i ... acts.append(lambda x: i ** x) # Все запомнят последнее значение i! ... return acts >>> acts = makeActions() >>> acts[0] Такой подход не дает желаемого результата, потому что поиск пере менной в объемлющей области видимости производится позднее, при вызове вложенных функций, в результате все они получат одно и то же значение (значение, которое имела переменная цикла на последней итерации). То есть каждая функция в списке будет возвращать 4 во Области видимости и вложенные функции 417 второй степени, потому что во всех них переменная i имеет одно и то же значение: >>> acts[0](2) # Все возвращают 4 ** 2, последнее значение i 16 >>> acts[2](2) # Здесь должно быть 2 ** 2 16 >>> acts[4](2) # Здесь должно быть 4 ** 2 16 Это один из случаев, когда необходимо явно сохранять значение из объ емлющей области видимости в виде аргумента со значением по умолча нию вместо использования ссылки на переменную из объемлющей об ласти видимости. То есть, чтобы этот фрагмент заработал, необходимо передать текущее значение переменной из объемлющей области види мости в виде значения по умолчанию. Значения по умолчанию вычис ляются в момент создания вложенной функции (а не когда она вызыва ется), поэтому каждая из них сохранит свое собственное значение i: >>> def makeActions(): ... acts = [] ... for i in range(5): # Использовать значения по умолчанию ... acts.append(lambda x, i=i: i ** x) # Сохранить текущее значение i ... return acts >>> acts = makeActions() >>> acts[0](2) # 0 ** 2 0 >>> acts[2](2) # 2 ** 2 4 >>> acts[4](2) # 4 ** 2 16 Это достаточно замысловатый случай, но с ним можно столкнуться на практике, особенно в программном коде, который генерирует функции обработчики событий для элементов управления в графическом интер фейсе (например, обработчики нажатия кнопок). Подробнее о значениях по умолчанию и lambdaвыражениях мы поговорим в следующей главе, поэтому позднее вам может потребоваться вернуться к этому разделу. 1 1 В разделе «Ошибки при работе с функциями» к этой части книги, в конце следующей главы, мы также увидим, что существуют определенные про блемы с использованием изменяемых объектов, таких как списки и слова ри, при использовании их в качестве значений по умолчанию для аргумен тов (например, def f(a=[])). Так как значения по умолчанию реализованы в виде единственных объектов, изменяемые объекты по умолчанию сохра няют свое состояние от вызова к вызову, а не инициализируются заново при каждом вызове. В зависимости от того, кому задается вопрос, эта осо бенность может рассматриваться или как особенность, поддерживающая возможность сохранения состояния, или как недостаток языка. Подробнее об этом будет говориться в следующей главе. 418 Глава 16. Области видимости и аргументы Произвольное вложение областей видимости Прежде чем закончить это исследование, я должен заметить, что об ласти видимости могут вкладываться произвольно, но поиск будет производиться только в объемлющих функциях (не в классах, кото рые описываются в шестой части книги): >>> def f1(): ... x = 99 ... def f2(): ... def f3(): ... print x # Будет найдена в области видимости f1! ... f3() ... f2() >>> f1() 99 Интерпретатор будет искать переменную в локальных областях видимо сти всех объемлющих инструкций def, начиная от внутренних к внеш ним, выше локальной области видимости и ниже глобальной области видимости модуля. Однако такой программный код едва ли может по лучиться на практике. В языке Python считается, что плоское лучше вложенного – ваша жизнь и жизнь ваших коллег будет проще, если вы сведете к минимуму количество вложенных определений функций. Передача аргументов Раньше уже говорилось, что передача аргументов производится по средством операции присваивания. Здесь имеется несколько момен тов, не всегда очевидных для начинающих, о которых будет говорить ся в этом разделе. Ниже приводится несколько важных замечаний, ка сающихся передачи аргументов в функции: • Аргументы передаются через автоматическое присваивание объ! ектов локальным именам. Аргументы функции – ссылки на объек ты, которые (возможно) используются совместно с вызывающей программой – это всего лишь результат еще одной из разновидно стей операции присваивания. Ссылки в языке Python реализованы в виде указателей, поэтому все аргументы фактически передаются по указателям. Объекты, которые передаются в виде аргументов, никогда не копируются. • Операция присваивания именам аргументов внутри функции не оказывает влияния на вызывающую программу. При вызове функции имена аргументов, указанные в ее заголовке, становятся новыми локальными именами в области видимости функции. Это не является совмещением имен между именами аргументов и име нами в вызывающей программе. Передача аргументов 419 • Изменение внутри функции аргумента, который является изме! няемым объектом, может оказывать влияние на вызывающую программу. C другой стороны, так как аргументы – это всего лишь результат операции присваивания полученных объектов, функции могут воздействовать на полученные изменяемые объекты и тем са мым оказывать влияние на вызывающую программу. Изменяемые объекты могут рассматриваться функциями, как средство ввода, так и вывода информации. За дополнительной информацией о ссылках обращайтесь к главе 6 – все, что там говорится, вполне применимо и к аргументам функций несмотря на то, что присваивание именам аргументов выполняется ав томатически и неявно. Схема передачи аргументов посредством присваивания, принятая в язы ке Python, это далеко не то же самое, что передача аргументов по ссыл ке в языке C++, но она очень близка к модели передачи аргументов в языке C: • Неизменяемые объекты передаются «по значению». Такие объек ты, как целые числа и строки, передаются в виде ссылок на объек ты, а не в виде копий объектов, но, так как неизменяемые объекты невозможно изменить непосредственно, передача таких объектов очень напоминает копирование. • Изменяемые объекты передаются «по указателю». Такие объекты, как списки и словари, также передаются в виде ссылок на объекты, что очень похоже на то, как в языке C передаются указатели на мас сивы – изменяемые объекты допускают возможность непосредствен ного изменения внутри функции так же, как и массивы в языке C. Конечно, если вы никогда ранее не использовали язык C, модель пере дачи аргументов в языке Python буде казаться вам еще проще – со гласно этой модели выполняется присваивание объектов именам аргу ментов, и она одинаково работает с любыми объектами, как с изменяе мыми, так и с неизменяемыми. |