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

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


Скачать 4.86 Mb.
Название3е издание
АнкорМатематический анализ
Дата04.02.2022
Размер4.86 Mb.
Формат файлаpdf
Имя файлаpython_01.pdf
ТипДокументы
#351981
страница42 из 98
1   ...   38   39   40   41   42   43   44   45   ...   98
343
Понятие «итерируемого объекта» является относительно новым в язы
ке Python. По существу оно является обобщением понятия последова
тельности – объект считается итерируемым либо если он физически является последовательностью, либо если он является объектом, кото
рый воспроизводит по одному результату за раз в контексте инстру
ментов выполнения итераций, таких как цикл for. В некотором смыс
ле в категорию итерируемых объектов входят как физические после
Придется держать в уме: сканирование файлов
Вообще циклы удобно использовать везде, где необходимо повтор
но выполнять некоторые действия или многократно обрабатывать данные. Файлы содержат множество символов и строк, поэтому они могут рассматриваться как один из типичных объектов при
менения циклов. Чтобы просто загрузить содержимое файла в строку одной инструкцией, достаточно вызвать метод read:
file = open('test.txt', 'r')
print file.read()
Но для загрузки файла по частям обычно используется либо цикл while, завершающийся инструкцией break по достижении конца файла, либо цикл for. Чтобы выполнить посимвольное чтение, достаточно любого из следующих фрагментов:
file = open('test.txt')
while True:
char = file.read(1) # Читать по одному символу
if not char: break print char,
for char in open('test.txt').read():
print char
Здесь цикл for выполняет обработку каждого отдельного симво
ла, но загрузка содержимого файла в память производится одно
кратно. Чтение строками или блоками с помощью цикла while можно реализовать следующим образом:
file = open('test.txt')
while True:
line = file.readline() # Читать строку за строкой
if not line: break print line,
file = open('test.txt', 'rb')
while True:
chunk = file.read(10) # Читать блоками по 10 байтов
if not chunk: break print chunk,

344
Глава 13. Циклы while и for довательности, так и последовательности виртуальные, которые вы
числяются по требованию.
Итераторы файлов
Один из самых простых способов понять, что такое итераторы, – это посмотреть, как они работают со встроенными типами, такими как файлы. Напомню, что объекты открытых файлов имеют метод с име
нем readline, который читает по одной строке текста из файла за одно обращение – каждый раз, вызывая метод readline, мы перемещаемся к следуюшей строке. По достижении конца файла возвращается пус
тая строка, что может служить сигналом для выхода из цикла:
>>> f = open('script1.py')
>>> f.readline()
'import sys\n'
>>> f.readline()
'print sys.path\n'
>>> f.readline()
'x = 2\n'
>>> f.readline()
'print 2 ** 33\n'
>>> f.readline()
''
Однако при использовании цикла for реализация построчного чтения выглядит проще и работает быстрее:
for line in open('test.txt').readlines():
print line for line in open('test.txt').xreadlines():
print line for line in open('test.txt'):
print line
Функция readlines загружает файл целиком в список строк, то
гда как функция xreadlines загружает очередную строку по тре
бованию, благодаря чему исключается вероятность переполне
ния памяти при попытке загрузить большой файл. Последний пример основан на использовании файлового итератора, кото
рый является эквивалентом использования функции xreadlines
(итераторы будут рассматриваться в следующем разделе). Имя open во всех примерах выше можно также заменить именем file,
начиная с версии Python 2.2. Более подробную информацию о методах, использованных здесь, вы найдете в руководстве по библиотеке. Как правило, чем больше данных читается на каж
дом шаге, тем быстрее работает ваша программа.

