Лутц М. - Изучаем Python (1077325), страница 89
Текст из файла (страница 89)
Например, следующий фрагмент определяет функцию, которая создает и возвращает другую функцию: оег и() х = 88 оег (2(); ргтпт х гетцгп Г2 асттоп = Г1() а Создает и возвраюает функцию асттоп() а Вызов этой функции, выведет ВВ В этом фрагменте при вызове ас(1оп фактически запускается функция, созданная во время исполнения функции (1. Функция (2 помнит переменную х в области видимости объемлющей функции (1, которая уже неактивна. Фабричные функции В зависимости от того, кому задается вопрос о том, как называется такое поведение, можно услышать такие термины, как замыкание или фабричная функция, — объект функции, который сохраняет значения в объемлющих областях видимости, даже тогда, когда эти области могут прекратить свое существование.
Классы (описываются в шестой части книги) обычно лучше подходят для сохранения состояния, потому что они позволяют делать это явно, посредством присваивания значений атрибутам, тем не менее, подобные функции обеспечивают другую альтернативу.
Например, фабричные функции иногда используются в программах, когда необходимо создавать обработчики событий прямо в процессе исполнения, в соответствии со сложившимися условиями (например, 413 Области видимости и вложенные функции когда желательно запретить пользователю вводить данные). Рассмот- рим в качестве примера следующую функцию: »> вет пакет(й); Овт во(топ(Х): ге(ого Х ** й ге(ого аотгоп Здесь определяется внешняя функция, которая просто создает и воз- вращает вложенную функцию, не вызывая ее. Если вызвать внешнюю функцию: »> Г = ааКег(2) № Запишет 2 в № »> <топот>оп ас(1оп ат 0401472060> она вернет ссылку на созданную ею внутреннюю функцию, созданную при выполнении вложенной инструкции бег.
Если теперь вызвать то, что было получено от внешней функции: № Запишет 3 в Х, в р по-прешяеяу хранится число 2 № 4 ** 2 »> г(3) 9 »> г(4) 16 будет вызвана вложенная функция, с именем асттоп внутри функции заКег. Самое необычное здесь то, что вложенная функция продолжает хранить число 2, значение переменной й в функции аакег даже при том, что к моменту вызова функции асс(оп функция ааКег уже завершила свою работу и вернула управление.
В действительности имя й из объемлющей локальной области видимости сохраняется как информация о состоянии, присоединенная к функции асттоп, и мы получаем обратно значение аргумента, возведенное в квадрат. Теперь, если снова вызвать внешнюю функцию, мы получим новую вложенную функцию уже с другой информацией о состоянии, присоединенной к ней, — в результате вместо квадрата будет вычисляться куб аргумента, но ранее сохраненная функция по-прежнему будет возвращать квадрат аргумента: »> 9 = ааКег(3) »> 9(3) 27 »> Г(3) 9 №З **3 №3* ° 2 Это довольно сложный прием, который вам вряд ли часто придется часто встречать на практике, впрочем, он распространен среди программистов, обладающих опытом работы с функциональными языками программирования (и иногда его можно встретить в выражениях )азова, как будет описано далее).
Вообще классы, которые будут обсуждаться позднее, лучше подходят на роль «памяти», как в данном случае, пото- Глава 1б. Области видимости и аргументы 414 му что они обеспечивают явное сохранение информации. Помимо классов основными средствами хранения информации о состоянии функций в языке РуФЬоп являются глобальные переменные, объемлющие области видимости, как в данном случае, и аргументы по умолчанию. Сохранение состояния объемлющей области видимости с помощью аргументов по умолчанию В предыдущих версиях Ру()топ такой программный код, как в предыдущем разделе, терпел неудачу из-за отсутствия вложенных областей видимости в инструкциях ()е( — при обращении к переменной внутри функции (2 поиск производился сначала в локальной области видимости ((2), затем в глобальной (программный код за пределами (1) и затем во встроенной области видимости.
Области видимости объемлющих функций не просматривались, что могло приводить к ошибке. Чтобы разрешить ситуацию, программисты обычно использовали значения аргументов по умолчанию для передачи (сохранения) объектов, расположенных в объемлющей области видимости: ает (1(). х = 88 ает (2(х=х). ргтпт х (2() г Вэведет ВВ Этот фрагмент будет работать во всех версиях РуФ)топ, и такой подход по-прежнему можно встретить в существующих программах на языке Ру$Ьоп. Аргументы со значениями по умолчанию мы рассмотрим ниже, в этой же главе. А пока в двух словах замечу, что конструкция а гс = эа) в заголовке инструкции ое( означает, что аргумент агз по умолчанию будет иметь значение эа), если функции не передается какого-либо другого значения.
В измененной версии (2 запись х=х означает, что аргумент х по умолчанию будет иметь значение переменной х объемлющей области видимости. Поскольку значение для второго имени х вычисляется еще до того, как интерпретатор Ру(Ьоп войдет во вложенную инструкцию Ое(, оно все еще ссылается на имя х в функции г1. В результате в значении по умолчанию запоминается значение переменной х в функции (1 (т. е. объект 88).
Все это довольно сложно и полностью зависит от того, когда вычисляется значение по умолчанию. Фактически поиск во вложенных областях видимости был добавлен в Ру(Ьоп, чтобы избавиться от такого способа использования значений по умолчанию — сейчас РуФЬоп автоматически сохраняет любые значения в объемлющей области видимости для последующего использования во вложенных инструкциях Се(. Безусловно, наилучшей рекомендацией будет просто избегать вложения инструкций Се( в другие инструкции Оег, так как это существенно Области видимости и вложенные функции 415 упростит программы.
Ниже приводится фрагмент, который является эквивалентом предшествующего примера, в котором просто отсутствует понятие вложенности. Обратите внимание, что вполне допустимо вызывать функцию, определение которой в тексте программы находится ниже функции, откуда производится вызов, как в данном случае, при условии, что вторая инструкция Зет будет исполнена до того, как первая функция попытается вызвать ее, — программный код внутри инструкции оет не выполняется, пока не будет произведен фактический вызов функции: »> Ее( Г1(): х = 88 (2(х) »> Ее( т2(х); рг1п1 х »> 11() 88 При использовании такого способа можно забыть о концепции вложенных областей видимости в языке РуЫтоп, если вам не потребуется создавать фабричные функции, обсуждавшиеся выше, — по крайней мере при использовании инструкций Ьет.
Выражения 1ваооа, которые практически всегда вкладываются в инструкции Зе(, часто используют вложенные области видимости, как описывается в следующем разделе. Вложенные области видимости и 1атЫа-выражения Несмотря на то, что вложенные области видимости на практике редко используются непосредственно для инструкций Ьет, вам наверняка придется столкнуться с областями видимости вложенных функций, когда вы начнете использовать выражения 1заЬЬа. Мы не будем подробно рассматривать зти выражения до главы 17, но в двух словах замечу, что зто выражение генерирует новую функцию, которая будет вызываться позднее, и оно очень похоже на инструкцию лет.
Поскольку 1аеЬЬа — зто выражение, оно может использоваться там, где не допускается использование инструкции Ое(, например, в литералах спискови словарей. Подобно инструкции Ье(, выражение 1ваЬЬа сопровождается появлением новой локальной области видимости. Благодаря наличию возможности поиска имен в объемлющей области видимости выражения 1аезза способны обращаться ко всем переменным, которые присутствуют в функциях, где находятся зти выражения. Таким образом, в настоящее время следующий программный код будет работать исключительно благодаря тому, что в настоящее время действуют правила поиска во вложенных областях видимости: пет тппс(); х = 4 Глава 16. Области видимости и аргументы 416 асс>оп = (1авьоа и: х ° п) в Запоиинается и из обвеипювей в инструкции Сет гетогп ас11оп х = гипс() ргтпт х(2) В Виеедет тб, 4 ° ° 2 До того как появилось понятие областей видимости вложенных функций, программисты использовали значения по умолчанию для передачи значений из объемлющей области видимости в выражения 1аавба точно так же, как и в случае с инструкциями бег.
Например, следующий фрагмент будет работать во всех версиях Ру()топ1 оег гцпс(): х = 4 асс>оп = ( 1авпба и, х=х: х ° * и) В Передача и вручную Поскольку 1аавба — это выражения, они естественно (и даже обычно) вкладываются внутрь инструкций Ое(. Следовательно, именно они извлекли наибольшую выгоду от добавления областей видимости объемлющих функций в правила поиска имен — в большинстве случаев отпадает необходимость передавать в выражения 1авЬба аргументы со значениями по умолчанию. Области видимости и значения по умолчанию применительно к переменным цикла Существует одно известное исключение из правила, которое я только что дал: если 1аавба-выражение или инструкция бе( вложены в цикл внутри другой функции и вложенная Функция ссылается на переменную из объемлющей области видимости, которая изменяется в цикле, все функции, созданные в этом цикле, будут иметь одно и то же значение — значение, которое имела переменная на последней итерации.
Например, ниже предпринята попытка создать список Функций, каждая из которых запоминает текущее значение переменной 1 из объемлющей области видимости: »> Сот аа)<еас11опв(): аств = (1 Гог 1 1п тапсе(5): 4 Сокранити кахдое значение т аств.аррепо(1ааьса х: 1 * ° х) в Все ззпоинят последнее значение т' гетогп аств »> яств = вааеАС11опв() »> асса(01 <гипс(1оп <1авьоа> ат Ох01201ВВО> Такой подход не дает желаемого результата, потому что поиск переменной в объемлющей области видимости производится позднее, при вызове вложенных функций, в результате все они получат одно и то же значение (значение, которое имела переменная цикла на последней итерации). То есть каждая функция в списке будет возвращать 4 во Области видимости и вложенные функции 417 второй степени, потому что во всех них переменная 1 имеет одно и то же значение: Это один из случаев, когда необходимо явно сохранять значение из объемлющей области видимости в виде аргумента со значением по умолчанию вместо использования ссылки на переменную из объемлющей области видимости.
То есть, чтобы этот фрагмент заработал, необходимо передать текущее значение переменной из объемлющей области видимости в виде значения по умолчанию. Значения по умолчанию вычисляются в момент создания вложенной функции (а не когда она вызывается), поэтому каждая из них сохранит свое собственное значение 1: »> Сет щахеАс(1опз(): астз = П Гог 1 1п галсе(б): № Ислользоаать значения ло унолчанню астз.аррепз()аазза х, 1=1: 1 ° х) № Сохранять текущее значанна з гетзгп астз »> астз = вахзАс(1опз() »> зстз[0)(2) 0 »> асса[2)(2) »> апта[4)(2) 16 №о* г № г ** 2 №4 **2 Это достаточно замысловатый случай, но с ним можно столкнуться на практике, особенно в программном коде, который генерирует функции- обработчики событий для элементов управления в графическом интерфейсе (например, обработчики нажатия кнопок). Подробнее о значениях по умолчанию и 1забда-выражениях мы поговорим в следующей главе, поэтому позднее вам может потребоваться вернуться к этому разделу.' В разделе «Ошибки при работе с функциями«к этой части книги, в конце следующей главы, мы также увидим, что существуют определенные проблемы с использованием изменяемых объектов, таких как списки и словари, при использовании их в качестве значений по умолчанию для аргументов (например, Се[ Г(а=[])).