Главная страница

Математический анализ. 3е издание


Скачать 4.86 Mb.
Название3е издание
АнкорМатематический анализ
Дата04.02.2022
Размер4.86 Mb.
Формат файлаpdf
Имя файлаpython_01.pdf
ТипДокументы
#351981
страница55 из 98
1   ...   51   52   53   54   55   56   57   58   ...   98
ord('s')
115
Теперь предположим, что нам необходимо получить коды ASCII всех
символов в строке. Пожалуй, самый простой подход заключается в ис
пользовании цикла for, в котором полученные результаты добавляют
ся в список:
>>> res = []
>>> for x in 'spam':
... res.append(ord(x))
>>> res
[115, 112, 97, 109]
Однако теперь, когда мы уже познакомились с функцией map, тех же ре
зультатов мы можем достичь с помощью единственного вызова функ
ции без необходимости заботиться о создании и заполнении списка:
>>> res = map(ord, 'spam') # Применить функцию к последовательности
>>> res
[115, 112, 97, 109]
Но, начиная с версии Python 2.0, те же результаты можно получить с помощью генератора списка:
>>> res = [ord(x) for x in 'spam'] # Применит выражение к последовательности
>>> res
[115, 112, 97, 109]
Генераторы списков собирают результаты применения произвольного выражения к элементам последовательности и возвращают их в виде но
вого списка. Синтаксически генераторы списков заключаются в квад
ратные скобки (чтобы показать, что они конструируют списки). В про
стейшем виде генератор списков представляет собой выражение, опери
рующее переменной, за которым следует конструкция, напоминающая