Итераторы: первое знакомство
345
Теперь файлы имеют также метод next, который производит практиче
ски тот же эффект – всякий раз, когда его вызывают, он возвращает следующую строку. Единственное значимое различие состоит в том,
что по достижении конца файла метод next возбуждает встроенное ис
ключение StopIteration вместо того, чтобы возвращать пустую строку:
>>> f = open('script1.py')
>>> f.next()
'import sys\n'
>>> f.next()
'print sys.path\n'
>>> f.next()
'x = 2\n'
>>> f.next()
'print 2 ** 33\n'
>>> f.next()
Traceback (most recent call last):
File "
", line 1, in
f.next()
StopIteration
Такое поведение в точности соответствует тому, что мы в языке Python называем итерационным протоколом, – объект реализует метод next,
который возбуждает исключение StopIteration в конце серии результа
тов. Любой такой объект в языке Python считается итерируемым. Лю
бой такой объект доступен для сканирования с помощью цикла for или других итерационных инструментов, потому что все инструменты выполнения итераций вызывают метод next в каждой итерации и опре
деляют момент выхода по исключению StopIteration.
Следствие всего вышесказанного: лучший способ построчного чтения текстового файла, как уже упоминалось в главе 9, состоит не в том,
чтобы прочитать его целиком, а в том, чтобы позволить циклу for авто
матически вызывать метод next для перемещения к следующей строке в каждой итерации. Например, следующий фрагмент читает содержи
мое файла строку за строкой (попутно приводит символы к верхнему регистру и выводит их) без явного обращения к методам файла:
>>> for line in open('script1.py'): # Использовать итератор файла
... print line.upper(),
IMPORT SYS
PRINT SYS.PATH
X = 2
PRINT 2 ** 33
Такой способ построчного чтения текстовых файлов считается луч
шим по трем причинам: программный код выглядит проще, он выпол
няется быстрее и более экономно использует память. Более старый способ достижения того же эффекта с помощью цикла for состоит в том, чтобы вызвать метод readlines для загрузки содержимого файла в память в виде списка строк:

346
Глава 13. Циклы while и for
>>> for line in open('script1.py').readlines():
... print line.upper(),
IMPORT SYS
PRINT SYS.PATH
X = 2
PRINT 2 ** 33
Способ, основанный на использовании метода readlines, попрежнему может использоваться, но на сегодня он проигрывает изза подхода к использованию памяти. Изза того что в этом случае файл загружа
ется целиком, этот способ не позволит работать с файлами, слишком большими, чтобы поместиться в память компьютера. При этом вер
сия, основанная на применении итераторов, не подвержена таким про
блемам с памятью, так как содержимое файла считывается по одной строке за раз. Более того, итераторы были существенно оптимизирова
ны, поэтому способ на базе итераторов должен иметь более высокую производительность.
Как упоминалось во врезке «Придется держать в уме: сканирование файлов», существует возможность построчного чтения файлов с помо
щью цикла while:
>>> f = open('script1.py')
>>> while True:
... line = f.readline()
... if not line: break
... print line.upper(),
...вывод тот же самый...
Однако такой вариант наверняка будет работать медленнее версии, ос
нованной на использовании итератора в цикле for, потому что итера
торы внутри интерпретатора выполняются со скоростью, присущей программам, написанным на языке C, тогда как версия на базе цикла while работает со скоростью интерпретации байткода виртуальной ма
шиной Python. Всякий раз, когда код на языке Python подменяется кодом на языке C, скорость его выполнения увеличивается.
Другие итераторы встроенных типов
С технической точки зрения итерационный протокол имеет еще одну сторону. В самом начале цикл for получает итератор из итерируемого объекта, передавая его встроенной функции iter, которая возвращает объект, имеющий требуемый метод next. Это станет более очевидным,
если посмотреть на то, как внутренние механизмы циклов for обраба
тывают такие встроенные типы последовательностей, как списки:
>>> L = [1, 2, 3]
>>> I = iter(L) # Получить объектитератор
>>> I.next() # Вызвать next, чтобы перейти к следующему элементу

Итераторы: первое знакомство
347
1
>>> I.next()
2
>>> I.next()
3
>>> I.next()
Traceback (most recent call last):
File "
", line 1, in
I.next()
StopIteration
Кроме файлов и фактических последовательностей, таких как списки,
удобные итераторы также имеют и другие типы. Классический способ выполнить обход всех ключей словаря, например, состоит в том, что
бы явно запросить список ключей:
>>> D = {'a':1, 'b':2, 'c':3}
>>> for key in D.keys():
... print key, D[key]
a 1
c 3
b 2
В последних версиях Python вообще не обязательно использовать ме
тод keys – словари имеют итератор, который автоматически возвраща
ет по одному ключу за раз в контексте итераций, поэтому больше не требуется создавать в памяти сразу полный список ключей. В резуль
тате применения этого итератора скорость выполнения возрастает, па
мять используется экономнее, а программный код выглядит проще:
>>> for key in D:
... print key, D[key]
a 1
c 3
b 2
Другие контексты итераций
До настоящего момента я демонстрировал итераторы в контексте ин
струкции цикла for, которая является одной из основных обсуждае
мых тем этой главы. Однако, имейте в виду, что каждый инструмент,
который выполняет обход объектов слева направо, использует итера
ционный протокол. В число этих инструментов входят и циклы for,
как уже было показано выше:
>>> for line in open('script1.py'): # Использовать итератор файла
... print line.upper(),
IMPORT SYS
PRINT SYS.PATH

