Лутц М. - Изучаем Python (1077325), страница 100
Текст из файла (страница 100)
Кроме того, существует возможность реализовать произвольные объекты-генераторы с помощью классов, которые соответствуют протоколу итераторов и поэтому могут использоваться в циклах Гог и в других итерационных контекстах. Такие классы определяют специальный метод 1(ег, возвращающий объект-итератор (что более предпочтительно, чем использование метода 0е1зсеэ, обеспечивающего доступ к элементам по индексу).
Однако эта тема далеко выходит за рамки данной главы — обращайтесь к шестой части книги, где приводится информация о классах, и к главе 24 в частности, где приводятся примеры классов, реализующих протокол итераторов. бще раз об итераторах: генераторы 466 Выражения-генераторы: итераторы и генераторы списков В последних версиях Ру()топ понятия итератора и генератора списков были объединены в новую языковую конструкцию — выражения.
генералторьь Синтаксически выражения напоминают обычные генераторы списков, но они заключаются не в квадратные, а в круглые скобки: »> [х * ° 2 тот х 1п гаазе(4)) е Генератор списков, создает список [О. 1. 4, 9) »> (х ° * 2 тот х 1п гвпзе(4)) е Ввраиенив-генератор, создает Е итврируеиий объект <Оепегатог оь)ест ат 0<01100848> Однако с функциональной точки зрения выражения-генераторы кардинально отличаются от генераторов списков — вместо того, чтобы создавать в памяти список с результатами, они возвращают объект-генератор, который в свою очередь поддерживает итерационный протокол, поставляя по одному элементу списка за раз в любом итерационном контексте: »> О = (х ° . 2 тот х 1п гвпве(4)) »> О.пехт() 0 »> О.пехт() 1 »> О.пехт() 4 »> О.пехт() 9 »> О.пехт() ТгасеЬаск (воет гесепт са11 1авт): е!1е "<рувье!14410>", 1!пе 1, [п <вооо1е> С.пехт() Зтор1тегаыоп Как правило, нам не приходится наблюдать итерационную механику действий выражений-генераторов в виде вызовов метода пехт, как в данном примере, потому что циклы тот вызывают его автоматически: »> тот пов 1п (х * ° 2 тот х 1п гапов(4)): рг1пт 'Хв, вв' 1 (пов, пов / 2.0) О, 0.0 т, О.
5 4, 2.0 9, 4.5 Фактически именно таким образом работает любой итерационный контекст, включая встроенные функции вьа, аар и вогтеб, и другие итерационные инструменты, которые мы рассматривали в главе 13, такие как встроенные функции а11, апу и 1!81. 466 Глава 17. Расширенные возможности функций Обратите внимание, что круглые скобки вокруг выражения-генератора можно опустить, если оно является единственным элементом, заключенным в другие круглые скобки, например в вызове функции.
Однако круглые скобки необходимы во втором вызове функции зогтеб: »> збв(х ° ° 2 Гог х 1п гапбе(4)) 14 »> зогтеб(х ° ° 2 Гог х 1п гаере(4)) [0,1,4,9] »> зогтеб((х ° ° 2 Гог х 1п гапре(4)), гечегзе=тгие) [9,4,1,0] »> 1врогт васа »> вар(вата.загц (х * 2 Гог х 1п гапре(4))) [0.0, 1.0, 2.0, 3.0] Выражения-генераторы в первую очередь оптимизируют использование памяти — они не требуют создания в памяти полного списка с результатами, как это делают генераторы списков в квадратных скобках.
Кроме того, на практике они могут работать несколько медленнее, поэтому их лучше использовать, только когда объем результатов очень велик, — и мы естественным образом переходим к следующему разделу. Хронометраж итерационных альтернатив В этой книге нам встретилось несколько итерационных альтернатив. Чтобы подвести итог, коротко проанализируем ситуацию, соединив все, что мы узнали об итерациях и функциях. Я уже упоминал, что генераторы списков обладают более высокой скоростью выполнения, чем циклы 10 г, а скорость работы функции вар может быть выше или ниже в зависимости от конкретной решаемой задачи.
Выражения-генераторы, рассматривавшиеся в предыдущем разделе, обычно немного медленнее, чем генераторы списков, но при этом они минимизируют требования к объему используемой памяти. Все это справедливо на сегодняшний день, но относительная производительность может измениться со временем (интерпретатор РуФЬоп постоянно оптимизируется). Если вам захочется проверить это самим, попробуйте запустить следующий сценарий на своем компьютере, со своей версией интерпретатора: з Файл т!вегзесз,ру !прог! 11ве, зуз гера = 1000 з1те = 10000 бег тезтег(гппс, *аг9з): зтагтттве = ттве.т1ве() Гог т тп галсе(гера) 467 Хронометраж итерационных альтернатив гопс(*агцв) е1арвео = ттве.ттае() - втагтттае гетогп е1арвее оег гогбтатеаепт(): гев = [) Гог х 1п гап9е(вазе): гев,аррепе(аов(х)) оег 1(втСоаргеьепв!оп(): гев = [аЬв(х) Гог х (п гапце(в1ге)) Оет варропст1оп(); гев = вар(аЬв, галде(в1хе)) оег цепегасогехргевюоп(), гев = 1твт(аов(х) тог х 1п галде(вые)) рмпт вув.чегв1оп сев(в = (гогбтатеаепт, 11втсоаргепепвтоп, вареопст(оп, депегатогехргевюоп) гог тевтгопс (п тевтв: ргтпт тевтгопс.
паве . 1)овт(20), =>', тевтег(тевтгопс) 19 2006, 09.52:17) [М80 ч,1310 32 Ыт (1пте1)) => б. 10899996758 => 3.51499986649 => 2.73399996758 => 4.11600017548 2.5 (г25:51908, 8Ер гогбтатеаепт 1твтСоаргеоепюоп варропст(оп депегатогЕхргевв(оп Но вот как изменилось положение дел, когда сценарий был изменен так, чтобы он выполнял настоящую операцию, такую как сложение: сет гогбтатеаепт(): гев = [) Этот сценарий тестирует все альтернативные способы создания списков и, как видно из листинга, выполняет по 10 миллионов итераций каждым из способов, т.
е. каждый из тестов создает список из 10 000 элементов 1000 раз. Обратите внимание, как выражение-генератор вызывается через вызов встроенной функции 1181, чтобы вынудить его выдать все значения,— если бы этого не было сделано, мы бы просто создали генератор, который не выполняет никакой работы. Кроме того, заметьте, как программный код в самом конце сценария выполняет обход кортежа из четырех функций и выводит значение атрибута паве для каждой из них: это встроенный атрибут, который возвращает имя функции.
Когда я запустил этот сценарий в среде ПН Е в ч(г[пг[очгв ХР, где установлен РуЫтоп 2.5, я обнаружил следующее: генератор списков оказался почти в два раза быстрее эквивалентной инструкции цикла (ог, функция вар оказалась немного быстрее генератора списков при отображении встроенной функции аЬв (возвращает абсолютное значение): 488 Глава 17. Расширенные возможности функций Гог х 1п галде(вые): гев.аррепс(х в 10) Оет 1!в1Соергеоепв!оп(): гев = [х + 10 Гог х тп галде(юге)] оег аареопстюп(): гев = вар((1ааопа х; х > 10), галде(в!зе)) Оет депегатогЕхргеввтоп(); ГЕВ = 1!а!(Х г 10 ГОГ Х 1П Галов(В>ЗЕ)) Присутствие вызова функции сделало вызов зар таким же медленным, как и цикл (ог, несмотря на то, что инструкция цикла содержит больше программного кода: 2 5 (г25;51906, бер 19 2006, 09:52:17) [МБС ш1310 32 Еят (Хпте1)] Гогбтатеаепт => 5.25699996948 11втсоаргепепвтоп => 2,68400001526 еарропс1топ => 5.96900010109 депегатогЕхргввв1оп => 3.37400007248 Так как внутренние механизмы интерпретатора сильно оптимизированы, анализ производительности, как в данном случае, становится очень непростым делом.
В действительности невозможно заранее утверждать, какой метод лучше — лучшее, что можно сделать, это провести хронометраж своего программного кода, на своем компьютере, со своей версией Руб]топ. В этом случае все, что можно сказать наверняка,— это то, что в данной версии Руб]топ использование пользовательской функции в вызове вар может привести к снижению производительности по крайней мере в 2 раза и что в этом испытании генератор списков оказался самым быстрым.
Однако, как уже говорилось ранее, производительность не должна быть главной целью при создании программ на языке Руб]топ — основное внимание должно уделяться удобочитаемости и простоте программного кода, и только потом код можно будет оптимизировать, если это действительно необходимо. Вполне возможно, что все четыре варианта обладают достаточной скоростью обработки имеющихся наборов данных— в этом случае основной целью должна быть ясность программного кода. Чтобы еще глубже вникнуть в ситуацию, попробуйте изменить количество повторений в начале сценария или рассмотрите возможность использования новейшего модуля 1!зе!1, который автоматизирует хронометраж кода и позволяет избежать проблем, связанных с используемой платформой [на некоторых платформах, к примеру, предпочтительнее использовать функцию 1!зе, ттзе, а не ттзе.
01осм). кроме того, обратите внимание на модуль ргогт1е из стандартной библиотеки, где вы найдете полные исходные тексты инструментов профилиро. вання программного кода. Концепции проектирования функций 469 Концепции проектирования функций Когда начинают использоваться функции, возникает проблема выбора, как лучше связать элементы между собой, например, как разложить задачу на функции (связность), как должны взаимодействовать функции (взаимодействие) и т. д. Вы должны учитывать такие понятия, как слаженность, взаимодействие н размер функций, — часть которых относится к категории структурного анализа и проектирования.
Некоторые понятия, имеющие отношение к взаимодействию функций и модулей, были представлены в предыдущей главе, а здесь мы коротко рассмотрим некоторые основные правила для тех, кто начинает осваивать язык РуФЬоп: ° Взаимодействие: для передачи значений функции используйте аргументы, для возврата результатов — инструкцию гетогя. Всегда следует стремиться сделать функцию максимально независимой от того, что происходит за ее пределами. Аргументы и инструкция гетогп часто являются лучшими способами ограничить внешнее воздействие небольшим числом известных мест в программном коде. ° Взаимодействие: используйте глобальные переменные, только если зто действительно необходимо.