Марк Лутц - Изучаем Python, Четвертое издание (1184811), страница 103
Текст из файла (страница 103)
Поскольку эта концепция циклов занимает такое важное положение,мы еще раз вернемся к ней, когда будем рассматривать генераторы списковниже в этой главе.Параллельный обход: zip и mapКак было показано выше, встроенная функция range позволяет выполнять обход отдельных частей последовательностей. В том же духе встроенная функцияzip позволяет использовать цикл for для обхода нескольких последовательностей параллельно. Функция zip принимает одну или несколько последовательностей в качестве аргументов и возвращает список кортежей, составленных изсоответствующих элементов этих последовательностей. Например, предположим, что мы выполняем обработку двух списков:>>> L1 = [1,2,3,4]>>> L2 = [5,6,7,8]Для объединения элементов этих списков можно использовать функцию zip,которая создаст список кортежей из пар элементов (подобно функции range,в версии 3.0 функция zip возвращает итерируемый объект, поэтому, чтобы вывести все результаты, возвращаемые этой функцией, необходимо обернуть еевызов вызовом функции list, – подробнее об итераторах рассказывается в следующей главе):>>> zip(L1, L2)<zip object at 0x026523C8>>>> list(zip(L1,L2))# Функция list необходима в 3.0, но не в 2.6[(1, 5), (2, 6), (3, 7), (4, 8)]Такой результат может пригодиться в самых разных ситуациях, но применительно к циклу for он обеспечивает возможность выполнения параллельныхитераций:>>>......1 52 63 74 8for (x, y) in zip(L1, L2):print(x, y, ‘--’, x+y)-----681012Здесь выполняется обход результата обращения к функции zip, то есть пар,составленных из элементов двух списков.
Обратите внимание, что в этом цикле используется операция присваивания кортежей для получения элементов412Глава 13. Циклы while и forкаждого кортежа, полученного от функции zip. На первой итерации она будетвыглядеть, как если бы была выполнена инструкция (x, y) = (1, 5).Благодаря этому мы можем сканировать оба списка L1 и L2 в одном цикле. Тотже эффект можно получить с помощью цикла while, в котором доступ к элементам производится вручную, но такой цикл будет сложнее в реализации и наверняка медленнее, чем прием, основанный на использовании for/zip.Функция zip на самом деле более универсальна, чем можно было бы представить на основе этого фрагмента. Например, она принимает последовательности любого типа (в действительности – любые итерируемые объекты, включаяи файлы) и позволяет указывать более двух аргументов.
При вызове с тремяаргументами, как показано в следующем примере, она конструирует списоккортежей, состоящих из трех элементов, выбирая элементы из каждой последовательности с одним и тем же смещением (с технической точки зрения, из Nаргументов функция zip создает N-мерный кортеж):>>> T1, T2, T3 = (1,2,3), (4,5,6), (7,8,9)>>> T3(7, 8, 9)>>> list(zip(T1,T2,T3))[(1, 4, 7), (2, 5, 8), (3, 6, 9)]Длина списка, возвращаемого функцией zip, равна длине кратчайшей из последовательностей, если аргументы имеют разную длину.
В следующем примере выполняется объединение двух строк с целью параллельной обработкиих символов, при этом результат содержит столько кортежей, сколько былоэлементов в кратчайшей последовательности:>>> S1 = ‘abc’>>> S2 = ‘xyz123’>>>>>> list(zip(S1, S2))[(‘a’, ‘x’), (‘b’, ‘y’), (‘c’, ‘z’)]Эквивалентная функция map в Python 2.6В Python 2.X имеется родственная встроенная функция map, объединяющаяэлементы последовательностей похожим образом, но она не усекает результатпо длине кратчайшей последовательности, а дополняет недостающие элементызначениями None, если аргументы имеют разную длину:>>> S1 = ‘abc’>>> S2 = ‘xyz123’>>> map(None, S1, S2)# Только в 2.X[(‘a’, ‘x’), (‘b’, ‘y’), (‘c’, ‘z’), (None, ‘1’), (None, ‘2’), (None,’3’)]В этом примере используется вырожденная форма обращения к встроеннойфункции map, которая больше не поддерживается в Python 3.0.
Обычно онапринимает функцию и одну или более последовательностей и собирает результаты вызова функции с соответствующими элементами, извлеченными из последовательностей. Подробнее функция map будет рассматриваться в главах 19и 20. Ниже приводится короткий пример, где встроенная функция ord применяется к каждому символу в строке и собирает результаты в список (подобнофункции zip, в версии 3.0 map возвращает генератор, и поэтому, чтобы получитьПриемы программирования циклов413все ее результаты в интерактивном сеансе, обращение к ней следует заключитьв вызов функции list):>>> list(map(ord, ‘spam’))[115, 112, 97, 109]Тот же результат можно получить с помощью следующего цикла, но реализация на основе функции map зачастую выполняется быстрее:>>> res = []>>> for c in ‘spam’: res.append(ord(c))>>> res[115, 112, 97, 109]Примечание, касающееся различий между версиями: Вырожденная форма вызова функции map, когда в первом аргументевместо функции передается объект None, больше не поддерживается в Python 3.0, потому что в этом случае она в значительнойстепени совпадает с функцией zip (и, честно говоря, нескольконе соответствует основному назначению функции map).
В версии 3.0 вы можете либо использовать функцию zip, либо написать цикл, который сам дополняет недостающие результаты.Как это сделать, будет показано в главе 20, после того как мы познакомимся с дополнительными концепциями итераций.Конструирование словаря с помощью функции zipВ главе 8 я говорил, что функцию zip, используемую здесь, удобно применятьдля создания словарей, когда ключи и значения вычисляются во время выполнения программы. Теперь, когда мы поближе познакомились с этой функцией, я объясню, какое отношение она имеет к конструированию словарей. Каквы уже знаете, словарь всегда можно создать с помощью литерала словаря илиприсваивая значения ключам:>>> D1 = {‘spam’:1, ‘eggs’:3, ‘toast’:5}>>> D1{‘toast’: 5, ‘eggs’: 3, ‘spam’: 1}>>>>>>>>>>>>D1 = {}D1[‘spam’] = 1D1[‘eggs’] = 3D1[‘toast’] = 5Но как быть, если программа получает ключи и значения для словаря в видесписков во время выполнения, уже после того, как сценарий был написан? Например, предположим, что имеются следующие списки ключей и значений:>>> keys = [‘spam’, ‘eggs’, ‘toast’]>>> vals = [1, 3, 5]Один из способов превратить их в словарь состоит в том, чтобы передать спискифункции zip и затем выполнить обход полученного результата в цикле for:>>> list(zip(keys, vals))[(‘spam’, 1), (‘eggs’, 3), (‘toast’, 5)]414Глава 13.
Циклы while и for>>> D2 = {}>>> for (k, v) in zip(keys, vals): D2[k] = v...>>> D2{‘toast’: 5, ‘eggs’: 3, ‘spam’: 1}Однако, начиная с версии Python 2.2, можно обойтись без цикла for и простопередать результат вызова функции zip встроенному конструктору dict:>>> keys = [‘spam’, ‘eggs’, ‘toast’]>>> vals = [1, 3, 5]>>> D3 = dict(zip(keys, vals))>>> D3{‘toast’: 5, ‘eggs’: 3, ‘spam’: 1}Встроенное имя dict в языке Python в действительности является именемтипа (больше об именах типов и о создании подтипов вы узнаете в главе 31).Этот вызов производит преобразование списка в словарь, но в действительности это вызов конструктора объекта. В следующей главе мы рассмотрим родственное, но более широкое понятие генераторов списков, которые позволяютсоздавать списки с помощью единственного выражения.
Мы также вернемсяеще раз к генераторам словарей, появившихся в версии 3.0, которые являютсяальтернативой вызову dict для пар ключ/значение, объединенных в последовательность.Генерирование индексов и элементов: enumerateРанее мы рассматривали использование функции range для генерации индексов (смещений) элементов в строке вместо получения самих элементов с этимииндексами. Однако в некоторых программах необходимо получить и то, и другое: и элемент, и его индекс.
При традиционном подходе можно было бы использовать простой цикл for, в котором вести счетчик текущего индекса:>>> S = ‘spam’>>> offset = 0>>> for item in S:...print(item,...offset += 1...s appears at offsetp appears at offseta appears at offsetm appears at offset‘appears at offset’, offset)0123Этот способ вполне работоспособен, но в последних версиях языка Python те жесамые действия можно выполнить с помощью встроенной функции с именемenumerate:>>> S = ‘spam’>>> for (offset, item) in enumerate(S):...print(item, ‘appears at offset’, offset)...s appears at offset 0p appears at offset 1a appears at offset 2m appears at offset 3В заключение415Функция enumerate возвращает объект-генератор – разновидность объекта,который поддерживает протокол итераций, который мы будем рассматриватьв следующей главе, и более подробно будем обсуждать в следующей части книги.
В двух словах: он имеет метод __next__, вызываемый встроенной функциейnext и возвращающий кортеж (index, value) для каждого элемента списка. Мыможем использовать эти кортежи для присваивания в цикле for (точно так же,как и в случае с функцией zip):>>> E = enumerate(S)>>> E<enumerate object at 0x02765AA8>>>> next(E)(0, ‘s’)>>> next(E)(1, ‘p’)>>> next(E)(2, ‘a’)Обычно мы не видим всю эту механику, потому что во всех случаях (включаягенераторы списков – тема главы 14) протокол итераций выполняется автоматически:>>> [c * i for (i, c) in enumerate(S)][‘’, ‘p’, ‘aa’, ‘mmm’]Чтобы окончательно разобраться с такими понятиями итераций, как функцииenumerate, zip и генераторы списков, нам необходимо перейти к следующей главе, где производится разбор этих понятий с более формальной точки зрения.В заключениеВ этой главе мы исследовали инструкции циклов языка Python, а также некоторые концепции, имеющие отношение к циклам.
Мы рассмотрели инструкции циклов while и for во всех подробностях и узнали о связанных с нимиблоках else. Мы также изучили инструкции break и continue, которые могутиспользоваться только внутри циклов, и дополнительно познакомились с некоторыми встроенными инструментами, часто используемыми в цикле for,включая функции range, zip, map и enumerate (хотя понимание их роли, как итераторов в Python 3.0, не может быть полным до прочтения следующей главы).В следующей главе мы продолжим исследование механизмов итераций и рассмотрим генераторы списков и протокол итераций в языке Python�������������������������� – концепций, которые тесно связаны с циклами for.