348
Глава 13. Циклы while и for
X = 2
PRINT 2 ** 33
Генераторы списков, оператор in, встроенная функция map и другие встроенные средства, такие как функции sorted и sum, также основаны на применении итерационного протокола:
>>> uppers = [line.upper() for line in open('script1.py')]
>>> uppers
['IMPORT SYS\n', 'PRINT SYS.PATH\n', 'X = 2\n', 'PRINT 2 ** 33\n']
>>> map(str.upper, open('script1.py'))
['IMPORT SYS\n', 'PRINT SYS.PATH\n', 'X = 2\n', 'PRINT 2 ** 33\n']
>>> 'y = 2\n' in open('script1.py')
False
>>> 'x = 2\n' in open('script1.py')
True
>>> sorted(open('script1.py'))
['import sys\n', 'print 2 ** 33\n', 'print sys.path\n', 'x = 2\n']
Используемая здесь функция map, которую мы будем рассматривать в следующей части книги, представляет собой инструмент, вызываю
щий заданную функцию для каждого элемента итерируемого объекта,
напоминая тем самым генераторы списков, хотя и с ограниченными возможностями, потому что ей можно передать только функцию и нель
зя указать произвольное выражение. Так как генераторы списков свя
заны с циклами for, мы займемся их исследованием ниже в этой главе,
а затем еще раз вернемся к ним в следующей части книги.
Функцию sorted, задействованную здесь, мы уже видели в работе в гла
ве 4. Функция sorted – это относительно новая встроенная функция,
которая использует итерационный протокол, – она напоминает метод списка sort, но в качестве результата возвращает новый отсортирован
ный список и способна работать с любым итерируемым объектом. Су
ществуют и другие, более новые встроенные функции, поддерживаю
щие итерационный протокол. Например, функция sum вычисляет сум
му всех чисел в любом итерируемом объекте, а встроенные функции any и all возвращают True, если любой (any) или все (all) элементы ите
рируемого объекта являются истинными значениями, соответственно:
>>> sorted([3, 2, 4, 1, 5, 0]) # Другие контексты итераций
[0, 1, 2, 3, 4, 5]
>>> sum([3, 2, 4, 1, 5, 0])
15
>>> any(['spam', '', 'ni'])
True
>>> all(['spam', '', 'ni'])
False
Интересно, что область влияния итерационного протокола в языке Py
thon в настоящее время гораздо шире, чем было продемонстрировано

Приемы программирования циклов
349
в примерах – любые встроенные инструменты в языке Python, которые выполняют обход объектов слева направо, по определению используют итерационный протокол при работе с объектами. Сюда относятся даже такие замысловатые инструменты, как встроенные функции list и tuple
(которые создают новые объекты из итерируемых объектов), строковый метод join (который вставляет подстроку между строками, содержащи
мися в итерируемом объекте) и даже операция присваивания последова
тельностей. Благодаря этому все они могут применяться к открытому файлу и автоматически выполнять чтение по одной строке за раз:
>>> list(open('script1.py'))
['import sys\n', 'print sys.path\n', 'x = 2\n', 'print 2 ** 33\n']
>>> tuple(open('script1.py'))
('import sys\n', 'print sys.path\n', 'x = 2\n', 'print 2 ** 33\n')
>>> '&&'.join(open('script1.py'))
'import sys\n&&print sys.path\n&&x = 2\n&&print 2 ** 33\n'
>>> a, b, c, d = open('script1.py')
>>> a, d
('import sys\n', 'print 2 ** 33\n')
Итераторы, определяемые пользователем
Итераторы мы еще будем рассматривать в главе 17, вместе с функция
ми, и в главе 24, когда будем изучать классы. Как будет показано позд
нее, существует возможность превратить пользовательскую функцию в итерируемый объект с помощью инструкции yield. Генераторы спи
сков также поддерживают протокол с помощью выраженийгенерато
ров, а пользовательские классы можно сделать итерируемыми за счет методов перегрузки операторов __iter__ и __getitem__. Итераторы,
определяемые пользователем, позволяют использовать любые объек
ты и операции в любом из итерационных контекстов, с которыми мы встретились здесь.
Приемы программирования циклов
Цикл for относится к категории счетных циклов. Обычно он выглядит проще и работает быстрее, чем цикл while, поэтому его нужно рассмат
ривать в самую первую очередь, когда возникает необходимость выпол
нить обход последовательности. Однако существуют такие ситуации,
когда необходимо выполнять обход какимто особенным способом. На
пример, как быть, когда необходимо выполнить обход каждого второго или каждого третьего элемента в списке, или попутно выполнить изме
нения в списке? Или когда необходимо реализовать параллельный об
ход более чем одной последовательности в одном и том же цикле for?
Такие уникальные ситуации всегда можно запрограммировать с помо
щью цикла while и извлечения элементов вручную, но Python предос

