Математический анализ. 3е издание
Скачать 4.86 Mb.
|
import sys >>> showall = (lambda x: map(sys.stdout.write, x)) Анонимные функции: lambda 445 >>> t = showall(['spam\n', 'toast\n', 'eggs\n']) spam toast eggs >>> showall = lambda x: [sys.stdout.write(line) for line in x] >>> t = showall(('bright\n', 'side\n', 'of\n', 'life\n')) bright side of life Теперь, когда я продемонстрировал вам некоторые уловки, я должен просить вас использовать их только в случае крайней необходимости. Без должной осторожности они могут сделать программный код нечи табельным (запутанным). Вообще, простое лучше сложного, явное лучше неявного, а понятные инструкции лучше таинственных выра жений. С другой стороны, при умеренном использовании эти приемы могут быть полезны. Вложенные lambdaвыражения и области видимости lambdaвыражения чаще других используют возможность поиска в об ласти видимости вложенной функции (символ E в названии правила LEGB, с которым мы познакомились в главе 16). Например, ниже lambda выражение находится внутри инструкции def – типичный слу чай – и потому получает значение имени x из области видимости объ емлющей функции, имевшееся на момент ее вызова: >>> def action(x): ... return (lambda y: x + y) # Создать и вернуть фцию, запомнить x >>> act = action(99) >>> act >>> act(2) 101 В предыдущей главе, где обсуждались области видимости вложенных функций, не говорилось о том, что lambdaвыражения обладают досту пом к именам во всех объемлющих lambdaвыражениях. Это сложно се бе вообразить, но представьте, что мы записали предыдущую инструк цию def в виде lambdaвыражения: >>> action = (lambda x: (lambda y: x + y)) >>> act = action(99) >>> act(3) 102 >>> ((lambda x: (lambda y: x + y))(99))(4) 103 446 Глава 17. Расширенные возможности функций Эта структура lambdaвыражений создает функцию, которая при вызо ве создает другую функцию. В обоих случаях вложенное lambdaвыра жение имеет доступ к переменной x в объемлющем lambdaвыражении. Это фрагмент будет работать, но программный код выглядит весьма за мысловато, поэтому в интересах соблюдения удобочитаемости лучше избегать использования вложенных друг в друга lambdaвыражений. Придется держать в уме: функции обратного вызова Другое распространенное применение lambdaвыражений состоит в определении функций обратного вызова для Tkinter GUI API. Например, следующий фрагмент создает кнопку, по нажатию которой на консоль выводится сообщение: import sys x = Button( text ='Press me', command=(lambda:sys.stdout.write('Spam\n'))) Здесь в качестве обработчика события регистрируется функция, сгенерированная lambdaвыражением в аргументе command. Преиму щество lambdaвыражения перед инструкцией def в данном случае состоит в том, что обработчик события нажатия на кнопку нахо дится прямо здесь же, в вызове функции, создающей эту кнопку. В действительности lambdaвыражение откладывает исполнение обработчика до того момента, пока не произойдет событие: вызов метода write произойдет, когда кнопка будет нажата, а не когда она будет создана. Поскольку правила областей видимости вложенных функций при меняются и к lambdaвыражениям, их проще использовать в ка честве функций обратного вызова. Начиная с версии Python 2.2, они автоматически получают доступ к переменным объемлю щих функций и в большинстве случаев не требуют передачи па раметров со значениями по умолчанию. Это особенно удобно при обращении к специальному аргументу экземпляра self, который является локальной переменной в объемлющих методах классов (подробнее о классах рассказывается в шестой части книги): class MyGui: def makewidgets(self): Button(command=(lambda: self.display("spam"))) def display(self, message): ...использовать текст сообщения... В предыдущих версиях даже self приходилось передавать в виде аргумента со значением по умолчанию. Применение функций к аргументам 447 Применение функций к аргументам В некоторых программах бывает необходимо вызывать самые разные функции одинаковым образом, заранее не зная ни имен функций, ни их аргументов (примеры, где этот прием может быть полезен, будут показаны позднее). Сделать это можно с помощью встроенной функ ции apply и специального синтаксиса вызова в языке Python. К моменту написания этих строк в версии Python 2.5 можно ис пользовать и функцию apply, и специальный синтаксис вызова, описываемые в этом разделе, но вполне вероятно, что в Python 3.0 функция apply исчезнет. Если для вас желательно, чтобы ваш программный код сохранил свою работоспособность в будущем, используйте специальный синтаксис вызова, а не функцию apply. Встроенная функция apply Для обеспечения динамичности сгенерированные функции можно вы зывать, передавая их в качестве аргументов функции apply вместе с кор тежем аргументов, которые должны быть переданы этой функции: >>> def func(x, y, z): return x + y + z >>> apply(func, (2, 3, 4)) 9 >>> f = lambda x, y, z: x + y + z >>> apply(f, (2, 3, 4)) 9 Функция apply просто вызывает функцию, переданную ей в первом ар гументе, и сопоставляет полученный кортеж аргументов с аргумента ми, которые ожидает получить функция. Так как список аргументов передается в виде кортежа (т. е. в виде структуры данных), программа может создавать его во время выполнения. 1 Истинная мощь функции apply заключается в том, что ей не требуется знать, сколько аргументов принимает вызываемая ею функция. На пример, можно с помощью условной инструкции if организовать вы бор из множества функций и списков аргументов и затем передавать их функции apply для вызова: if action, args = func1, (1,) else: action, args = func2, (1, 2, 3) 1 Будьте внимательны, не путайте функцию apply с функцией map, которая описывается в следующем разделе. Функция apply вызывает функцию, по лученную в аргументе, всего один раз, в то время как map вызывает функцию несколько раз, по одному разу для каждого элемента последовательности. 448 Глава 17. Расширенные возможности функций apply(action, args) В общем случае функцию apply удобно использовать, когда заранее нельзя определить список аргументов. Например, когда пользователь выбирает произвольную функцию пользовательского интерфейса, мо жет оказаться невозможным жестко определить вызов функции в сце нарии. Чтобы решить эту проблему, можно просто создать список аргу ментов в виде кортежа и вызвать функцию косвенно, с помощью apply: >>> args = (2,3) + (4,) >>> args (2, 3, 4) >>> apply(func, args) 9 Передача аргументов по ключу Функция apply поддерживает третий необязательный аргумент, в ко тором можно указать словарь, содержащий аргументы, передаваемые функции по ключу: >>> def echo(*args, **kwargs): print args, kwargs >>> echo(1, 2, a=3, b=4) (1, 2) {'a': 3, 'b': 4} Эта возможность позволяет конструировать во время выполнения спи ски позиционных аргументов и аргументов передаваемых по ключу: >>> pargs = (1, 2) >>> kargs = {'a':3, 'b':4} >>> apply(echo, pargs, kargs) (1, 2) {'a': 3, 'b': 4} applyподобный синтаксис вызова Тот же самый эффект, что дает функция apply, в языке Python можно получить с использованием специального синтаксиса вызова. Этот синтаксис отражает синтаксис передачи произвольного числа аргу ментов в заголовке инструкции def, с которым мы познакомились в главе 16. Например, предположим, что в этом примере используются имена с присвоенными им значениями из примера выше: >>> apply(func, args) # Традиционный способ: кортеж 9 >>> func(*args) # Новый, applyподобный синтаксис 9 >>> echo(*pargs, **kargs) # Словари с ключами также допустимы (1, 2) {'a': 3, 'b': 4} Этот специальный синтаксис вызова является более новым, чем функ ция apply, и, в общем, более предпочтительным. Он не дает какихли Отображение функций на последовательности: map 449 бо очевидных преимуществ перед явным вызовом apply, кроме соот ветствия заголовку инструкции def и необходимости ввода меньшего числа символов. Однако новый альтернативный синтаксис вызова по зволяет передавать дополнительные аргументы и является более уни версальным: >>> echo(0, *pargs, **kargs) # Обычный, *tuple, **dictionary (0, 1, 2) {'a': 3, 'b': 4} Отображение функций на последовательности: map Одна из наиболее часто встречающихся задач, которые решаются в про граммах, состоит в применении некоторой операции к каждому эле менту в списке или в другой последовательности и сборе полученных результатов. Например, обновление всех счетчиков в списке может быть выполнено с помощью простого цикла for: >>> counters = [1, 2, 3, 4] >>> >>> updated = [] >>> for x in counters: ... updated.append(x + 10) # Прибавить 10 к каждому элементу >>> updated [11, 12, 13, 14] Но, так как такие операции встречаются достаточно часто, язык Py thon предоставляет встроенную функцию, которая выполняет боль шую часть этой работы. Функция map применяет указанную функцию к каждому элементу последовательности и возвращает список, содер жащий результаты всех вызовов функции. Например: >>> def inc(x): return x + 10 # Функция, которая должна быть вызвана >>> map(inc, counters) # Сбор результатов [11, 12, 13, 14] Функция map была представлена в главе 13, как средство одновремен ного обхода сразу нескольких последовательностей в цикле. Как вы наверняка помните, в той главе вместо функции мы передавали объ ект None, чтобы выполнить попарное объединение элементов последо вательностей. Ниже приводится более полезный пример, где исполь зуется настоящая функция, применяемая ко всем элементам списка, – функция map вызывает функцию inc для каждого элемента списка и со бирает полученные результаты в новый список. Функция map ожидает получить в первом аргументе функцию, поэтому здесь часто можно встретить lambdaвыражения: >>> map((lambda x: x + 3), counters) # Выражениефункция [4, 5, 6, 7] 450 Глава 17. Расширенные возможности функций В данном случае функция прибавляет число 3 к каждому элементу списка counters, а так как эта функция нигде в другом месте больше не используется, она оформлена в виде lambdaвыражения. Такой вариант использования функции map представляет собой эквивалент цикла for, поэтому такую утилиту в общем виде можно представить так: >>> def mymap(func, seq): ... res = [] ... for x in seq: res.append(func(x)) ... return res >>> map(inc, [1, 2, 3]) [11, 12, 13] >>> mymap(inc, [1, 2, 3]) [11, 12, 13] Однако функция map является встроенной функцией, поэтому она дос тупна всегда, всегда работает одним и тем же способом и обладает неко торыми преимуществами производительности (проще говоря – она вы полняется быстрее, чем любой цикл for). Кроме того, функция map мо жет использоваться в более сложных ситуациях, чем показано здесь. Например, в данном случае имеется несколько аргументов с последо вательностями, а функция map извлекает их параллельно и передает как отдельные аргументы в функцию: >>> pow(3, 4) 81 >>> map(pow, [1, 2, 3], [2, 3, 4]) # 1**2, 2**3, 3**4 [1, 8, 81] Здесь функция pow при каждом вызове принимает от функции map два аргумента – по одному из каждой последовательности. Мы могли реа лизовать свою собственную функцию, имитирующую это действие, но вполне очевидно, что в этом нет никакой необходимости, так как име ется высокопроизводительная встроенная функция. Этот вызов функции map напоминает генераторы списков, кото рые рассматривались в главе 13 и с которыми мы встретимся еще раз далее в этой главе. Основное отличие состоит в том, что map применяет к каждому элементу последовательности не произ вольное выражение, а функцию. Вследствие этого ограничения она обладает меньшей гибкостью. Однако современная реализа ция map в некоторых случаях обладает более высокой производи тельностью, чем генераторы списков (например, когда отобража ется встроенная функция), и использовать ее проще. Благодаря этому функция map скорее всего останется доступной в версии Py thon 3.0. Однако в недавнем описании Python 3.0 предлагалось убрать из встроенного пространств имен (и, возможно, перенести в модули) эту функцию, а также функции reduce и filter, которые будут рассматриваться в следующем разделе. Средства функционального программирования: filter и reduce 451 В то время как функция map может остаться в языке Python, функ ции reduce и filter в версии 3.0 будут изъяты, отчасти потому, что они избыточны и легко могут быть реализованы с помощью гене раторов списков (функция filter легко замещается оператором if в генераторах списков), а отчасти изза своей сложности (функция reduce – одна из самых сложных функций в языке и одна из наиме нее понятных). Впрочем, я не могу предсказывать будущее, поэто му за дополнительной информацией об этих и других изменениях обращайтесь к примечаниям к выпуску Python 3.0. Эти функции попрежнему рассматриваются в данном издании книги потому, что они входят в состав текущей версии Python, и почти наверняка будут встречаться в программном коде еще какоето время. Средства функционального программирования: filter и reduce Функция map – это простейший представитель класса встроенных функций в языке Python, используемых в функциональном програм+ мировании , т. е. функций, которые применяют другие функции к по следовательностям. Родственные ей функции отфильтровывают эле менты с помощью функций, выполняющих проверку (filter), и при меняют функции к парам элементов, накапливая результаты (reduce). Например, следующий вызов функции filter отбирает элементы по следовательности больше нуля: >>> range( 5, 5) [5, 4, 3, 2, 1, 0, 1, 2, 3, 4] >>> filter((lambda x: x > 0), range( 5, 5)) [1, 2, 3, 4] Элементы последовательности, для которых применяемая функция возвращает истину, добавляются в список результатов. Как и функ ция map, filter является примерным эквивалентом цикла for, только она – встроенная функция и обладает высокой скоростью выполнения: >>> res = [ ] >>> for x in range( 5, 5): ... if x > 0: ... res.append(x) >>> res [1, 2, 3, 4] reduce – более сложная функция. Ниже приводятся два вызова функции reduce , которые вычисляют сумму и произведение элементов списка: >>> reduce((lambda x, y: x + y), [1, 2, 3, 4]) 10 >>> reduce((lambda x, y: x * y), [1, 2, 3, 4]) 24 452 Глава 17. Расширенные возможности функций На каждом шаге функция reduce передает текущую сумму или произве дение вместе со следующим элементом списка lambdaфункции. По умолчанию первый элемент последовательности принимается в качест ве начального значения. Ниже приводится цикл for, эквивалентный первому вызову, с жестко заданной операцией сложения внутри цикла: >>> L = [1,2,3,4] >>> res = L[0] >>> for x in L[1:]: ... res = res + x >>> res 10 Написать свою версию функции reduce (на тот случай, если она дейст вительно будет убрана из Python 3.0) достаточно просто: >>> def myreduce(function, sequence): ... tally = sequence[0] ... for next in sequence[1:]: ... tally = function(tally, next) ... return tally >>> myreduce((lambda x, y: x + y), [1, 2, 3, 4, 5]) 15 >>> myreduce((lambda x, y: x * y), [1, 2, 3, 4, 5]) 120 Если этот пример разжег ваш интерес, загляните также во встроенный модуль operator, который содержит функции, соответствующие встро енным выражениям, которые могут пригодиться при использовании некоторых функциональных инструментов: >>> import operator >>> reduce(operator.add, [2, 4, 6]) # Оператор сложения в виде функции 12 >>> reduce((lambda x, y: x + y), [2, 4, 6]) 12 Как и функция map, filter и reduce поддерживают мощные приемы функ ционального программирования. Некоторые программисты могут до полнить комплект средств функционального программирования языка Python также lambdaвыражениями в комплексе с функцией apply и ге нераторами списков, которые рассматриваются в следующем разделе. Еще раз о генераторах списков: отображения Отображение операций на последовательности и сбор результатов яв ляются настолько распространенной задачей в программировании на языке Python, что в версии Python 2.0 появилась новая особенность – генераторы списков , которые упрощают решение задач еще больше, чем только что рассмотренные функции. Мы уже встречались с гене Еще раз о генераторах списков: отображения 453 раторами списков в главе 13, но, так как они относятся к средствам функционального программирования, таким как функции map и fil ter , здесь мы вернемся к этой теме еще раз. С технической точки зре ния эта особенность не привязана к функциям; как мы увидим, гене раторы списков – более универсальные инструменты, чем map и filter, но иногда их проще понять, проводя аналогии с функциональными альтернативами. Основы генераторов списков Рассмотрим несколько примеров, демонстрирующих самые основы. Как было показано в главе 7, встроенная функция ord в языке Python возвращает целочисленный код ASCII единственного символа (обрат ной к ней является встроенная функция chr – она возвращает символ, соответствующий коду ASCII): >>> |