Лутц М. - Изучаем Python (1077325), страница 74
Текст из файла (страница 74)
Ьгеах ргтпт спопх, № Читать блоками ло 70 байтов Понятие «итерируемого объекта» является относительно новым в языке РуЬ)топ. По существу оно является обобщением понятия последовательности — объект считается итерируемым либо если он физически является последовательностью, либо если он является объектом, который воспроизводит по одному результату за раз в контексте инструментов выполнения итераций, таких как цикл тот. В некотором смысле в категорию итерируемых объектов входят как физические после- Глава 13. Циклы уул()е и (ог 344 Однако при использовании цикла !Рг реализация построчного чтения выглядит проще и работает быстрее: Гог 1гре !п орел('гев!.!х!').геаа1греа(); рггп! 1!Ре гсг 1гпе !и ореп('гев!.!х!').хгеа01гпев(): рг!и! 11ре Гог 1!РЕ гр ОРЕР('ГЕв!.Гх!'): рып! 1гпе Функция геаз1шев загружает файл целиком в список строк, тогда как функция хгеао11 лев загружает очередную строку по требованию, благодаря чему исключается вероятность переполнения памяти при попытке загрузить большой файл.
Последний пример основан на использовании файлового итератора, который является эквивалентом использования функции хгеас1гпев (итераторы будут рассматриваться в следующем разделе). Имя орел во всех примерах выше можно также заменить именем (У1е, начиная с версии Ру!Поп 2.2. Более подробную информацию о методах, использованных здесь, вы найдете в руководстве по библиотеке. Как правило, чем больше данных читается на каждом шаге, тем быстрее работает ваша программа. довательности, так и последовательности виртуальные, которые вы- числяются по требованию. Итераторы файлов Один из самых простых способов понять, что такое итераторы, — это посмотреть, как они работают со встроенными типами, такими как файлы.
Напомню, что объекты открытых файлов имеют метод с именем геас1!Ре, который читает по одной строке текста из файла за одно обращение — каждый раз, вызывая метод геас1Уле, мы перемещаемся к следуюшей строке. По достижении конца файла возвращается пустая строка, что может служить сигналом для выхода из цикла: »> Г = орел('всг!р!1.Ру') »> г. геао1!пе() уарргг яуаул »> Г.геа01!Ре() 'рггп! ВУВ Рагв'>Р »> Г,геа01!Ре() 'х = 2УР' »> Г.геа01!Ре() 'ргпп 2 ** 331п' »> Г.геа01!Ре() Итераторьп первое знакомство Теперь файлы имеют также метод пехт, который производит практически тот же эффект — всякий раз, когда его вызывают, он возвращает следующую строку.
Единственное значимое различие состоит в том, что по достижении конца файла метод пехт возбуждает встроенное исключение Бтор!тегат1оп вместо того, чтобы возвращать пустую строку: »> 1 = орвп('всг1ртт РУ') »> 1.пехт() '1врогт вув)п' »> 1.пехт() 'ргтпт вув.рдтптп »> 1.пехт() 'х = 2тп' »> 1,пехт() 'ргшт 2 ° ° 33)п' »> 1.пехт() тгвсеьвск (еовт гесепт св11 1ввт): Р>1е "<рувпеп№330>", 1тпе т, тп <водв1е> г.пехт() Бтор!тегвт1оп Такое поведение в точности соответствует тому, что мы в языке Ру1Лоп называем игперационныл протоколом, — объект реализует метод пехт, который возбуждает исключение Бтор1те тат(оп в конце серии результатов. Любой такой объект в языке Рус)топ считается итерируемым.
Любой такой объект доступен для сканирования с помощью цикла 1ог или других итерационных инструментов, потому что все инструменты выполнения итераций вызывают метод пехт в каждой итерации и определяют момент выхода по исключению Бтор!те тат топ. Следствие всего вышесказанного: лучший способ построчного чтения текстового файла, как уже упоминалось в главе 9, состоит не в том, чтобы прочитать его целиком, а в том, чтобы позволить циклу 1о г автоматически вызывать метод пехт для перемещения к следующей строке в каждой итерации. Например, следующий фрагмент читает содержимое файла строку за строкой (попутно приводит символы к верхнему регистру и выводит их) без явного обращения к методам файла: »> 1ог 11пе 1п ореп('всггртт.ру'): № использовать итератор файла рг1пт 11пвгвррвг(), 1ИРОПТ БУБ РИ1ИТ БУБ.РЯТН Х = 2 РИ1ИТ 2 ** 33 Такой способ построчного чтения текстовых файлов считается лучшим по трем причинам: программный код выглядит проще, он выполняется быстрее и более экономно использует память.
Более старый способ достижения того же эффекта с помощью цикла 1ог состоит в том, чтобы вызвать метод геаз11пев для загрузки содержимого файла в память в виде списка строк: Глаза 13. Циклы ччЫ!е и (ог 346 »> Тот 11пе 1п ореп('всг(р11.
ру'). геас11пев().' ргапт 11пе, оррег(), 1ЯРОЯТ ЗУЗ РЯ1МТ ЗУЗ.РЯТН Х = 2 РЯ1ЯТ 2 ** ЗЗ Способ, основанный на использовании метода геаб1ъпев, по-прежнему может использоваться, но на сегодня он проигрывает из-за подхода к использованию памяти. Из-за того что в этом случае файл загружается целиком, этот способ не позволит работать с файлами, слишком большими, чтобы поместиться в память компьютера. При этом версия, основанная на применении итераторов, не подвержена таким проблемам с памятью, так как содержимое файла считывается по одной строке за раз.
Более того, итераторы были существенно оптимизированы, поэтому способ на базе итераторов должен иметь более высокую производительность. »> Г = орел('всг1р(1.ру ) »> иб[1е Тгое: 11пв = Г. геае11пеп 11 пес 11пв: Ьгеап рг1п1 11пе.оррег(), ..вывод тот ие салий... Однако такой вариант наверняка будет работать медленнее версии, основанной на использовании итератора в цикле Гог,потому что итера- торы внутри интерпретатора выполняются со скоростью, присущей программам, написанным на языке С, тогда как версия на базе цикла ил11е работает со скоростью интерпретации байт-кода виртуальной машиной Ру([Топ. Всякий раз, когда код на языке РуЫъоп подменяется кодом на языке С, скорость его выполнения увеличивается.
Другие итераторы встроенных типов С технической точки зрения итерационный протокол имеет еще одну сторону. В самом начале цикл Гог получает итератор из итерируемого объекта, передавая его встроенной функции !ге г, которая возвращает объект, имеющий требуемый метод пех(. Это станет более очевидным, если посмотреть на то, как внутренние механизмы циклов Тот обрабатывают такие встроенные типы последовательностей, как списки: »>1=[1, 2, 31 »> 1 = 11ег(1) »> 1.пехг() В Получить обьвкт-итврвтор в Вивввть пекг, чтоби перейти к слвдуюввиу влвивиту Как упоминалось во врезке «Придется держать в уме: сканирование файлов«, существует возможность построчного чтения файлов с помо- щью цикла илъ1е: Итерагорьс первое знакомство 1 »> 1.пехс() 2 »> 1.пехс() 3 »> Т.пехс() ТгасеЬасК (аса( гесеп( са11 1аат): е[1е "<руаьепа343>", 1(пе 1, 1п <ессс1е> 1.пехт() Бгор1(егаысп Кроме файлов и фактических последовательностей, таких как списки, удобные итераторы также имеют и другие типы.
Классический способ выполнить обход всех ключей словаря, например, состоит в том, чтобы явно запросить список ключей: »> 0 = ('а':ц 'Ь".г, 'с".3) »> Гсг Кеу 1п Ь.хеуа(): рг1пт Кеу, 0[азу) а) с 3 Ь 2 В последних версиях РуЬ)топ вообще не обязательно использовать метод Кеуз — словари имеют итератор, который автоматически возвращает по одному ключу за раз в контексте итераций, поэтому больше не требуется создавать в памяти сразу полный список ключей. В результате применения этого итератора скорость выполнения возрастает, память используется экономнее, а программный код выглядит проще: »> Гсг Кеу 1п 0: рг1пт КеУ, 0[хеу) а) с 3 Ь 2 Другие контексты итераций До настоящего момента я демонстрировал итераторы в контексте инструкции цикла Тот, которая является одной из основных обсуждаемых тем этой главы.
Однако, имейте в виду, что каждый инструмент, который выполняет обход объектов слева направо, использует итерационный протокол. В число этих инструментов входят и циклы (сг, как уже было показано выше: »> гсг 11пе 1п преп('есг1рь1.ру'): Ф Использовать итератср файла рг1п( 11пе,сррег(), ТМРОНТ БУБ РН1МТ БУБ.РАТН 348 Глава 13.
Циклы ийн!е и [ог Х=2 РН[ИТ 2 ** ЗЗ Генераторы списков, оператор ! и, встроенная функция вар и другие встроенные средства, такие как функции воггео и зцв, также основаны на применении итерационного протокола: »> цррегв = [11пе.цррег() Гог 11пе 1п ореп('зсг1Р11.ру')) »> цррегв ['1МРОНТ ЗУЗХп', 'РН(ИТ ЗУЗ РАТНХп', 'Х = 2>,п', 'РН1ИТ 2 ** ЗЗХп') »> аар(в1г.цррег, орел('всг1Р11.ру')) ['1МРОНТ ЗУЗХп', 'РН1ИТ ЗУЗ.РАТНХп', 'Х = 2Хп', 'РН1ИТ 2 * ЗЗХп') »> 'у = оп' 1п орел('всг1Р11.ру') Ра1ве »> 'к = 21п' 1п ореп('всг1РС1.ру') Тгце »> вогтео(ореп('всг1Р11,ру')) [ >прог( вувХп'.