Математический анализ. 3е издание
Скачать 4.86 Mb.
|
• Размер: каждая функция должна иметь относительно небольшой размер. Это условие естественным образом следует из предыдущего, однако если функция начинает занимать несколько экранов, – это 470 Глава 17. Расширенные возможности функций явный признак, что пора подумать о том, чтобы разбить ее. Особен но, если учесть краткость, присущую языку Python. Длинная функ ция с большой глубиной вложенности часто свидетельствует о прома хах в проектировании. Сохраняйте функции короткими и простыми. • Взаимодействие: избегайте непосредственного изменения пере! менных в другом модуле. Мы рассматривали эту концепцию в пре дыдущей главе и еще вернемся к ней в следующей части книги, ко гда сконцентрируем свое внимание на модулях. И тем не менее на помню, что непосредственное изменение переменных в других мо дулях устанавливает тесную зависимость между модулями, так же как тесную зависимость устанавливает изменение глобальных пе ременных из функций – модули становятся сложными в понима нии и малопригодными для многократного использования. Всегда, когда это возможно, для изменения переменных модуля вместо прямых инструкций присваивания используйте функции доступа. На рис. 17.1 приводится схема организации взаимодействий функций с внешним миром – входные данные поступают в функции из элемен тов слева, а результаты могут возвращаться в любой из форм справа. Многие программисты предпочитают использовать для ввода только аргументы, и для вывода – только инструкцию return. Конечно, из приведенных выше правил проектирования есть свои ис ключения, включая те, что связаны с поддержкой ООП в языке Python. Как вы увидите в шестой части книги, классы в языке Python зависят от изменения передаваемого изменяемого объекта – функции воздей ствуют на атрибуты аргумента self, получаемого автоматически, из Рис. 17.1. Окружение функции времени выполнения. Функция может полу+ чать входные данные и возвращать результаты различными способами, однако функции проще в понимании и сопровождении, когда входные данные передаются в виде аргументов, а возврат результатов производится с помо+ щью инструкции return или посредством воздействия на изменяемые аргу+ менты, при условии, что последнее предполагается вызывающей программой Другие функции Выходные данные Входные данные Аргументы Глобальные переменные Файлы/потоки Функция Локальные переменные Инструкция return Изменяемые аргументы Глобальные переменные Файлы/потоки Концепции проектирования функций 471 меняя информацию о его состоянии (например, self.name = 'bob'). Кроме того, когда классы не используются, глобальные переменные часто представляют для функций в модуле наилучший способ сохране ния состояния между вызовами. Побочные эффекты в этом случае не опасны, потому что они ожидаемы. Функции – это объекты: косвенный вызов Так как функции в языке Python во время выполнения являются объ ектами, можно написать такую программу, которая будет обрабаты вать их в общем виде. Объекты функций могут присваиваться, пере даться другим функциям, сохраняться в структурах данных и т. д., как если бы они были простыми числами или строками. Мы встречали уже такие способы использования в более ранних примерах. Кроме то го, объекты функций поддерживают специальные операции: они мо гут вызываться перечислением аргументов в круглых скобках, сле дующих сразу же за выражением функции. И тем не менее, функции принадлежат к той же категории, что и другие объекты. Например, в имени, которое используется в инструкции def, нет ниче го уникального: это всего лишь переменная, которая создается в теку щей области видимости, как если бы она стояло слева от знака =. По сле того как инструкция def будет выполнена, имя функции представ ляет собой всего лишь ссылку на объект – ее можно присвоить другим именам и вызывать функцию по любому из них (не только по первона чальному имени): >>> def echo(message): # Имени echo присваивается объект функции ... print message >>> x = echo # Теперь на эту функцию ссылается еще и имя x >>> x('Hello world!') # Вызов объекта добавлением () Hello world! Поскольку аргументы передаются путем присваивания объектов, функции легко можно передавать другим функциям в виде аргумен тов. В результате вызываемая функция может вызвать переданную ей функцию простым добавлением списка аргументов в круглых скобках: >>> def indirect(func, arg): ... func(arg) # Вызов объекта добавлением () >>> indirect(echo, 'Hello jello!') # Передача функции в функцию Hello jello! Существует даже возможность наполнять структуры данных функ циями, как если бы они были простыми числами или строками. В этом нет ничего особенного, так как составные типы объектов могут содер жать объекты любых типов: >>> schedule = [ (echo, 'Spam!'), (echo, 'Ham!') ] >>> for (func, arg) in schedule: 472 Глава 17. Расширенные возможности функций ... func(arg) Spam! Ham! В этом фрагменте просто выполняется обход списка schedule и произ водится вызов функции echo с одним аргументом (обратите внимание на операцию присваивания кортежа в заголовке инструкции цикла for , которая была представлена в главе 13). Отсутствие описаний ти пов делает язык программирования Python невероятно гибким. Типичные ошибки при работе с функциями При работе с функциями вас поджидают подводные камни, о которых вы можете не догадываться. Они не всегда видны, некоторые из них исчезли в последних версиях, но большая часть оставшихся продол жает ставить в тупик начинающих программистов. Локальные имена определяются статически Как известно, имена, которым выполняется присваивание внутри функции, по умолчанию рассматриваются как локальные – они рас полагаются в области видимости функции и существуют только во время работы функции. Но я еще не говорил, что локальные перемен ные определяются статически, во время компиляции программного кода в инструкции def, а не в соответствии с операциями присваива ния, производимыми во время выполнения. Эта особенность становит ся причиной появления самых причудливых сообщений в группе но востей, получаемых от начинающих программистов. Обычно, если внутри функции имени не присваивается какоелибо значение, поиск его будет производиться в области видимости объем лющего модуля: >>> X = 99 >>> def selector(): # Переменная X используется, но ей ничего # не присваивается ... print X # Переменная X будет найдена в глобальной # области видти >>> selector() 99 В этом фрагменте переменная X внутри функции определяется как пе ременная X модуля. Но посмотрите, что произойдет, если добавить ин струкцию присваивания переменной X после ее использования: >>> def selector(): ... print X # Переменная еще не существует! ... X = 88 # X классифицируется как локальная переменная ... # То же самое происходит при "import X", "def X"... Типичные ошибки при работе с функциями 473 >>> selector() Traceback (most recent call last): File " File " UnboundLocalError: local variable 'X' referenced before assignment (UnboundLocalError: обращение к локальной переменной 'X' до присваивания) Было получено сообщение о том, что переменная не определена, но причина его появления неочевидна. Этот программный код компили руется интерпретатором во время ввода в интерактивной оболочке или во время импорта модуля. Во время компиляции Python обнаружива ет операцию присваивания переменной X и делает вывод, что X – это локальное имя везде в теле функции. Но во время выполнения функ ции, изза того что к моменту вызова инструкции print операция при сваивания еще не производилась, интерпретатор сообщает о том, что имя не определено. Согласно этому правилу использования имен он го ворит, что обращение к локальной переменной X произведено до того, как ей было присвоено значение. Фактически, любая операция при сваивания внутри функции создает локальное имя. Операция импор тирования, =, вложенные инструкции def, вложенные определения классов и т. д., – все трактуются именно таким образом. Проблема возникает изза того, что операция присваивания делает имена локальными для всей функции, а не только для той ее части, ко торая следует за инструкцией присваивания. На самом деле предыду щий пример далеко не однозначен: что имелось в виду – требовалось вывести глобальную переменную X и затем создать локальную пере менную или это просто ошибка программиста? Так как Python интер претирует имя X как локальное во всей функции, то это ошибка – если вы действительно хотите вывести значение глобальной переменной X, объявите ее глобальной с помощью инструкции global: >>> def selector(): ... global X # Принудительное объявление X глобальным (везде) ... print X ... X = 88 >>> selector() 99 При этом следует помнить, что в этом случае операция присваивания изменит глобальную переменную X, а не локальную. Внутри функции можно использовать как локальную, так и глобальную версии одного и того же имени. Если вы действительно предполагаете вывести значе ние глобальной переменной, а затем присвоить значение локальной версии того же самого имени, импортируйте вмещающий модуль и об ращайтесь к глобальной переменной как к атрибуту модуля: >>> X = 99 >>> def selector(): ... import __main__ # Импортировать вмещающий модуль 474 Глава 17. Расширенные возможности функций ... print __main__.X # Квалифицированное обращение # к глобальной версии имени ... X = 88 # Неквалифицированное локальное имя X ... print X # Вывести локальную версию имени >>> selector() 99 88 Обращение по квалифицированному (полному) имени (с .X) приводит к извлечению значения из пространства имен объекта. Пространством имен интерактивной оболочки является модуль с именем __main__, по этому при обращении по имени __main__.X извлекается глобальная вер сия X. Если чтото вам показалось непонятным, прочитайте пятую часть книги. 1 Значения по умолчанию и изменяемые объекты Значения по умолчанию для аргументов функции вычисляются и за поминаются в момент выполнения инструкции def, а не при вызове функции. Внутренняя реализация Python сохраняет по одному объек ту для каждого аргумента со значением по умолчанию, присоединен ного к функции. Вычисление значений по умолчанию в момент определения функции позволяет, в случае необходимости, сохранять значения из объемлю щей области видимости. Но, так как значения по умолчанию сохраня ются между вызовами функции, следует быть внимательным при воз действии на изменяемые значения по умолчанию. Например, следую щая функция использует пустой список в качестве значения по умол чанию своего аргумента, а затем изменяет его при каждом вызове: >>> def saver(x=[]): # Объект списка сохраняется ... x.append(1) # При каждом вызове изменяется один и тот же объект! ... print x >>> saver([2]) # Значение по умолчанию не используется [2, 1] >>> saver() # Используется значение по умолчанию [1] >>> saver() # Список растет при каждом вызове! [1, 1] >>> saver() [1, 1, 1] 1 Положение дел с локальными переменными в языке Python к настоящему моменту несколько улучшилось, потому что в данном случае выводится бо лее определенное сообщение об ошибке «обращение к локальной перемен ной до присваивания», которое показано в листинге примера (теперь оно используется вместо более расплывчатого сообщения об ошибке, связанной с именем), впрочем, этот вид ошибки все еще встречается. Типичные ошибки при работе с функциями 475 Некоторые воспринимают такое поведение как достоинство – изменяе мые аргументы по умолчанию сохраняют свое состояние между вызова ми функции, поэтому они могут играть роль, подобную роли статиче+ ских локальных переменных в языке C. В некотором смысле они ведут себя как глобальные переменные за исключением того, что их имена яв ляются локальными по отношению к функциям, вследствие чего исклю чается конфликт имен с переменными, определенными в другом месте. Для большинства же это выглядит как недостаток, особенно для тех, кто впервые сталкивается с этой особенностью. В языке Python суще ствует лучший способ сохранения состояния между вызовами функ ций (например, за счет использования классов, которые будут рас сматриваться в шестой части книги). Кроме того, такое поведение аргументов по умолчанию сложно запом нить (и вообще понять). Они могут изменяться с течением времени. В предыдущем примере для значения по умолчанию существует един ственный объект списка – тот, что был создан в момент выполнения инструкции def. При каждом обращении к функции не будет созда ваться новый список, поэтому он будет расти с каждым новым вызо вом – он не опустошается при каждом вызове. Если такое поведение является неприемлемым, можно просто созда вать копию аргумента по умолчанию в начале тела функции или пере местить выражение, возвращающее значение по умолчанию, в тело функции. Поскольку в этом случае значение по умолчанию будет на ходиться в программном коде, который выполняется при каждом вы зове функции, вы всякий раз будете получать новый объект: >>> def saver(x=None): ... if x is None: # Аргумент отсутствует? ... x = [] # Создать новый список ... x.append(1) # Изменить объект списка ... print x >>> saver([2]) [2, 1] >>> saver() # Список больше не растет [1] >>> saver() [1] Между прочим, инструкцию if в этом примере в большинстве случаев можно было бы заменить выражением x = x or [], где используется тот факт, что оператор or в языке Python возвращает один из двух объек тов: если аргумент отсутствует, имя x получит значение по умолчанию None , и тогда оператор or вернет новый пустой список справа от него. Однако это не совсем одно и то же. Если функции будет передан пус той список, оператор вернет вновь созданный список вместо получен ного в аргументе, как это делает инструкция if. (Выражение примет вид [] or [], которое возвращает новый пустой список справа, – вер 476 Глава 17. Расширенные возможности функций нитесь к разделу «Проверка истинности» в главе 12, если вам не по нятно, почему так происходит). В разных программах могут предъяв ляться разные требования к такому поведению. Функции, не возвращающие результат В языке Python функции могут не иметь инструкцию return (или yield ). Когда функция не возвращает управление явно, выход из нее происходит, когда поток управления достигает конца тела функции. С технической точки зрения все функции возвращают некоторое зна чение – в отсутствие инструкции return функция автоматически воз вращает объект None: >>> def proc(x): ... print x # Нет возвращаемого значения, возвращается None >>> x = proc('testing 123...') testing 123... >>> print x None Такие функции, как эта, не имеющие инструкции return, представля ют собой эквивалент того, что в других языках программирования на зывается «процедурами». Как правило, они вызываются как инструк ции, а возвращаемое значение None игнорируется, поскольку они вы полняют свою работу, не вычисляя результат. Об этом следует помнить, потому что интерпретатор ничего не сообщит вам, если вы попытаетесь присвоить результат функции, которая ни чего не возвращает. Например, присваивание результата метода спи сков append не вызывает появления ошибки, но при этом вы получите объект None, а не обновленный список: >>> list = [1, 2, 3] >>> list = list.append(4) # Метод append – это "процедура" >>> print list # Метод append изменяет сам список None Как упоминалось в разделе «Типичные ошибки программирования» в главе 14, действие таких функций проявляется как побочный эф фект и они обычно вызываются как инструкции, а не как выражения. Переменные цикла в объемлющей области видимости Эта ошибка была описана в главе 16, когда мы рассматривали области видимости объемлющих функций, однако напомню еще раз: будьте внимательны при использовании переменных в области видимости объемлющей функции, которые изменяются объемлющим циклом – все ссылки на эту переменную будут запоминать значение, которое бу дет иметь переменная в последней итерации цикла. Чтобы сохранить значения переменной цикла в каждой итерации, используйте аргу В заключение 477 менты со значениями по умолчанию (дополнительные сведения по этой теме вы найдете в главе 16). В заключение В этой главе мы рассмотрели расширенные понятия, связанные с функ циями – lambdaвыражения; функциигенераторы и инструкцию yield; выражениягенераторы: applyподобный синтаксис вызова; инстру менты функционального программирования, такие как map, filter и reduce; и общие правила проектирования функций. Мы также по вторно рассмотрели итераторы и генераторы списков, просто потому, что они так же связаны с функциональным программированием, как и инструкции циклов. В завершение изучения итерационных концеп ций мы произвели измерения производительности различных методов выполнения итераций. Наконец, мы коротко рассмотрели типичные ошибки, допускаемые при работе с функциями, чтобы помочь вам обойти потенциальные ловушки. Эта глава завершает функциональную часть книги. В следующей час ти мы рассмотрим модули – вершину в иерархии структур языка Py thon; структуру, в которой всегда и располагаются наши функции. По сле этого мы займемся исследованием классов – инструментов, кото рые являются пакетами функций со специальным первым аргумен том. Как вы увидите, все, что мы здесь узнали, пригодится везде, где далее в книге будут появляться функции. Но прежде чем двинуться дальше, проверьте, насколько вы овладели основами функций, ответив на контрольные вопросы к главе и выпол нив упражнения для этой части. |