454
Глава 17. Расширенные возможности функций заголовок цикла for, в котором используется та же переменная. Во вре
мя выполнения интерпретатор Python собирает результаты выраже
ния для каждой итерации подразумеваемого списка.
Предыдущий пример дает тот же результат, что цикл for и вызов функции map выше. Однако генераторы списков более удобны, особен
но, когда требуется применить к последовательности произвольное выражение:
>>> [x ** 2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Здесь создается список квадратов чисел от 0 до 9 (здесь мы позволили интерактивной оболочке автоматически вывести список – если вам не
обходимо сохранить список, присвойте его переменной). Чтобы вы
полнить аналогичные действия с помощью функции map, потребова
лось бы написать отдельную функцию, реализующую операцию возве
дения в квадрат. Так как эта функция нам не потребуется в другом месте программы, ее можно было бы (хотя это и не обязательно) реали
зовать не с помощью инструкции def, а в виде lambdaвыражения:
>>> map((lambda x: x ** 2), range(10))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Этот вызов выполняет ту же работу, и он всего на несколько символов длиннее эквивалентной реализации на базе генератора списка. Кроме того, он ненамного сложнее (по крайней мере, для тех, кто разбирает
ся в lambdaвыражениях). Однако в случае более сложных выражений генераторы списков часто выглядят проще. В следующем разделе бу
дет показано почему.
Добавление проверок и вложенных циклов
Генераторы списков обладают даже еще большей гибкостью, чем было показано до сих пор. Например, после цикла for можно добавить опе
ратор if для реализации логики выбора. Генераторы списков с операто
ром if можно представить как аналог встроенной функции filter, пред
ставленной в предыдущем разделе, – они пропускают элементы, для ко
торых условное выражение в операторе if возвращает ложь. Ниже при
водятся две версии реализации выбора четных чисел в диапазоне от 0
до 4 – с помощью генератора списка и с помощью функции filter, кото
рая использует небольшое lambdaвыражение для выполнения проверки.
Для сравнения здесь также показана реализация на основе цикла for:
>>> [x for x in range(5) if x % 2 == 0]
[0, 2, 4]
>>> filter((lambda x: x % 2 == 0), range(5))
[0, 2, 4]
>>> res = [ ]
>>> for x in range(5):

Еще раз о генераторах списков: отображения
455
... if x % 2 == 0:
... res.append(x)
>>> res
[0, 2, 4]
Во всех этих версиях используется оператор деления по модулю (оста
ток от деления) %, с помощью которого определяются четные числа: ес
ли остаток от деления на два равен нулю, следовательно, число четное.
Вызов функции filter здесь также выглядит ненамного длиннее, чем генератор списка. Однако, генераторы списков дают возможность объ
единять оператор if и произвольные выражения, позволяя добиться эффекта действия функций filter и map в единственном выражении:
>>> [x ** 2 for x in range(10) if x % 2 == 0]
[0, 4, 16, 36, 64]
На этот раз создается список квадратов четных чисел в диапазоне от 0
до 9: цикл for пропускает числа, для которых условное выражение,
присоединенное справа, возвращает ложь, а выражение слева вычис
ляет квадраты. Эквивалентный вызов функции map потребовал бы от нас больше работы – нам пришлось бы объединить выбор элементов с помощью функции filter и обход списка с помощью map, что в ре
зультате дает более сложное выражение:
>>> map((lambda x: x**2), filter((lambda x: x % 2 == 0), range(10)))
[0, 4, 16, 36, 64]
В действительности, генераторы списков обладают еще большей гиб
костью. Они дают возможность запрограммировать любое число вло
женных циклов for, каждый из которых может сопровождаться собст
венным оператором if с условным выражением. В общем виде генера
торы списков выглядят следующим образом:
[ expression for target1 in sequence1 [if condition]
for target2 in sequence2 [if condition] ...
for targetN in sequenceN [if condition] ]
Вложенные операторы for в генераторах списков действуют точно так же, как вложенные инструкции for. Например, следующий фрагмент:
>>> res = [x + y for x in [0, 1, 2] for y in [100, 200, 300]]
>>> res
[100, 200, 300, 101, 201, 301, 102, 202, 302]
дает тот же результат, что и более объемный эквивалент:
>>> res = []
>>> for x in [0, 1, 2]:
... for y in [100, 200, 300]:
... res.append(x + y)
>>> res
[100, 200, 300, 101, 201, 301, 102, 202, 302]

456
Глава 17. Расширенные возможности функций
Генераторы списков конструируют списки, однако итерации могут выполняться по любым последовательностям и итерируемым объек
там. Следующий, немного похожий, фрагмент выполняет обход уже не списков чисел, а строк, и возвращает результаты конкатенации:
>>> [x + y for x in 'spam' for y in 'SPAM']
['sS', 'sP', 'sA', 'sM', 'pS', 'pP', 'pA', 'pM',
'aS', 'aP', 'aA', 'aM', 'mS', 'mP', 'mA', 'mM']
В заключение приведу более сложный генератор списка, который ил
люстрирует действие оператора if, присоединенного к вложенному оператору for:
>>> [(x, y) for x in range(5) if x % 2 == 0 for y in range(5) if y % 2 == 1]
[(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]
Это выражение возвращает возможные комбинации четных и нечет
ных чисел в диапазоне от 0 до 4. Условные выражения отфильтровы
вают элементы в каждой из последовательностей. Ниже приводится эквивалентная реализация на базе инструкций:
>>> res = []
>>> for x in range(5):
... if x % 2 == 0:
... for y in range(5):
... if y % 2 == 1:
... res.append((x, y))
>>> res
[(0, 1), (0, 3), (2, 1), (2, 3), (4, 1), (4, 3)]
Не забывайте, что в случае, когда генератор списков становится слиш
ком сложным для понимания, вы всегда можете развернуть вложен
ные операторы for и if (добавляя отступы), чтобы получить эквива
лентные инструкции. Программный код при этом получится более длинным, но более понятным.
Эквивалентные реализации на основе функций map и filter оказались бы чрезвычайно сложными и имели бы глубокую вложенность вызо
вов, поэтому я даже не буду пытаться продемонстрировать их. Остав
лю эту задачу в качестве упражнения мастерам Дзен, бывшим про
граммистам на языке LISP и просто безумцам.
Генераторы списков и матрицы
Рассмотрим еще одно, более сложное применение генераторов спи
сков, чтобы поупражнять мозги. Основной способ реализации матриц
(они же – многомерные массивы) в языке Python заключается в ис
пользовании вложенных списков. В следующем примере определяют
ся матрицы 3x3 в виде вложенных списков:
>>> M = [[1, 2, 3],
... [4, 5, 6],

Еще раз о генераторах списков: отображения
457
... [7, 8, 9]]
>>> N = [[2, 2, 2],
... [3, 3, 3],
... [4, 4, 4]]
При такой организации всегда можно использовать обычную операцию индексирования для обращения к строкам и элементам внутри строк:
>>> M[1]
[4, 5, 6]
>>> M[1][2]
6
Генераторы списков являются мощным средством обработки таких структур данных, потому что они позволяют автоматически сканиро
вать строки и столбцы матриц. Например, несмотря на то, что при та
кой структуре матрицы хранятся в виде списка строк, мы легко мо
жем извлечь второй столбец, просто обходя строки матрицы и выби
рая элементы из требуемого столбца или выполняя обход требуемых позиций в строках:
>>> [row[1] for row in M]
[2, 5, 8]
>>> [M[row][1] for row in (0, 1, 2)]
[2, 5, 8]
Пользуясь позициями, мы также легко можем извлечь элементы, ле
жащие на диагонали. В следующем примере используется функция range
– она создает список смещений, который затем используется для индексирования строк и столбцов одним и тем же значением. В ре
зультате сначала выбирается M[0][0], затем M[1][1] и т. д. (здесь пред
полагается, что матрица имеет одинаковое число строк и столбцов):
>>> [M[i][i] for i in range(len(M))]
[1, 5, 9]
Наконец, проявив немного изобретательности, генераторы списков можно использовать для объединения нескольких матриц. Первый пример ниже создает простой список, содержащий результаты умно
жения соответствующих элементов двух матриц, а второй создает структуру вложенных списков с теми же самыми значениями:
>>> [M[row][col] * N[row][col] for row in range(3) for col in range(3)]
[2, 4, 6, 12, 15, 18, 28, 32, 36]
>>> [[M[row][col] * N[row][col] for col in range(3)] for row in range(3)]
[[2, 4, 6], [12, 15, 18], [28, 32, 36]]
В последнем выражении итерации по строкам выполняются во внеш
нем цикле: для каждой строки запускается итерация по столбцам, ко
торая создает одну строку в матрице с результатами. Это выражение эквивалентно следующему фрагменту:

458
Глава 17. Расширенные возможности функций
>>> res = []
>>> for row in range(3):
... tmp = []
... for col in range(3):
... tmp.append(M[row][col] * N[row][col])
... res.append(tmp)
>>> res
[[2, 4, 6], [12, 15, 18], [28, 32, 36]]
В отличие от этого фрагмента, версия на базе генератора списков уме
щается в единственную строку, вероятно, работает значительно быст
рее в случае больших матриц, но, правда, может и взорвать ваш мозг!
На этом перейдем к следующему разделу.
Понимание генераторов списков
При такой степени гибкости генераторы списков очень быстро могут стать непостижимыми, особенно при наличии вложенных конструк
ций. Поэтому начинающим осваивать язык Python я рекомендую в большинстве случаев использовать простые циклы for и функцию map
(если они не становятся слишком сложными). Здесь также дейст
вует правило «чем проще, тем лучше»: лаконичность программного кода – намного менее важная цель, чем его удобочитаемость.
Однако в настоящее время усложнение программного кода обеспечи
вает более высокую его производительность: проведенные тесты свиде
тельствуют, что функция map работает практически в два раза быстрее,
чем эквивалентные циклы for, а генераторы списков обычно немного быстрее, чем функция map.
1
Это различие в скорости выполнения обу
словлено тем фактом, что функция map и генераторы списков реализо
ваны на языке C, что обеспечивает более высокую скорость, чем вы
полнение циклов for внутри виртуальной машины Python (PVM).
Применение циклов for делает логику программы более явной, поэто
му я рекомендую использовать их для обеспечения большей простоты.
Однако, функция map и генераторы списков стоят того, чтобы знать и применять их для реализации простых итераций, а также в случаях,
1
Производительность этих тестов может зависеть от вида решаемой задачи,
а также от изменений и оптимизаций в самом интерпретаторе языка Py
thon. Например, в последних версиях Python была увеличена скорость вы
полнения инструкции цикла for. Тем не менее, генераторы списков обычно показывают более высокую скорость работы, чем циклы for, и даже более высокую, чем функция map (хотя функция map может выйти победителем в состязании среди встроенных функций). Чтобы проверить скорость рабо
ты альтернативных реализаций, можно использовать функции time.clock и time.time в модуле time. В версии Python 2.4 появился новый модуль timeit
, который рассматривается в разделе «Хронометраж итерационных альтернатив» далее в этой главе.

Еще раз об итераторах: генераторы
459
когда скорость работы приложения имеет критически важное значе
ние. Кроме того, функция map и генераторы списков являются выраже
ниями и синтаксически могут находиться там, где недопустимо ис
пользовать инструкцию for, например в теле lambdaвыражений, в ли
тералах списков и словарей и во многих других случаях. То есть вы должны стараться писать простые функции map и генераторы списков,
а в более сложных случаях использовать полные инструкции.
Еще раз об итераторах: генераторы
В этой части книги мы уже познакомились с обычными функциями, ко
торые получают входные параметры и возвращают результат. Однако точно так же возможно написать функцию, которая может возвращать значение, а позднее продолжить свою работу с того места, где она была приостановлена. Такие функции известны как генераторы, потому что они генерируют последовательность значений с течением времени.
Функциигенераторы во многом похожи на обычные функции, единст
венное отличие состоит в том, что они автоматически поддерживают итерационный протокол и могут использоваться в контексте итераций.
Мы рассмотрели итераторы в главе 13, а здесь мы взглянем на них еще раз, чтобы увидеть, какое отношение они имеют к генераторам.
Придется держать в уме:
генераторы списков и map
Ниже приводится более реалистичный пример использования генераторов списков и функции map (мы решали эту задачу с по
мощью генераторов списков в главе 13, а здесь мы снова вернем
ся к ней, чтобы продемонстрировать альтернативную реализа
цию на базе функции map). Вспомните, что метод файлов read
lines возвращает строки с символом конца строки (\n) в конце:
>>> open('myfile').readlines()
['aaa\n', 'bbb\n', 'ccc\n']
Если требуется удалить символы конца строки, их можно отсечь сразу во всех строках за одно действие с помощью генератора списков или функции map:
>>> [line.rstrip() for line in open('myfile').readlines()]
['aaa', 'bbb', 'ccc']
>>> [line.rstrip() for line in open('myfile')]
['aaa', 'bbb', 'ccc']
>>> map((lambda line: line.rstrip()), open('myfile'))
['aaa', 'bbb', 'ccc']

460
Глава 17. Расширенные возможности функций
В отличие от обычных функций, которые возвращают значение и за
вершают работу, функциигенераторы автоматически приостанавли
вают и возобновляют свое выполнение, при этом сохраняя информа
цию, необходимую для генерации значений.
Вследствие этого часто они представляют собой удобную альтернативу вычислению всей серии значений заранее и ручному сохранению и вос
становлению состояния в классах. Функциигенераторы при приоста
новке автоматически сохраняют информацию о своем состоянии, под которым понимается вся локальная область видимости, со всеми ло
кальными переменными, которая становится доступной сразу же, как только функция возобновляет работу.
Главное отличие функцийгенераторов от обычных функций состоит в том, что генератор поставляет значение, а не возвращает его – ин
струкция yield приостанавливает работу функции и передает значение вызывающей программе, при этом сохраняется информация о состоя
нии, необходимая, чтобы возобновить работу с того места, где она бы
В последних двух случаях используются файловые итераторы
(по сути это означает, что вам не требуется вызывать метод, кото
рый будет читать строки из файла). Вызов функции map выглядит немного длиннее, чем генератор списков, но ни в одном из этих двух случаев не требуется явно управлять списком результатов.
Кроме того, генераторы списков могут играть роль операции из
влечения столбца. Стандартный прикладной интерфейс доступа к базам данных в языке Python возвращает результаты запроса в виде списка кортежей, как показано ниже. Список – это табли
ца, кортежи – это строки, а элементы кортежей – это значения столбцов:
listoftuple = [('bob', 35, 'mgr'), ('mel', 40, 'dev')]
Выбрать все значения из определенного столбца можно и вруч
ную, с помощью цикла for, но функция map и генераторы списков сделают это быстрее и за один шаг:
>>> [age for (name, age, job) in listoftuple]
[35, 40]
>>> map((lambda (name, age, job): age), listoftuple)
[35, 40]
В обоих случаях используется операция присваивания кортежа,
чтобы извлечь значения в список.
За дополнительной информацией о прикладных интерфейсах языка Python обращайтесь к другим книгам и источникам ин
формации.

Еще раз об итераторах: генераторы
461
ла приостановлена. Это позволяет функциям воспроизводить последо
вательности значений в течение долгого времени, вместо того чтобы создавать всю последовательность сразу и возвращать ее в виде некото
рой конструкции, такой как список.
Функциигенераторы тесно связаны с понятием протокола итераций в языке Python. Проще говоря, функции, содержащие инструкцию yield
, компилируются особым образом, как генераторы – при вызове они возвращают объектгенератор, поддерживающий интерфейс итера
ций. Функциигенераторы могут также содержать инструкцию return,
которая завершает генерацию значений.
Объекты итераторов в свою очередь определяют метод next, который либо возвращает следующий элемент в итерации, либо возбуждает ис
ключение (StopIteration) в конце итераций. Доступ к итератору можно получить с помощью встроенной функции iter. Циклы for в языке Py
thon используют такой итерационный протокол, если он поддержива
ется, для обхода последовательностей (или генераторов последователь
ностей). Если протокол не поддерживается, инструкция for терпит не
удачу и возвращается к операции индексирования последовательности.
1   ...   51   52   53   54   55   56   57   58   ...   98


написать администратору сайта