350
Глава 13. Циклы while и for тавляет две встроенные возможности, позволяющие управлять обхо
дом элементов в цикле for:

Встроенная функция range возвращает список последовательно уве
личивающихся целых чисел, которые можно использовать в каче
стве индексов внутри цикла for.
1

Встроенная функция zip возвращает список кортежей, составлен
ных из элементов входных списков с одинаковыми индексами, ко
торый может использоваться для одновременного обхода несколь
ких последовательностей в цикле for.
Обычно циклы for выполняются быстрее, чем аналогичные им счет
ные циклы на базе инструкции while, поэтому везде, где только воз
можно, лучше пользоваться такими инструментами, которые позво
лят использовать цикл for. Рассмотрим каждый из этих встроенных инструментов по очереди.
Счетные циклы: while и range
Функция range является понастоящему универсальным инструмен
том, который может использоваться в самых разных ситуациях. Чаще всего она используется для генерации индексов в цикле for, но вы мо
жете использовать ее везде, где необходимы списки целых чисел:
>>> range(5), range(2, 5), range(0, 10, 2)
([0, 1, 2, 3, 4], [2, 3, 4], [0, 2, 4, 6, 8])
Функция range с одним аргументом генерирует список целых чисел в диапазоне от нуля до указанного в аргументе значения, не включая его. Если функции передать два аргумента, первый будет рассматри
ваться как нижняя граница диапазона. Необязательный третий аргу
мент определяет шаг – в этом случае интерпретатор будет добавлять величину шага при вычислении каждого последующего значения (по умолчанию шаг равен 1). Существует возможность воспроизводить по
следовательности чисел в диапазоне отрицательных значений и в по
рядке убывания:
>>> range( 5, 5)
[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]
1
Современные версии Python предоставляют также встроенную функцию xrange
, которая генерирует индексы по одному вместо того, чтобы создавать их сразу все и сохранять в списке, как это делает функция range. Функция xrange не дает никакого преимущества в скорости, но она удобна в смысле экономии памяти, если приходится генерировать огромное число значе
ний. На момент написания этих строк ходят слухи, что в версии Python 3.0
функция xrange исчезнет, а функция range будет преобразована в объектге
нератор, поддерживающий итерационный протокол для воспроизведения одного элемента за раз. Дополнительную информацию вы найдете в приме
чаниях к выпуску Python 3.0.

Приемы программирования циклов
351
>>> range(5, 5, 1)
[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]
Такое использование функции range само по себе может быть полезным,
однако чаще всего она используется в циклах for. Прежде всего она обеспечивает простой способ повторить действие определенное число раз. Например, чтобы вывести три строки, можно использовать функ
цию range для создания соответствующего количества целых чисел:
>>> for i in range(3):
... print i, 'Pythons'
0 Pythons
1 Pythons
2 Pythons
Функция range также часто используется для косвенного обхода после
довательностей. Самый простой и самый быстрый способ выполнить обход последовательности заключается в использовании цикла for,
когда основную работу выполняет интерпретатор :
>>> X = 'spam'
>>> for item in X: print item, # Простейший цикл
s p a m
При таком использовании все задачи, касающиеся выполнения итера
ций, решаются внутренними механизмами цикла for. Если вам дейст
вительно необходимо явно управлять логикой доступа к элементам,
вы можете использовать цикл while:
>>>
1   ...   38   39   40   41   42   43   44   45   ...   98


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