Стандартная библиотека Python 3: справочник с примерами [2-е издание]
 9785604001388

Table of contents :
Содержание
Предисловие
Введение
Глава 1. Текст
Глава 2. Структуры данных
Глава 3. Алгоритмы
Глава 4. Дата и время
Глава S. Математика
Глава б. Файловая система
Глава 7. Постоянное хранение и обмен данными
Глава 8. Сжатие и архивирование данных
Глава 9. Криптография
Глава 10. Параллельные вычисления: процессы, потоки и сопрограммы
Глава 11. Обмен данными по сети
Глава 12. Интернет
Глава 13. Электронная почта
Глава 14. Строительные блоки приложений
Глава 15. Интернационализация и локализация приложений
Глава 16. Инструменты разработки
Глава 17. Инструменты среды времени выполнения
Глава 18. Инструменты языка
Глава 19. Модули и пакеты
Приложение А. Замечания относительно портирования программ
Приложение Б. Внешние ресурсы, дополняющие стандартную библиотеку
Указатель модулей Python
Предметный указатель

Citation preview

Стандартная библиотека Python 3 Справочник с примерами 2-е издание

Даг Хеллман

Москва



Санкт-Петербург 2019

ББК 32.973.2&018.2.75

Х36

УД К 681.3.07

"Диалектика"

Компьютерное и:щатсльство

По общим вопросам обращайтесь в излателы�тво "Диалектика" по мресу: [email protected], 11ttp://WW\\�(lialektika.coш

Хеллман, Лаг. Х36

Стан,r\артная библиотека

аш".11. - СПб. : ООО тит. англ. : Пер.

с

ISBN 978-5-6040043-8-8

Руtlюв

3:

справочник с примерами, 2-е изд. с.: ил. - П а рал .

"Диалекти1\ { \ { ) 1 ( ? P [ _a- z ] [ _a - z 0 - 9 ] * ) \ ) \ } 1 ( ? P< b r a ced> [ _a - z ] [ _a - z 0 - 9 ] * ) \ } \ ) 1 ( ? P < i nval id> ) =

=

)

1 1 1

40 t { { '

Гмва 1. Текст

= MyTemp l a t e ( ' ' ' {{{ { va r l } ' ) '

p r i n t ( ' МATCHES : ' , t . pa t t e rn . f i n da l l ( t . temp l a te ) ) p r i n t ( ' SUBST I TUTED : ' , t . s a fe_s u b s t i tute ( va r = ' repl aceme nt ' ) )

Как следует из кода примера, шаблоны именованных (named) и заключенных в фигурные скобки (braced) переменных должны предоставляться по отдельно­ сти, даже если они совпадают. Выполнив этот код, вы получите следующие ре­ зультаты. $ pythonЗ s t r i ng_template_news ynt ax . py МАТСНЕS : [ ( ' { { ' , SUBST ITUTED : {{ repla cement

' ',

' ',

' '),

(' ',

' va r ' ,

' '

' ')]

1 . 1 .4. Кла сс Formatter Класс Forma t t e r реализует тот же самый язык спецификаций, что и метод fo rmat ( ) класса s t r. Язык спецификаций охватывает приведение типов, вырав­ нивание текста, ссылки на поля и ач>ибуты, именованные и позиционные аргу­ менты шаблонов, а также зависящие от типа опции форматирования. В большин­ стве случаев метод format ( ) предлагает более удобный интерфейс для доступа к этим средствам, однако класс Formatter позволяет создавать подкласс ы, которые можно адаптировать для внесения в язык новых элементов. 1 . 1 . 5 . Кон станты В модуле s t r ing определен ряд констант, имеющих отношение к таблице ASCII и символьным наборам. Л истинг 1 .7. s trinq cons tants . py import inspect impo r t s t r i n g de f i s_s t r ( va l ue ) : r eturn i s i n s t ance ( va l u e , s t r ) f o r name , v a l u e i n i nspect . ge tmemЬers ( s t r i n g , i s_s t r ) : i f name . s ta r t swith ( ' ' ) : cont i nu e p r i n t ( ' % s=% r \ n ' % ( name , v a l ue ) )

Константы модуля st ring полезны при работе с АSСП-данными , но ввиду все возраегающей популярности рааличных кодировок стандарта Unicode сфера применимости этих констант ограничена.

1..2. textwrap: форм8'Ирование текстовых абаацев

41

$ python 3 s t r i ng_con s t ant s . py a s c i i_ l e t t e r s = ' abcde fghi j k lmnopqr s tuvwxy zABC DEFGHI JKLMNOPQRSTUVW XYZ ' a s c i i_lowe r c a s e= ' abcde fghi j klmn opq r s t uvwxy z ' a s c i i_uppe r c a s e = ' ABC DEFGHI JKLMNOPQRSTUVWXY Z ' digits= ' 01 2 3 4 5 67 8 9 ' hexdi g i t s = ' 0 1 2 3 4 5 6 7 8 9abcde fABC DEF ' octdi g i t s = ' O l 2 3 4 5 6 7 ' pri ntaЫe= ' 0 1 2 3 4 5 6 7 8 9abcde fghi j klmnop q r s t uvwxyzABCDE FGH I JKLМNOPQ RSTUVWXYZ ! " # $ % & \ ' ( ) * + , - . / : ; ? @ [ \ \ ] " { 1 } � \ t \n \ r \ x 0 b \ x 0 c ' - '

pun ctu a t i on= ' ! " # $ % & \ ' ( ) * + , - . / : ; ? @ [ \ \ ] "_ { 1 } � ' ·

wh i t espace= ' \ t \ n \ r \ x O b \ x O c '

Дополнительные ссылки •

Раздел документации стандартной библиотеки, посвященный классу s t r i n g 2 •



String Мethods 3 • Методы объектов s t r, которые заменяют строковые функции, при­

знанные устаревшими. •

РЕР 2924 • Simpler String Substitutions.



Format String Syntax5• Формальное определение языка спецификаций формата, исполь­

зуемого в классе Forma t t e r и методе s t r . format ( ) .

1 . 2 . textwrap: фо р мати р ование текстовых абзацев Модуль textwrap может использоваться для форматирования текста в ситуа­ циях, когда требуется красиво оформленный вывод. Он предоставляет функцио­ палыюсть, аналогичную средствам автоматического выравнивания и распределе­ ния текста в пределах абзаца, предлагаемым многими текстовыми редакторами и издательскими системами.

1 .2 . 1 . Данн ые, используемые в примерах В приведенных в этом разделе примерах используется модуль textwrap_ examp l e . ру, в котором содержится строка sample text. _

2 3 4 5

www . python . o r g / dev/pep s / p ep - 0 2 92 https : / / docs . python . o rg / 3 / l i b r a ry/ s tdt ype s . html # s t r i n g -methods www . python . or g /dev/peps /pep - 0 2 92 https : / /docs . python . org / 3 . 5 / l ib r a r y / s t r i n g . html # fo rmat - s t ri n g - syntax

42

Гмsва

1. Текст

Листинг 1 .8. textwrap example . ру =

1

1



s amp l e t ext The t extwrap modu l e can Ье u s e d to f o rmat t e x t for output i n s i t u a t i ons whe r e pre t t y- p r i n t i n g i s de s i red . I t o f fe r s prog ramma t i c funct i on a l i t y s imi l a r t o the p a r a g r aph wrapping o r f i l l ing features found i n many text edi t o r s . 1 1 1

1 . 2 . 2 . Заполнение абзацев с помощью функции fill Функция fill ( ) подучает текст в качестве входных данных и возвращает текст, отформатированный с учетом ширины заданной области. Листинг 1 .9. textwrap fill . py _

impo r t t extwrap f r om t extwrap_examp l e import s amp l e_t ext p r i n t ( t e xtwrap . f i l l ( s amp l e_text , wi dth=S O ) )

Результат все еще оставляет желать лучшего. Теперь текст выровнен по левому краю, но первая строка осталась выделенной отступом , а пробелы, раснола�·авши· сся до этого в начале каждой последующей строки, перешли в тело абзаца. $ pythonЗ t extwrap_fi l l . py The t e xtwrap modu l e can Ье u s ed to f o rmat t e x t for output i n s i t ua t i on s whe r e p r e t t y­ p r i n t i n g i s de s i red . I t o f fe r s p r o g r amma t i c funct i on a l i t y s imi l a r t o t h e p a r a g r aph wrapp ing or f i l l i n g features found i n ma ny t e x t edi t o r s .

1 . 2 . 3 . Удал е ние существующих отступов В предыдущем примере символы табуляции и дополнительные пробелы смешались с текстом, поэтому такое форматирование нельзя считать приемле­ мым. Удаление общею пробедьного префикса из всех строк с помощью функ· ции dede nt ( ) приводит к лучшему результату и дает возможность использовать строки документирования и многострочный текст непосредственно из кода на Руt l юн , одновременно удаляя форматирование самого кода. Строка в примере содержит искусственно введенное выд еление отступом, чтобы продемонстриро­ вать :�ту особенность. Листинг 1 . 1 0. textwrap dedent . py impo r t t extwrap f r om textwrap_ex ampl e impo rt samp l e _ t ex t dedented_t ext = t extwr ap . dedent ( s amp l e _text ) pr i n t ( ' Dedented : ' ) p r i nt ( dedented_text )

1..2. tutwrap: формаtИрован ие тексmвых абэацев

43

Результат начинает улучшаться. $ python З t ex twr ap_de dent . py Dedented : The textwrap modu l e can Ье u s e d to fo rma t t ext f o r output in s i t u a t ions whe r e p r e t t y-pr i n t i ng i s de s i r e d . It o f fe r s pr og r amma t i c funct iona l i t y s imi l a r to the p a r a g r aph wr app i ng o r f i l l i ng f e a t u r e s found i n many t ext edito r s .

Поскольку отмена отступа ( dedent) противоположна выделению отступом ( ind.ent) , результат представляет собой прямоугольный блок текста, в котором об­ щие для всех строк начальные пробелы удалены. Если исходные строки имеют разные отступы от левого края , то некоторые из пробелов не будут удалены. В этих условиях входной текст вида wLi newone . wwwLinewtwo . wLi newthree .

превратится в следующий: L i newone . wwLi newtwo . Li newthree .

1 .2 .4. Совместн ый эфф е кт функций dedent и fill Текст с предварительно удаленными начальными отступами можно нропу­ стить через функцию fi l l ( ) , несколько изменив ширину области вывода. Листинr 1 . 1 1 . textwrap_fill_width . ру impo rt t ex t wrap f rom t ex t wr ap_ex amp l e impo r t s ampl e_text dedented text = textwrap . dedent ( s amp l e - text ) . s t r i p ( ) f o r widt h i n [ 4 5 , 6 0 ] : print ( ' { } Column s : \n ' . format ( wi dth ) ) print ( textwrap . f i l l ( dedented_tex t , width=width ) ) print ( )

Этот код выведет текст с использованием двух указанных значений ширины. $ python З textwrap_f i l l_wi dth . py

4 5 Col umns : The t extwrap modu l e can Ье u s e d to f o rmat t e x t for output i n s i t uations whe re p r e t t y ­ p r i n t i ng i s de s i red . I t o f fe r s programmat i c funct i o na l i t y s imi l a r t o the p a r ag r aph

44

ГАава 1.. текст

wrapp i п g or f i l l iпg features f ouпd i п mап у t e x t edi t o r s . 6 0 C o l umпs : The t extwrap modu l e сап Ье u s e d to f o rma t text for output i п s i t u a t i o п s whe re pre t t y-pri п t i пg i s de s i re d . I t o f f e r s p r o g r ammat i c fuп c t i o пa l i t y s imi l a r t o t h e paragraph wrapp i пg or f i l l i пg f e a t u r e s fouпd i п mапу text edi t o r s .

1 . 2. 5. Декорирование блоков текста с помощью фун кции inden t Функцию indent ( ) можно использовать для добавления общего префикса в нач але каждой выведенной строки. В следующем примере уже знакомый вам текст форматируется так, как если бы это был текст из сообщения эл е ктронной почты, включаемый в ответное сообщение с использованием символа > в каче­ стве префикса в каждо й строке. Листинг 1 . 1 2. textwrap_indent . py impo r t textwrap f r om textwrap_ex amp l e import s ampl e_text dedeпte d_t ext t extwrap . dedeпt ( s amp l e_tex t ) wr apped t extwrap . f i l l ( dedeпted tex t , width= 5 0 ) wrapped += ' \ п \ п S е сопd parag r aph a f t e r а Ы ап k l i п e . ' fiпal t e x t wr ap . i пdeпt ( wrapped , ' > ' ) =

=

=

priпt ( ' Quote d Ы о с k : \ п ' } priпt ( f i п a l }

'Iекстовый блок разбивается на физические строки по символам перевода строки , в начало каждой строки, содержащей текст, добавляется префикс, после чего все строки объединяются в новую строку, которая возвращается в качестве результата. $ руthо п З t e xtwrap_iпdeпt . py Quo t ed Ы о с k : > The tex twrap modu l e сап Ье u s ed t o f o rmat text > f o r output i п s i tu a t i o п s whe re p re t t y- p r i п t i п g is > de s i r ed . I t o f f e r s programmat i c fuпct i o п a l i t y > s imi l a r t o t h e p a r a g r aph wrappiпg o r f i l l i пg > features fouпd iп mап у t e x t edi t o r s . > S e coпd p a r ag r aph a f t e r а Ы а п k l i пe .

Дополнением стр ок префиксами можно управлять, передав функции inden t ( ) вызываемый объект predica te в качестве аргумента. Этот объект будет вызван поочередно для каждой строки, и префикс получат те строки, для кото­ р ых он вернет истинное значение.

1.2. textwnlp: фор м1mtроеанме текстовых 863ацев

45

Листинг 1 . 1 3. textwrap indent_predi ca te . ру =

import t extwrap f rom t e x twrap_examp l e import s amp l e_text def shou l d_i пdeпt ( l i пe ) : priпt ( ' I пdeпt { ! r } ? ' . forma t ( l i пe ) ) returп l e п ( l i пe . s t r ip ( ) ) % 2 == О deden t ed_text t e xtwrap . dedeпt ( s ampl e_text ) wrapped textwrap . f i l l ( dedeпted_text , width= 5 0 ) f i п a l = tex twrap . i пdent ( wrappe d, ' EVEN ' , predi cat e=shou l d_iпdeпt ) =

=

priпt ( ' \ пQuoted Ы о с k : \ п ' ) priпt ( f i n a l )

В этом примере префикс EVEN добавляется в строки, содержащие четное коли­ чество символов. $ руthопЗ tex twrap_ iпde пt_predi c a t e . py I пdeпt I пdeпt I пdeп t I пdeпt I пdeпt

' The textwrap modul e сап Ье u s e d to format t e x t \ п ' ? ' fo r output iп s i tuatioпs whe re pretty-p r i п t i пg i s \ п ' ? ' de s i red . I t o f fe r s prog rammat i c fuпct i oпal i t y \ п ' ? ' s imi l a r to the parag raph wrapp iпg or f i l l i n g \ п ' ? ' fe a t u r e s f ound i п mапу text e d i t o r s . ' ?

Quoted Ы ос k : EVEN The textwrap modul e сап Ь е u s e d t o forma t text for output i п s i t u a t i oп s where p r e t t y- p r i п t i п g i s EVEN de s i red . I t o f fe r s prog ramma t i c fuп c t i oпa l i t y EVEN s imi l a r to t h e paragr aph wrapp i n g or f i l l i п g EVEN features fouпd i п mап у t e x t edi t o r s .

1 . 2 . 6 . Висяч и е отступы Точно так же, как существует возможность устанавливать ширину вывода, мож­ но управлять и величиной отступа первой строки независимо от последующих строк. Листинг 1 . 1 4. textwrap hanging_indent . py impo r t t ex twr ap f rom t extwrap_examp l e import s amp l e_t ext dede п t e d_t e x t = t extwrap . dedent ( s amp l e_text ) . s t r i p ( ) priпt ( t extwrap . f i l l ( dedeпt ed_text , i п i t i a l _iпde п t = ' ' , subsequeпt_i пdeп t= ' ' * 4 , width= 5 0 , ) )

ГАU8 1. Тексr

48

Это делает возможным создание висячего отступа, характеризующегося тем, что первая строка смещена на меньшее расстояние от левого края , чем остальн ы е строки.

$

python З textwrap_hangi n g_i ndent . py

The textwrap modu l e сап Ье u s e d to f o rma t t e x t for output i n s i tua t i ons whe r e p re t t y- p r i n t i n g is de s i re d . I t o f f e r s p r o g r amma t i c funct i o n a l i t y s imi l a r to t h e paragr aph wrapp i n g o r f i l l i n g f e a t u r e s found i n ma ny t e x t edito r s .

Значения отступ о в могут включать также непробельные символы. Например, висячий отступ можно снабдить префиксом в виде символа * дл я создания маркера.

1 .2 . 7 . Усечение длинного текста При подготовке кратких резюме или анонсов на основе существующего теста вам может пригодиться функция sho r ten ( ) , которая стандартизирует встреча­ ющиеся в тексте пробельные символы - символы табуляции, символы перевода строки и длин ные последовательности пробелов, - заменяя их одиноч ными про­ белами, а затем усекает текст до заданных размеров, не допуская образования не­ полных слов. Листинг 1 . 1 5 . textwrap shorten . ру _

impo r t t extwrap from textwrap_e xamp l e import s amp l e _t e x t dedented_t ext = t extwr ap . dedent ( s amp l e_text ) original textwrap . f i l l ( deden t e d_t ext , wi dth= 5 0 ) =

p r i nt ( ' Or i g i na l : \n ' ) p r i n t ( o r i g i na l ) shor t ened = t extwrap . sho r t e n ( o r i g i na l , 1 0 0 ) short ened_wr apped = textwrap . f i l l ( shortened, wi dth= 5 0 ) p r int ( ' \nSho r t e ne d : \ n ' ) print ( shortened_wr apped )

Если в той части исходного текста, которая удаляется в результате его усече­ ния , содержатся не только пробельные символы, вместо нее можно использовать заполнитель. П редоставляемое п о умолчанию значение заполнителя [ . ] мож­ но изменить, передав его функции sho r ten ( ) в виде аргумента p laceho lder. .

$

python З textwrap_shorten . py

Original : The tex twrap modu l e c a n Ье u s e d to fo rma t text for output i n s i tu a t i o n s whe re p r e t t y -p r i n t i n g i s

.

1.3. re:

реrумр ные выражения

47

des i red . I t o f fe r s p r o g r amma t i c fun c t i o na l i t y s imi l a r t o t he parag r aph wr apping o r f i l l ing features found i n man y t e x t edi t o r s . Shortene d : The textwrap modu l e can Ь е u s ed t o f o rma t text f o r output i n s i tu a t i o n s where pre t t y-pr i n t i n g [ . . ] .

Дополнительные ссылки •

Раздел документации стандартной библиотеки, посвященный модулю textwrap6 .

1 .3 . re: регулярные выражения Рееулярные выражен:u.я ( РВ) представляют собой образцы текста, или шабло­ ны, предназначенные для сопоставления с тексто1�ыми подстроками и описыва­ емые с помощью специального формального синтаксиса. Шаблоны РВ и нтерпре­ тируются как набор инструкций, которые выполняются при предоегавлении им входной строки и создают объект совпадения или измененную версию исходной строки . В профессиональной литературе вместо термина "regular ex p 1·essio11s " " ч асто используют его сокращенные формы "regex или "regexp". Регулярные выражен ия могут включать литерал ы , повторения, составные шаблоны , чередо­ вания элементов (альтернативы ) и другие элементы , подчиняющиеся сложному набору правил сопоставления с целевым текстом. Многие задачи , связанные с разбором текста, проще решить с 1юмощ1.ю регулярных выражений, ч е м созда­ вать специально для этого синтаксический анализатор или нарсер. Как правило, необходимость в использовании регулярных выражений воз­ никает в приложениях, связанных с обработкой больших объемов текстовой информации . Например, они часто используются в качестве шаблонов поиска в программах обработки тексто в , используемых разработчиками, включая тексто­ вые редакторы vi, emacs и современные интегрированн ые средства разработки (IDE) . Кроме того, они являются неотъемлемой частью таких утилит командной оболочки Uнix, как sed, grep и a:wk. Многие языки программирования (Pcrl, RнЬу, Awk и Те!) поддерживают регулярные выражения на уровне синтаксиса языка. В других языках (например, С, С++ и Pytl1011) поддержка регулярных выражений обеспечивается библиотеками расширений. Существуют многочисленные реализации регулярных выражений на основе открытого исходного кода, в каждой из которых исrюльауется общий базоный синтаксис, но с различными расширениями или изменениями , обеснечиваю­ щими дополнительные возможности. Синтаксис, используемый в модуле re в Pytl1011, основан на синтаксисе регулярных выражений, используемом в Perl, но с некоторыми усовершенствованиями, специфичными для Pytl1011.

Примечание Несмотря на то что формальное определение термина "реrулярное выражение" ограни­ чено выражениями, описывающими реrулярные языки, некоторые из расширений, под6

https : / / d o c s . python . o rg / 3 . 5 / l i b r a r y / t extwrap . html

48

Гl\888 1. Тексr

держиваемых модулем re, выходят за рамки описания регулярных языков. В данной книге термин " регулярное выражение" используется в более общем смысле и подразумевает любое выражение, которое может быть вычислено средствами модуля re в Python.

1 . 3 . 1 . Поиск образцов текста Чаще всего регулярные выражения используются для поиска в тексте под­ строк, удовлетворяющих определенным требованиям. Функция search ( ) получа­ ет в качестве аргументов ш аблон и текст, в котором должен осуществляться по­ иск, и возвращает объект Ma tch, если найдено совпадение с шаблоном. Если най­ ти соответствие шаблону пе удается, то функция search ( ) возвращает значение None. Каждый объект Match хранит информацию о природе найденного соответ­ ствия (совпадения) , в том числе исходную входную строку, использованное регу­ лярное выражение и позицию в исходной строке, в которой встретилось вхожде­ ние данного шаблона. Лисrинг 1 .1 6. re_simple шatch . py imp o r t re pa t t e rn ' thi s ' text ' Does t h i s t e x t ma t ch the pa t t e rn ? ' =

=

ma t ch s е

= =

=

re . search ( p a t t e r n , text )

ma tch . s t a r t ( ) ma t ch . e nd ( )

p r i n t { ' Found " { } " \n i n " { } " \ n f r om { } to { } ( " { } " ) ' . f orma t ( ma t ch . r e . pa t t e r n , ma t ch . s t r i n g , s , е , text [ s : e ] ) )

Методы s t a r t ( ) и end ( ) предоставляют индексы в строке, в пределах кото­ рых встретился шаблон. $ pythonЗ r e _s impl e_ma t ch . py Found " t hi s " i n " Does t h i s t ex t ma t ch the pa t t e r n ? " from 5 t o 9 ( " th i s " )

1 . 3 . 2 . Ко м пиляция выражений Несмотря на то что модуль re включает функции уровня модуля, позволяющие работать с регулярными выражениями как с текстовыми строками, гораздо более эффективным способом, часто исполь.")уемым программами , является компиля­ ция регулярных выражений. Функция comp i le ( ) преобразует строку регулярного выражения в объект RegexObj ect.

LЗ. re: реrумрные вырurенмн

49

Л истинг 1 . 1 7 . re simple_compiled . py impor t re * Предваритель ная компиляция шаблонов regexes [ r e . c omp i l e ( p ) for р i n [ ' t h i s ' , ' that ' ] =

text

' Does thi s text mat ch the pa t t e rn ? '

=

p r i nt ( ' Text :

( ! r } \n ' . format ( t ext ) }

for regex in r e g e xe s : p r i n t ( ' S e e k i n g " ( } " - > ' . f ormat ( re ge x . pa t t e rn ) , end= ' ' ) i f regex . s e a rch ( t ext ) : p r i n t ( ' match ! ' ) e l se : p r i n t ( ' n o ma t ch ' )

Функции уровня модуля поддерживают кеш-память для хранения скомпили­ рованных выражений, но ее объем о граничен, в то время как непосредственное использование скомпилированных выражений позволяет избежать накладных расходов, связанных с выполнением операций поиска в кеше. Еще одним преи­ муществом использования предварительно скомпилированных выражений явля­ ется то, что в mat ch ! S e e k i n g " that " -> no mat ch

1 . 3 . 3. Многократны е совп ад е ния В приведенных до сих пор примерах исполыовалась функция search ( ) для поиска одиночных вхождений литеральных строк. Функция f i ndall ( ) возвраща­ ет все неперекрывающиеся подстроки, совпадающие с шаблоном. Л истинг 1 . 1 8 . re findall . py imp o r t re t e xt

=

p a t t e rn

' аЬЬ а а аЬЬЬЬаа а а а ' =

' аЬ '

f o r match in re . f i n da l l ( pa t t e r n , text ) : print ( ' Found ( ! r } ' . f o rma t ( ma t ch ) )

Гl\888

50

1. Текст

Входная строка содержит два экземпляра а Ь . $ pythonЗ re f i ndal l . py _ Found ' аЬ ' Found ' аЬ '

В отличие от функции findall ( ) , возвращающей строки, функция findi ter ( ) возвращает итератор, создающий экземпляры Match. Листинг 1 . 1 9. re findi ter . py import r e text

' аЬЬаа аЬЬЬЬа а а а а '

=

p a t t e r n = ' аЬ ' for ma t ch in re . f i n d i t e r ( pa t t e r n , t e x t ) : s ma t ch . s t a r t ( ) е ma t ch . end ( ) p r i n t ( ' Found { ! r ) a t { : d ) : { : d ) ' . f orma t ( t ext [ s : e ] , s , е ) ) =

=

Этот код находит те же два вхождения подстроки а Ь , и экземпляр Ма tch пре­ доста мяет информацию о том , в каких именно позициях исходной строки они были обнаружены. $ pythonЗ re f i n di t e r . py _ Found ' аЬ ' at 0 : 2 Found ' аЬ ' at 5 : 7

1 . 3 .4. Синтаксис шаблонов регулярных выражен ий Регулярные выражения поддерживают более сложные шаблоны, чем простые литеральные текстовые строки . Шаблоны могут повторяться, привязываться к различным логическим позициям в исходной строке и даже выражаться в ком­ па ктных формах, не требующих наличия в шаблоне каждого литерального сим­ вола искомой подстроки. Все эти возможности основаны на совместном исполь­ зовании литеральных текстовых значений и метасимволов, являющихся частью синтаксиса регулярных выражений в модуле re. Листинг 1 .20. re te s t_Patterns . ру import r e de f t e s t p a t t e rns ( t ext , pat t e rns ) : _ " " " Получив и сходный текст и список шаблонов в кач е с т в е аргуме н то в , выполняет пои ск в с ех вхожде ний каждо го шаблона в тексте и направляет резул ь т а ты в с т андартный поток вывода s t dout . " " "

# Поиск в сех вхожде ний шаблона в тексте и вывод резуль татов for p a t t e r n , de s c i n pat t e rns :

1.3. re: реtуМрные выреженмя

51

p r i nt ( " ' { } ' ( { } ) \ n " . fo rmat ( pa t t e r n , de s c ) ) ' { } ' 11 • format ( t ex t ) ) p r i nt ( for ma t ch in re . f i ndi t e r ( pa t t e r n , t e xt ) : s = mat ch . s t a rt ( ) е = mat c h . end ( ) sub s t r = text [ s : e ] n_backs l ashes = t e xt [ : s ] . count ( ' \ \ ' ) pre f i x = ' . ' * ( s + n_b a c k s l a s he s ) print ( 11 { } ' { } ' " . forma t ( p r e f i x , subs t r ) ) p r i nt ( ) r e t u rn 11

name ' ma i n ' · t e s t_pa t te rns ( ' abba a a bbbbaa a a a ' , [ ( ' аЬ ' , 11 ' а ' fo l l owe d Ьу ' Ь 1 11 ) , ] )

if

==

В последующих примерах функция t e s t_patterns ( ) используется для иссле­ дования того, каким образом изменение шаблона влияет на характер обнаружива· ем ых им совпадений в одном и том же исходном тексте. В выводимых результатах 01'0бражается входной текст и диапазон подстроки из каждой части входного тек­ ста , которая совпадает с шаблоном. $ pyt hon З re_t e s t_p a t t e rns . py ' аЬ '

( ' а ' f o l l owed Ьу ' Ь ' )

' аЬЬаа аЬЬЬЬааааа ' ' аЬ ' . . . . . ' аЬ '

1 .3 .4. 1 . Повторение

Существует пять способов указания повторения шаблона. Шаблон, за которым следует метасимвол * , повторяется нуль или более раз (фраза "повторяется нуль раз" означает, что шаблон может вообще отсугствовать в совпадающей подстро­ ке) . Замена метасимвола * метасимволом + приводит к тому, что шаблон должен встретиться по крайне й мере один раз. Использование метасимвола ? означает, что шаблон должен встретиться нуль или один раз. Если требуется указать опре­ деленное количество повторений шаблона, используйте фигурные скобки: { т } , где т - количество повторений шаблона. 1 Iаконец, если требуется указать допу­ стим ы й диапазон количества повторений, используйте запись вида { т, п } , где т минимальное требуемое количество повторений, а п - максимально допустимое количество повторений. При опущенном п аапись { m, } означает, что значение должно встретиться по крайней мере т раз подряд, тогда как максимальное коли­ чество повторений ничем не ограничено. Листинг 1 .2 1 . re_repe ti tion . ру f rom re_t e s t_pat t e rnз import t e s t _pa t t e rns t e s t_p at t e rns (

ГА888

52

L Тексr

' аЬЬааЬЬЬа ' , ( ( ' аЬ * ' , ' а fol l owed Ьу z e ro o r more Ь ' ) , ( ' аЬ+ ' , ' а f o l l owe d Ь у one o r mo re Ь ' ) , ( ' аЬ ? ' , ' а f o l l owe d Ь у z e ro o r one Ь ' ) , ( ' аЬ ( З ) ' , ' а fo l l owed Ьу three Ь ' ) , ( ' аЬ { 2 , 3 ) ' , ' а f o l lowed Ьу two to three Ь ' ) ] ,

В этом примере для шаблонов аЬ* и аЬ? най де н о большее количество совпаде­ ний , чем для шаблона аЬ+ . $ pythonЗ re repe t i t i o n . py _ ' аЬ* '

( а fol l owed Ьу z e ro or mor e Ь )

' аЬЬааЬЬЬа ' ' аЬЬ ' . . . 'а' . . . . ' аЬЬЬ ' . . . . . . . . 'а' ' аЬ+ '

( а fol l owed Ьу one or mo re Ь )

' аЬЬааЬЬЬа ' ' аЬЬ ' . . . . ' аЬЬЬ ' ' аЬ ? '

( а fol l owe d Ь у z e ro or one Ь )

' аЬЬааЬЬЬа ' ' аЬ ' . . . 'а' . . . . ' аЬ ' . . . . . . . . ,а, ' аЬ ( З ) '

( а f o l l owed Ь у three Ь )

' аЬЬааЬЬЬа ' . . . . ' аЬЬЬ ' ' аЫ 2 , 3 ) '

( а fo l l owed Ьу two to three Ь )

' аЬЬа аЬЬЬа ' ' аЬЬ ' . . . . ' аЬЬЬ '

О бычно, обрабатывая инструкции повторения, модуль re пытается захватить как можно большую часть строки в п роцессе сопоставления ее с шаблоном. Этот так нааывасмый жадиыil поиск может привести к меньшему количеству отдель­ ных совпадений, т.е. совпадения будут в кл ючать большую часть текста, чем пред­ полагалось. Для отмены жадного поведения следует поместить после инструкции повторения с и м вол ? .

1.3. re: реrумрные выраженМR

53

Листинг 1 . 22. re repetition non greedy . py f rom r e _t e s t_patterns imp o r t t e s t pat t e rn s _ t e s t_pa t t er n s ( ' аЬЬааЬЬЬа ' , [ ( ' аЬ * ? ' , ' а ( ' аЬ + ? ' , ' а ( ' аЬ ? ? ' , ' а ( ' аЬ { З } ? ' , ' ( ' аЬ { 2 , 3 } ? ' ,

fo l l owed Ьу ze ro o r mo r e Ь ' ) , fol l owed Ь у one o r mo re Ь ' ) , fo l l owed Ьу z e ro o r one Ь ' ) , а fol lowed Ь у three Ь ' ) , ' а fo l l owed Ьу two to t h r e e Ь ' ) ] ,

В случае шаблонов, разрешающих повторение буквы Ь нуль раз, что эквива­ лентно ее отсутствию, отключение жад ного п оведе н ия озна чает, что найде н ные сов паде н ия вообще не будут включать букву Ь . $ pyt hon З re _ repe t i t i on_non_greedy . py ' аЬ* ? '

( а fo l l owed Ьу z e r o or mo re Ь )

' аЬЬааЬЬЬа ' 'а' . . . ,а, . . . . 'а' . . . . . . . . ,а, ' аЬ+ ? '

( а fo l l owed Ьу one or mor e Ь )

' аЬЬааЬЬЬа ' ' аЬ ' . . . . ' аЬ ' ' аЬ ? ? '

( а fo l l owed Ь у ze r o o r one Ь )

' аЬЬааЬЬЬа ' 'а' . . . 'а' . " . 'а' . . . . . . . . 'а' ' аЬ { З } ? '

( а fo l l owed Ьу three Ь )

' аЬЬа аЬЬЬа ' . . . . ' аЬЬЬ ' ' аЬ { 2 , 3 } ? '

( а fol l owed Ьу two to three Ь )

' аЬЬааЬЬЬа ' ' аЬЬ ' . . . . ' аЬЬ '

rмвs 1. тексr

54

1 .3.4.2. На боры си м волов llaбap символов - это группа символов, любой из которых может считаться со­ впадением в данной позиции шаблона. Например, набор [ аЬ] совпадет с а или Ь. Листинг 1 . 23. re charse t . ру f rom re t e s t_pa t t e rn s impo r t t e s t_pa t t e rns _ t e s t_pa t t e r n s ( ' аЬЬааЬЬЬа ' , [ ( ' [ аЬ ] ' , ' e i the r a o r b ' ) , ( ' а [ аЬ ] + ' , ' а f o l l owed Ь у 1 o r mo re а or Ь ' ) , ( ' а [ а Ь ] + ? ' , ' а fo l l owed Ьу 1 o r rnor e а or Ь , not g reedy ' ) ] ,

Жадная форма выражения ( а [ аЬ ] + ) пытается захватить всю строку, посколь­ ку первой буквой в ней является а, а все последующие символы - это либо буква а, либо Ь. $ pythonЗ re_cha r s e t . py ' [ аЬ ] '

( e ither а o r Ь )

' аЬЬааЬЬЬа ' 'а' . 'Ь' . . 'Ь' . . . 'а' . . . . ' а' . . " . 'Ь' " " " 'Ь' . . . . . . . 'ь1 . . . . . . . . 'а' ' а [ аЬ ) + '

( а fol l owe d Ьу 1 o r mo re а o r Ь )

' аЬЬааЬЬЬ а ' ' аЬЬааЬЬЬа ' ' а [ аЬ ] + ? '

( а fo l l owed Ь у 1 or mo re а o r Ь , not greedy )

' аЬЬааЬЬЬа ' ' аЬ ' . . . ' аа '

Набор символов можно также использовать для исключения некоторых сим­ волов. Символ "крышка", или циркумфлекс ( л ) , означает поиск символов, не вхо­ дящих в набор символов, следующих за циркумфлексом. Лисrинг 1 .24. re charse t exclude . py f rom re _t e s t _pat t e r n s impo rt t e s t_pa t t e rns t e s t _p a t t e r n s (

1.3. re: реrумрные вырuutнин

55

' Th i s i s some text - - with pun c t ua t i on . ' , [ ( ' [ л - . ] + ' , ' s equences w i t hout - , . , o r s p a ce ' ) ] ,

Этот шаблон находит все подстроки, которые не содержат символов - , . или символа пробела. $ pythonЗ r e _ cha r s e t_e x c l ude . py ' (

л_ .

]+'

( s equences wi thout -

.,

or space )

' Th i s i s s ome text -- with punctua t i o n . ' ' Th i s ' . . . . . ' is ' . . . . . . . . ' s ome ' . . . . . . . . . . . . . ' text ' . . . . . . . . . . . . . . . . . . . . . ' wi th ' . . . . . . . . . . . . . . . . . . . . . . . . . . ' punctua t i o n '

По мере разрастания символ1,ного набора ввод каждого символа, который дол­ жен (или не должен) встречаться в совпадении, превращается в трудоемкую зада­ чу. Для определения набора символов, ч исловые коды которых следуют подряд один за другим в пределах некоторого диапазона, можно использовать более ком­ пактную запись, указав начало и конец диапазона. Листинг 1 .25. re charset range s . ру

f rom re_t e s t_pa t t e r n s import t e s t_p a t t e r n s t e s t p a t t e rn s ( _ ' T h i s i s s ome text - - wi th punctua t i on . ' , [ ( [ a - z ] + ' , ' sequences o f l owe r c a s e l e t t e r s ' ) , ( ' [ A- Z ] + ' , ' s equences o f uppe rca s e l e t t e r s ' ) , ( ' [ a - zA- Z ] + ' , ' s equ e n c e s of l ower - or upp e r c a s e l e t t e r s ' ) , ( ' [ A- Z J [ a - z ] + ' , ' one uppe r c a s e fo l l owed Ьу lowe r c a s e ' ) ] , '

Здесь диапазон а - z включает все буквы нижнего регистра из таблицы кодов ASCII, тогда как диапазон A- Z включает аналогичн ые буквы верхнего регистра. Также допускается объединение диапазонов в единый символьный набор. $ python З re cha r s e t range s . p y _ ' [ a- z ] + '

( s e quence s of l owe r c a s e l e t t e r s )

' T h i s i s some text - - with pun c t u a t i o n . ' . ' hi s ' . . . . . ' is ' . . . . . . . . ' s ome ' . . . . . . . . . . . . . ' t ext ' . . . . . . . . . . . . . . . . . . . . . ' wi t h ' . . . . . . . . . . . . . . . . . . . . . . . . . . ' punctua t i o n '

Г/\Вва

' [ A- Z ] + '

1. Текст

( s eque n c e s of uppe r c a s e l e t t e r s )

' Th i s i s s ome t ext -- with punctuat i on . ' 'Т' ' [ a- zA- Z ] + '

( s e quence s of l owe r - or uppe r c a s e l e t t e r s )

' T h i s i s s ome text -- with pun c t ua t i o n . ' ' Th i s ' . . . . . • is • . . . . . . . . • s ome ' . . . . . . . . . . . . . ' text ' . . . . . . . . . . . . . . . . . . . . . ' wi t h ' . . . . . . . . . . . . . . . . . . . . . . . . . . ' pun ctua t i o n ' ' [ A- Z ] [ a- z ] + '

( one upp e r c a s e f o l l owe d Ьу lowe rcas e )

' Th i s i s s ome t e x t - - with pun ctua t i on . ' ' Th i s '

Специал1,ным случаем набора символов является метас имвол "точка" ( . ) , ука­ зывающий на то, что данный шаблон будет совпадать с любым одиночным симво· лом, находящимся в этой позиции. Листинг 1 .26. re_charset_dot . py

f rom re_t e s t_pa t t e rn s impo rt t e s t_pa t t e rns t e s t_pa t t e rn s ( ' аЬЬааЬЬЬа ' , [ ( ' а . ' , ' а f o l l owed Ьу any one cha ra cte r ' ) , ( ' Ь . ' , ' Ь f o l l owed Ь у any one cha r a c t e r ' ) , ( ' а . * Ь ' , ' а f o l l owe d Ьу anythi ng , ending i n Ь ' ) , ( ' а . * ? Ь ' , ' а f o l l owed Ьу anythi n g , ending i n Ь ' ) ] ,

Кроме тех случаев, когда выполняется нежадный поиск, сочетание точки с по­ вторением может приводить к получению длинных совпадений. $ pythonЗ re_ch a r s e t_dot . py 'а. '

( а fo l l owed Ьу any one cha r a ct e r )

' аЬЬааЬЬЬа ' ' аЬ ' . . . ' аа ' 'Ь. '

( Ь fo l l owe d Ьу any one cha r a ct e r )

' аЬЬа аЬЬЬа ' . ' ЬЬ ' . . . . . ' ЬЬ ' . . . . . . . ' Ьа '

1.3. re: реrумрные выраан ин ' а . *Ь '

57

( а f o l l owed Ьу anyth i n g , ending in Ь )

' аЬЬа аЬЬЬа ' ' аЬЬа аЬЬЬ ' ' а . * ?Ь '

( а fo l l owe d Ьу anyt h i n g , endi ng in Ь )

' аЬЬ ааЬЬЬа ' ' аЬ ' . . . ' ааЬ '

1 .3 .4.3. Специальные символы Еще более компактное представление обеспечивают специальные символ ы в виде экранированных посJiедовательностей (Еsсаре·кодов) , позволяющие указы· ват1, предопределенные символьные наборы. Коды специальных символов, рас­ познаваемых модулем r e , приведены в табл. l . l . Таблица 1 . 1 . Специальные символы регулярных выражений

Код

Значение

\d

Цифра

\D

Любой н е цифровой символ

\s

П робельн ый символ (табуля ция, пробел, перевод строки и т.п.)

\S

Любой символ, н е являющийся пробельным

\w

Алфавитно-цифровой символ

\W

Любой символ, н е являющийся алфавитно-цифровым

Примечание

Специальные символы обозначаются префиксом в виде обратной косой черты ( \), поме­ щаемым перед символом. К сожалению, в обычных строках Pythoп обратная косая черта сама нуждается в экранировании. Использование "сырых" строк, создаваемых посред­ ством помещения префикса r перед литеральным значением, устраняет эту проблему и способствует улучшению удобочитаемости кода. Л истинг 1 . 27. re_es cape codes . py

f r om r e_t e s t_p a t t e rns import t e s t_pat terns t e s t_pa t t e rns ( ' А pr ime # 1 e x amp l e ! ' , [ ( r ' \ d+ ' , ' s e quence o f d i g i t s ' ) , ( r ' \ D+ ' , ' s e quence o f non-di g i t s ' ) , ( r ' \ s + ' , ' s equence o f whi t e s p ace ' ) , ( r ' \ S + ' , ' s e quence of non-wh i tespace ' ) , ( r ' \w+ ' , ' a lphanume r i c cha racte r s ' ) , ( r ' \W+ ' , ' no n - a l phanume r i c ' ) ] ,

Гмва 1. Тексr

58

В этих образцах ре гулярных выражений специальные символы сочетаются с повторениями для нахождения подобных им символов во входной строке. $ pythonЗ re_e s cape_code s . py

' \ d+ '

( s equence o f d i g i t s )

' А prime # 1 exampl e ! ' . . . . . . . . . '1'

' \ D+ '

( s equence o f non-di g i t s )

' А pr ime # 1 examp l e ! ' ' А pr ime # ' .

.



.

' \s+ '

.

.

.

.

.

.

' exampl e ! '

( s equence o f wh i t e space )

' А pr ime # 1 examp l e ! ' 1

1

1

'

1

1

' \S+ '

( s equence o f non-wh i t espace )

' А pr ime # 1 exampl e ! ' 'А'

. . ' pr ime ' •















1

Jt l '

. . . . . . . . . . . ' examp l e ! ' ' \w+ '

( a l phanume r i c cha r a ct e r s )

' А prime Jt l exampl e ! ' 'А'

. . ' pr ime '

. . . . . . . . . 11' .

.

.

.

' \W+ '

.

.

.

.

.

.



' examp l e '

( no n - a lphanume r i c )

' А pr ime # 1 e xampl e ! ' 1





1











#1

1

1

1 .

.

.

.

.

.

.

.

.

.

.



.

.

.

.

.

.

'

!

1

Для поиска символов , являющихся составной частью ре гулярного выражения, их следует экранировать в шаблоне поиска. Листинг 1 . 28. re escape es capes . ру _

_

f rom re_t e s t_pa t t e rn s import t e s t_pa t t e rn s t e s t_pa t t e rn s (

U. re:

реrумрные выражения

59

r ' \ d+ \ D+ \ s + ' , [ ( r ' \ \ . \ + ' , ' e s cape code ' ) J ,

В этом шаблоне экранируются символы обратной косой черты и знака "плюс", поскольку оба они являются метасимволами и имеют специальный смысл в регу­ лярных выражениях. $ pythonЗ re_e s c ape_e s cape s . py ' \\ . \+ '

( e s cape code )

' \ d+ \ D+ \ s + ' \ d+ ' . . . . . , \ D+ , . . . . . . . . . . , \s+ , 1

1 .3.4.4. Якорные привязки Помимо описания содержимого шаблонов, сопоставляемых с текстом, можно указывать относительные позиции в исходном тексте, в которых должен встре­ титься шаблон , что достигается за счет использования привязок. Коды привязок, так называемых якорей, приведены в табл. 1 .2. Лисrинг 1 .29. re anchoring . ру f rom re_t e s t_pat t e rns impo rt t e s t_pa t terns t e s t_pat t e rns ( ' Th i s i s s ome text - - with pun ctuation . ' , [ ( r ' л \ w+ ' , ' wo r d at s t a rt o f s t r i ng ' ) , ( r ' \A\w+ ' , ' wo rd a t s t a rt o f s t r i ng ' ) , ( r ' \ w+ \ S * $ ' , ' word near end o f s t rin g ' ) , ( r ' \w+ \ S * \ Z ' , ' wo r d near end o f s t r i ng ' ) , ( r ' \w* t \w* ' , ' word con t a i n ing t ' ) , ( r ' \bt \w+ ' , ' t at s t a rt o f word ' ) , ( r ' \ w+t \ b ' , ' t a t e nd o f wo rd ' ) , ( r ' \ Bt \ B ' , ' t , not s t art o r end o f wo rd ' ) ] ,

Приведенные в ::этом примере шаблоны поиска слов в начале и в конце стро­ ки различаются своей структурой, поскольку за словом, расположенным в конце строки, следует знак препинания, завершающий предложение. Шаблон \ w + $ не дал бы совпадения , поскольку точка ( . ) не относится к алфавитно·цифровым сим­ волам. $ pythonЗ re_ ancho ring . py 1 л \ w+ '

( word at s t a r t o f s t r ing )

' Th i s i s s ome t e x t - - wi t h punctuat i on . ' ' Th i s '

INlвa 1. Текст

60 ' \A \ w+ '

( word at s t a rt o f s t r i n g )

' Th i s i s s ome text - - with punctua t i on . ' ' Th i s ' ' \w+ \ 8 * $ '

( wo rd n e a r end of s t r i ng )

' Th i s i s s ome text - - wi t h punct ua t i on . ' ' punctua t i on . ' ·

·

·

·

·

·

·

·

·

' \ w+ \ S * \ Z '

·

·

·

·

·

·

·

·

·

·

·

.

.

.

.

.

.

( wo rd near end o f s t ring )

' Th i s i s s ome text - - wi th punctua t i o n . ' ' punctua t ion . '

·

·

·

·

·

·

·

·

' \ w * t \w* '

·

·

·

·

·

·

·

·

.

.

·

·

.

.

.

.

.

.

( word cont a i n i ng t )

' Th i s i s s ome text - - with punctu a t i o n . ' . . . . . . . . . . . . . ' t ext ' . . . . . . . . . . . . . . . . . . . . . ' wi t h ' . . . . . . . . . . . . . . . . . . . . . . . . . . ' punctuat i o n ' ' \bt \ w+ '

( t at s t a rt of word ) with punctua t i on . '

' Th i s i s s ome text ' t ext ' .

.

.

.

.

.

.

' \ w+t \b '

.

.



.

.

.

( t at end o f wo rd )

' Th i s i s s ome t e x t -- wi th punctua t i on . ' ' text '

.

.

.





.

' \Bt\B '



.

.

.

.

.

.

( t , not s t art o r end of wo rd )

' Th i s i s s ome text - - with punctua t i on . ' . . . . . . . . . . . . . . . . . . . . . . . 't1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 't1 1t1 •

































































Таблица 1 .2 . Коды якорных привязок в регулярных вы ражениях Код

Значение

Н ачало логической или физ ичес кой строки $

К онец логической или физ ич еской строки



Н ачало логической строки

\Z

К он е ц логической строки



Пустая строка в начал е или в конце слова



Пустая строка, располож е нн ая н е в н ачал е или в конце слова

1.3. re:

реrумрные выражен ..,.

81

1 . 3 . 5. Ограничение зоны поиска В ситуациях, когда заранее известно, что будет осуществляться только поиск подстроки полной исходной строки , на условия сопоставления шаблона с тек­ стом можно наложить дополнительные ограничения , указав модулю re границы диапазона поиска. Например, если шаблон встречается в начале входной строки, то исполь:юванис функции match ( ) вместо функции s e a rch ( ) автоматически привяжет поиск к началу строки без явного включения соответствующего якоря в шаблон поиска. Листинг

1 .30.

re_match . py

import re t ex t = ' Th i s is s ome text - - with punctuation . ' pattern = ' i s ' print ( ' T ext : ' , text ) print ( ' Pa t t e r n : ' , pattern ) m re . ma t ch ( pa t t e r n , t e x t ) print ( ' Ma t ch : ' , m ) s r e . s e a r ch ( pa t t e rn , text ) print ( ' S e a r ch : ' , s ) =

=

Поскольку литеральный текст i s н е встречается в начале входного тсксrа, он не будет найден функцией mat ch ( ) . Однако эта последовательность символов встречается в тексте дважды и потому будет найдена функцией search ( ) . $ pythonЗ r e_match . py Text : Thi s i s some text -- wi th punct u a t ion . Pat t e r n : i s Match : None Search : < s r e . SRE_Match obj ect ; span= ( 2 , 4 ) , mat ch= ' i s ' >

Функция ful lmatch ( ) требует, чтобы вся входная строка совпала с шаблоном. Листинг

1 .31 .

re_fullma tch . ру

import re text = ' Th i s is s ome text - - wi th punctuation . ' pat tern ' is ' =

p r i n t ( ' Text : ' , text ) print ( ' P a t t e r n : ' , pa t t e rn ) m = r e . s e a rch ( pattern , text ) print ( ' Se a r ch : ' , m ) s re . ful l ma t ch ( pa t tern , text ) print ( ' Ful l ma t ch : ' , s ) =

82

В данном случае функция search ( ) подтверждает, что шаблон действительно встречается во входной строке, но вся строка им не исчерпывается, и поэтому функция fu l lmatch ( ) не обнаруживает совпадения. $ pythonЗ re_ful lma t ch . py Text Pa t t e rn Search Fu l l ma t ch

Thi s i s s ome text - - with punctuat i o n . is

None

Метод search ( ) скомпилированного регулярного выражения поддерживает необязательные позиционные параметры s tart и end, которые позволяют огра­ ничить поиск подстрокой полной строки . Листинг 1 .32. re search_suЬs trinq . ру _

impo r t re text ' Th i s is s ome text - - with punctua t i o n . ' p a t t e rn re . compi l e ( r ' \ b\w* i s \ w * \ b ' ) =

=

print ( ' T ext : ' , text ) print ( ) pos О wh i l e T rue : =

match p a t t e r n . s e a rch ( t ext , pos ) i f not ma tch : break s ma t ch . s t a rt ( ) е ma t ch . end ( ) print ( ' { : > 2 d ) : { : >2 d ) " { l " ' . fo rma t ( s , е - 1 , t e xt [ s : e ] ) ) # Переме сти т ь ся в перед по строке text для продолжения поиска pos е =

=

=

=

Этот пример реализует менее эффективную форму itera l l ( ) . Каждый раз, когда обнаруживается совпадение, его конечная позиция используется в качестве начальной для продолжения поиска. $ pyt honЗ re_s e a r ch_s ubs t r i n g . py Text : T h i s i s s ome t e x t - - with punctua t i on . о 5

з 6

"Thi s " " i s ''

1.3. re: реrумрные выражения

83

1 . 3 .6. Отделение совпадений с помощью групп Поиск совпадений с шаблоном служит основой для более мощных ре гуляр­ ных выражений. Добавление групп в шаблон изолирует отдельные составляю­ щие совпавшего текста, открывая доро гу для создания парсеров (программ для разбора текста) . Группы определяются посредством заключения шаблонов в кру­ глые скобки. Л истинг

1 .33.

re groups . ру

f r om re_t e s t_patte rns import t e s t_pa t t e r n s t e s t_pa t t e rn s ( ' аЬЬаааЬЬЬЬаааа а ' , [ ( ' а ( аЬ ) ' , ' а fo l l owed Ьу l i t e ra l аЬ ' ) , ( ' а ( а * Ь * ) ' , ' а fo l l owed Ьу 0 -n а and 0 - n Ь ' ) , ( ' а ( аЬ ) * ' , ' а f o l l owed Ьу 0 -n аЬ ' ) , ( ' а ( аЬ ) + ' , ' а fo l l owed Ьу 1 - n аЬ ' ) ] ,

Любое завершенное ре гулярное выражение может быт1. преобразо вано в груп·

пу и вложено в более крупное выражение. Для повторения группового шаблона

как целого к нему можно применить любой из специф икаторо1� числа повторений. $ pythonЗ re_g roups . py ' а ( аЬ ) '

( а fo l l owed Ьу l i t e r a l аЬ )

' аЬЬа ааЬЬЬЬ а а а а а ' . . . . ' а аЬ ' ' а ( а*Ь* ) '

( а fo l l owe d Ьу 0 - n а and 0 - n Ь )

' аЬЬаа аЬЬЬЬ а а а а а ' ' аЬЬ ' . . . ' а а аЬЬЬЬ ' . . . . . . . . . . ' ааааа ' ' а ( аЬ ) * '

( а f o l l owed Ьу 0 - n аЬ )

' аЬЬаааЬЬЬЬ ааааа ' 'а' . . . 'а' . . . . ' ааЬ ' . . . . . . . . . . 'а' . . . . . . . . . . . 'а' . . . . . . . . . . . . 'а' " . . . . . . . . . . . . 'а' . . . . . . . . . . . . . . ,а, ' а ( аЬ ) + '

( а f o l l owed Ьу 1 - n аЬ )

' аЬЬаааЬЬЬЬ а а а а а ' . . . . ' а аЬ '

ГА8811 1. Текст

84

Чтобы получить доступ ко всем подстрокам, совпавшим с отдельными группа· ми шаблона, используйте метод group s ( ) объекта Match. Листинг 1 .34. r e_groups_ша tch . ру

impo rt re text = ' Thi s is s ome text - - wi th punct uat ion . ' print ( t ext ) print ( ) patte rns = [ ( r ' л ( \w+ ) ' , ' wo rd at s t a rt o f s t r i ng ' ) , ( r ' ( \w+ ) \S * $ ' , ' word at end , with opti onal punctua t ion ' ) , ( r ' ( \bt \w+ ) \W+ ( \w+ ) ' , ' word s t a rt i ng with t , anothe r wo rd ' ) , ( r ' ( \w+t ) \b ' , ' word ending with t ' ) ,

for pat t e rn , regex mat ch = print ( " ' print ( ' print ( ) =

de s c i n pat t e rns : re . comp i l e ( p a t t e rn ) regex . s ea rch ( t ext ) { } ' ( { } ) \ n " . fo rmat ( pattern , de s c ) ) ' , ma tch . groups ( ) )

Вызов Ma tch . groups ( ) возвращает последовательность строк в том порядке, в котором группы встречаюТt·я в выражении , сопоставляемом со строкой. $ pythonЗ re_g roups_match . py This i s s ome text - - wi t h punctuat ion . 1 л ( \w+ ) '

( wo rd at s t a r t o f s t ring )

( ' Th i s ' , ) ' ( \ w+ ) \ S * $ '

( word a t end, with opt i onal punctuat ion )

( ' punct u a t i o n ' , ) ' ( \b t \ w+ ) \W+ ( \w+ ) ' ' wi th ' )

( ' t ext ' , ' ( \ w+t ) \b '

( word s t a rt i ng with t , another wo r d )

( word ending wi t h t )

( ' t ext ' , )

Чтобы получить доступ к подстроке, совпавшей с какой-то одной группой, ис­ пользуйте метод group ( ) . Это может пригодиться, если группирование исполыу­ ется для нахождения отдельных частей строки, но некоторые из частей, совпада­ ющих с группами, не нужны в конечных результатах.

1..3. re: реrумрные выр11Ж1tнМt1 Листинг

1 .35.

85

re_group s indi vidual . ру

import re text

' Th i s i s s ome text -- w i t h punctuat i o n . '

=

p r i n t ( ' I nput text : ' , text ) # Сл о во , начинающе е с я с ' t ' , за которым следует друг ое слов о regex r e . comp i l e ( r ' ( \ bt \w+ ) \ W+ ( \w+ ) ' ) p r i nt ( ' P a t t e r n : ' , regex . pa t t e r n ) =

ma tch regex . s e a rch ( t ext ) p r i n t ( ' En t i re ma t ch : ' , ma tch . g r oup ( O ) ) p r i n t ( ' Word s t a r t ing with " t " : ' , ma t ch . g roup ( l ) ) p r i n t ( ' Wo rd a f t e r " t " word : ' , ma t ch . g roup ( 2 ) ) =

Группа О представляет строку, совпавшую со всем выражением, тогда как под­ группы нумеруются начиная с 1 в порядке появления их открывающих скобок в выражении. $ pythonЗ re_g r oups_i ndi v i dua l . py I nput text : Thi s i s s ome text P at t e rn : ( \bt \ w+ ) \W+ ( \w+ ) Ent i re ma tch : t ext -- with Word s t a rt i n g with " t " : text Word a f t e r " t " word : with

with punctua t i o n .

Руtlюн расширяет базовый синтаксис группирования, разрешая исполь:ювать группъ1. Обращение к группам по именам упрощает внесение измене­ ний в шаблон по прошествии некоторого времени, избавляя от необходимости изменять также код, использующий результаты поиска совпадений. Чтобы при­ своить имя группе, используйте синтаксис ( ? Р < имя>ша блон) . именованные

Листинг 1 .36. re group s named . ру _

impo r t re text

=

' Th i s i s s ome text - - wi t h punctua t i on . '

p r i n t ( text ) pr i n t ( ) pa t t e rns [ r ' л ( ? P< f i rs t_word> \w+ ) ' , r ' ( ? P< l a st_wo rd>\w+ ) \8 * $ ' , r ' ( ? P < t_wo rd > \ b t \ w+ ) \W+ ( ? P< o the r_word> \w+ ) ' , r ' ( ? P \w+t ) \Ь ' , =

f o r p a t t e r n i n patte rns : regex re . compi l e ( patte rn ) =

ГА11118 j,, Текст

88 ma t ch print ( " p r i nt ( ' print ( ' print ( )

=

regex . s e a rch ( t ext ) ' ( ) ' " . format ( pa t t e rn ) ) ' ma tch . g roups ( ) ) ' , ma tch . g roupdi c t ( ) )

Метод g r oupdict ( ) используется для получения слова ря, который сопостав­ ляет имена групп с подстроками совп адений, хранящимися в объекте rnatch. В упорядоченную последовательность, возвращаемую методом g r oup s ( ) включа­ ются также именованные шаблоны. ,

$ pythonЗ re_g roups_named . py This is s ome text -- with punctuat i on . • л ( ? P < f i r s t_word> \w+ ) ' ( ' Th i s ' , ) ( ' f i rs t_wo rd ' : ' Th i s ' ) ' ( ? P < l a s t _word> \w+ ) \ 5 * $ ' ( ' punctua t i on ' , ) ( ' l a s t_word ' : ' punctu a t i on ' ) ' ( ? P \bt\w+ ) \W+ ( ? P \w+ ) ' ( ' t ext ' , ' wi t h ' ) ( ' t_word ' : ' text ' , ' other_word ' : ' wi th ' ) ' ( ? P< ends_wi th_t > \w+ t ) \b ' ( ' t ex t ' , ) ( ' ends _wi th_t ' : ' t ex t ' )

Обновленная версия функции t e s t_pa tte rns ( ) отображающая нумерован­ ные и именованные 1·руппы, совпавшие с шаблоном, упростит понимание резуль­ татов, получаемых в носледующих примерах. ,

Листинг 1 .37.

re te s t_patterns groups . ру

import re de f t e s t_pa t t e rns ( t ext , patte rns ) : " " " Получи в исходный текст и спис ок шаблонов в качестве аргументо в , выполняе т поиск в сех вхожде ний каждого шаблона в текс те и направляет резул ь т а ты в с т андартный поток выв ода

s t dou t . 11 11 11

# Пои с к в сех вх ождений шаблона в т е к с те и вывод резуль татов

for pattern, de s c i n patte rns : print ( ' ( ! r ) ( ( ) ) \ n ' . fo rmat ( pa t t e r n , de s c ) ) p r i nt ( ' { ! r ) ' . format ( t ex t ) ) for match in re . f i n di t e r ( pa t t e rn , tex t ) : s mat ch . s t a r t ( ) е ma tch . end ( ) pre f i x = ' * (s ) print ( =

=

'

1.З. re: реrумрные вwpeiкeНМfll

87

{ } { ! r } { } ' . f o rmat ( pre f i x , text [ s : e ] , ' ' * ( l e n ( text ) end= ' ' , •

-

е) ) ,

print ( match . g r oups ( ) ) i f match . g r oupdi ct ( ) : print ( ' { } { } ' . f ormat ( ' ' * ( l en ( t ext ) - s ) , mat ch . g r oupdict ( ) ) , print ( ) return

Поскольку группа сама по себе является законченным регулярным выражени· ем, возможно вложение одних групп в другие для создания еще более сложных выражений. Листинг 1 . 3 8. r e_groups nes ted . ру

f r om re_t e s t_patterns_g r oups import t e s t_patterns t e s t_pat t erns ( ' аЬЬааЬЬЬа ' ,

[ ( r ' a ( ( a* ) (b* ) ) ' ,

' а fol l owed Ьу 0 - n а and 0 - n Ь ' ) ] ,

В данном случае группа ( а* ) совпадает с пустой строкой, и таким образом зна­ чение, возвращаемое методом groups ( ) , включает эту пустую строку в качестве совпадающего значения. $ pythonЗ re_g r oup s_ne s t ed . py ' а ( ( а * ) ( Ь* ) ) ' ' аЬЬааЬЬЬа ' ' аЬЬ ' ' ааЬЬЬ ' 'а'

( а f o l l owed Ьу 0 - n а and 0 - n Ы

( ' ЬЬ ' , ' ' , ' ЬЬ ' ) ( ' аЬЬЬ ' , ' а ' , ' ЬЬЬ ' )

(

"

,

"

,

"

)

Группы можно также использовать для определения альтернативных шабло­ нов. Символ канала ( 1 ) указывает на возможность выбора одного из нескольких альтернативных вариантов шаблона при поиске совпадений. Однако работа с ка­ налами требует внимательности . В следующем примере первое в ы ражение совпа­ дает с буквой а, за которой идет последовательность, состоящая только из букв а или только из букв Ь. Второй шаблон совпадает с буквой а, за которой идет по­ следователыюсть, включающая буквы а и Ь в любых сочетаниях. Шаблоны очень похожи, но результирующие совпадения разительно отличаются. Листинг 1 .39. re_groups_al terna tive . ру

f r om re_te s t_patte rns_g r oups import t e s t_patterns t e s t_pa t t e r n s (

1Мва 1. Текст

88 ' аЬЬааЬЬЬа ' , [ ( r ' a ( ( а + ) 1 ( Ь + ) ) ' , ' а then s e q . o f а or s e q . o f Ь ' ) , ( r ' a ( ( a [ b ) + } ' , ' а then seq . o f [ аЬ ) ' } ) ,

�ели для какой-либо альтернативной группы не существует соответствия, но 0110 существует 1v1я всего шаблона, то возвращаемое методом groups ( ) значение вклю чает значение None в той точке 11оследоватсльности, в которой должна была бы 1юявит1,ся данная а.1ш1·ер11атив11ая групна. $ pythonЗ re g roups_a l t erna t i ve . py _ ' a ( ( a + ) J ( b+ ) ) ' ' аЬЬааЬЬЬа ' ' аЬЬ ' ' аа ' ' а ( (а J Ь) +) '

( а then s e q . of а or s eq . o f Ь )

( ' ЬЬ ' , None , ' ЬЬ ' ) ( ' а ' , ' а ' ' None )

( а then s e q . o f [ аЬ ) )

' аЬЬааЬЬЬа ' ' аЬЬааЬЬЬа '

( ' ЬЬааЬЬЬа ' ,

'а' )

О11ре1�еле11ие гру1111ы, содержащей нодшаблон , может быть полезным также в тех случаях, ко1·да строка, совпа,r�ающая с подшаблоном , не является частью того содержимо1·0 , которое должно изnлекат1.ся из полного текста. Группы тако1·0 ро1�а называются незахватываюи4им и. 1 Iезахватьшающие группы можно использо­ вать для описания повторяющихся шаблонов или альтернатив без обособления совпадающего фрагмента строки в возвращаемом значении . Незахватывающая группа опис ы вается с помощью синтаксиса ( ? : ша блон) . Листинг 1 .40. re_groups_noncapturing . ру

f r om re_t e s t_patte rn s g roups import t e s t_pa t t e r n s _ t e s t_pa t t e rn s ( ' аЬЬааЬЬЬа ' , [ ( r ' a ( ( а + ) 1 ( Ь + ) ) ' , ' capturing fo rm ' ) , ( r ' a ( ( ? : а + ) 1 ( ? : Ь + } ) ' , ' noncapt u ring ' ) ) ,

Сравните в следующем примере груп пы, которые возвращаются для захваты­ вающей и незахваты вающей форм шаблона, дающих одни и те же результирую­ щие совпадения. $ python З re _g roups_noncapturing . py

' а ( (а+) 1 (Ь+) ) ' ' аЬЬа аЬЬЬа ' ' аЬЬ ' ' аа '

( captu r i ng fo rm )

( ' ЬЬ ' , None , ' ЬЬ ' ) ( ' а ' , ' а ' , None )

' а ( ( ? : а + ) 1 ( ? : Ь+ ) ) '

( noncapt u ring )

1.З. re: реrумрные выраженю1

' аЬЬааЬЬЬа ' ' аЬЬ ' ' аа '

( ' ЬЬ ' , ) ( 'а'' )

1 . 3 . 7 . О пции поиска Правила со11оставления шаблона с текстом, Иугим функция м, 11олучающим шаблон для выполнения поиска.

1 .3. 7 . 1 . П оиск, нечувствительный к реги стру При установленном флаrе I GNORECASE литеральные символы и наборы симво­ лов, указанн ые в шаблоне, сопоставляют("Я с соотвt..'ТСтвующими символами в тек­ сте, игнорируя их регистр. Листинг 1 .41 . re_flags ignorecase . ру _

impo rt re text ' Thi s is s ome text - - wi t h punctua t i on . ' pattern = r ' \ bT \w+ ' re . compi l e ( pa t t e r n ) with_ca s e without case re . compi l e ( pattern , re . I GNORECASE ) =

=

=

print ( ' Text : \ n ( ! r } ' . fo rma t ( t ext ) ) print ( ' Pa t t e rn : \ n { } ' . f o rma t ( pa t t e rn ) ) print ( ' C a s e - s e n s i t i ve : ' ) f o r mat ch in wi th_ca s e . f i nda l l ( t e xt ) : print ( ' { ! r } ' . f o rma t ( ma t ch ) ) print ( ' C a s e - i n s e n s i t ive : ' ) f o r ma tch i n without ca s e . f i nda l l ( text ) : _ print ( ' { ! r ) ' . f o rma t ( ma t ch ) )

Данный шаблон включает литерал Т , и если фла1· I GNORECASE не установлен , то единственн ы м совпадением будет слово Thi s. Если игнорировать регистр, то совпадет также сл ово text. $ pyt honЗ re_f l a gs_i gnore c a s e . py Text : ' Th i s i s s ome text -- with punctua t i on . ' Pattern : \bT\w+ C a s e - s e n s i t ive : ' Th i s ' Case-ins e ns i t ive : ' Th i s ' ' t ext '

ГА8118 1. Текст

70

1 .3.7.2. М ногостро ч н ые входные строки На характер выполнения поиска в случае многострочных входных строк вли­ яют два флага: MULT I L INE и DOTALL. Флаг MULT ILINE управляет обработкой якор­ ных привязок для текста, содержащего символы перевода строки. Если включен многострочный режим, то якорные при вязки л и $ означают начало и конец не только всей логической строки, по и каждой из физических строк. Листинг 1 .42. re flags mul tiline . ру

import re text ' Th i s is s ome t e x t - - with pun ctuat i on . \ nA s e cond l i ne . ' pattern = r ' ( л \w+ ) 1 ( \w+ \ S * $ ) ' s i n g l e_l i n e re . compi l e ( pattern ) mu l t i l i ne re . comp i l e ( pa t tern , r e . MULT I LINE ) =

=

=

print ( ' Te x t : \ n { ! r } ' . f o rmat ( t ext ) ) print ( ' Pat t e rn : \ n { } ' . f o rmat ( pa t t e rn ) ) p r i n t ( ' S i n g l e Line : ' ) for mat ch i n s in g l e_ l i n e . f i nda l l ( text ) : print ( ' { ! r } ' . f ormat ( mat ch ) ) p r i n t ( ' Mu l t l i ne : ) f o r ma t ch in mul t i l ine . f i ndal l ( t ext ) : print ( ' { ! r } ' . f o rmat ( match ) ) '

В этом примере шаблон совпадает с первым или последним словом входной строки. Он совпадает с подстрокой l ine . в конце всей строки даже в отсутствие символа перевода строки. $ pythonЗ re_f l a g s_mul t i l i ne . py Text : ' Thi s i s s ome t e x t -- with pun ctuat i on . \ nA s e cond l i ne . ' Pa t t e r n : ( л \ w+ ) 1 ( \w+ \ S * $ ) S i n g l e Line : ( ' Th i s ' , ' ' ) ( ' ' ' ' l ine . ' ) Mu l t l i ne : ( ' Th i s ' , ' ' ) ( ' ' , ' punctuat i o n . ' ) ( 'А' ,

(' ',

" )

' l ine . ' )

Еще одним флагом, влияющим на обработку многострочного текста, является флаг DOTALL. Обы ч но то ч ке ( . ) соответс твует любой одиночный символ во вход­ ном тексте, кроме символа перевода строки. Данный флаг разрешает совпадение то ч ки и с этим символом. Листинг 1 .43. re flags do tall . py

import re text

=

' Th i s i s s ome t ext - - wi t h pun c tuat i o n . \ nA s e cond l i ne . '

1.3. re:

реrумрные вы ражения

71

pat t e rn r' .+' no_new l i n e s = re . compi l e ( pa t t e r n ) do t a l l re . comp i l e ( pattern , re . DOTALL ) =

=

print ( ' Text : \ n { ! r } ' . fo rma t ( t ex t ) ) print ( ' P at t e rn : \ n { } ' . fo rmat ( p a t t e rn ) ) p r i n t ( ' No newl i n e s : ' ) f o r match in n o_new l i nes . f i ndal l ( t ext ) : print ( ' { ! r } ' . f o rmat ( ma t ch ) ) : ') p r i n t ( ' Do t a l l f o r mat ch i n do t al l . f i nda l l ( t ex t ) : print ( ' { ! r } ' . f o rmat ( ma t ch ) )

Без установки этого флага шаблону соответствовала б ы каждая физическая строка входного текста. Добавление флага приводит к тому, что захватывается вся строка. $ pythonЗ r e_ f l ag s_do t a l l . py Text : ' Th i s i s s ome text -- with punctuat i o n . \ nA s e cond l i ne . ' Pattern : .+ No newl i n e s ' Th i s i s s ome text - - wi th punctuat i o n . ' ' А s e cond l i n e . ' Dot a l l : ' Th i s i s s ome text -- with punctuati on . \ nA s e cond l i ne . '

1 .3.7.3. Unicode В Pythoп 3 объект ы s t r используют полный набор символов Uпicode , и при обработке механизмом регулярных выражений предполагается , что именно этот набор задействован в шаблоне и исходном тексте. Описанные ранее Еsсаре-последовательности по умолчанию также определяются в терминах Uпi­ code. Эти предположения , в частности, означают, что шаблон \w+ совпадет как со словом "Fre11cl1", так и со словом "Fraш;:ais". Чтобы ограничить Еsсаре­ последоватсльности символьным набором ASCII, как это принято по умолчанию в Руt lюп 2, при компиляции шаблона или вызове таких функций уровня модуля, как se arch ( ) и ma tch ( ) , следует использовать флаг ASC I I . Листинг 1 .44. re_flaqs ascii . ру

import re text u ' Fran� a i s l z o t y O s t e r r e i ch ' pattern r ' \ w+ ' a s c i i_pattern re . c ompi l e ( p a t t e rn , re . AS CI I ) unicode_pat t e r n re . comp i l e ( pa t t e rn ) =

=

=

=

print ( ' Text

: ' , text )

ГМ88 1. Текст

72 p r i n t ( ' Pa t t e r n print ( ' ASCI I p r i n t ( ' Uni code

.



.



.



1 1 1

, pattern ) , l i s t ( a s c i i_pa t t ern . f indal l ( t ext ) ) ) , l i s t ( uni code pattern . finda l l ( t ex t ) ) ) _

Другие Еsсаре- п оследовательности ( \W, \Ь, \В, \d, \ D, \ s и \ S ) также обраба­ тываются по-другому в случае АSСП-текста. Вместо того чтобы каждый раз об ра­ щаться к базе даппых Uпicode для выяснения свойств каждого символа, модуль re использует АSСП-определение набора символов, идентифицируемого Еsса ре­ последовательностью. $ pythonЗ re_f l a g s_a s c i i . py Text Pattern AS C I I Uni code

Fran 9 a i s l zo t y б s t e r re i ch \ w+ [ ' Fran ' , ' ai s ' , ' z ' , ' o t y ' , ' s te r r e i ch ' ] [ ' Fran9a i s ' , ' l zo ty ' , ' бs t e r r e i ch ' ]

1 . 3. 7 .4. Синтаксис описатель ных реrулярных выра жени й Компактный формат синтаксиса регулярных выражений может стать 11ре1 1ятствисм 110 мере их усложнения. С ростом количества групп в выражении вам будет требоваться все больше времени для того, чтобы понять роль каждого эле­ мента и разобраться в том, каким образом взаимодействуют различные части вы­ ражения. Ис11ользова11ие именованных rруп11 немного с глаживает подобные нро­ блсмы , но лучшим решением является использование описател·ьиого (Ашогословиого) режима (veгbose ню Name : Fi r s t L a s t Emai l : f i rs t . l a s t @examp l e . com Candidat e : No B r a c ket s f i r s t . l a s t @ex ampl e . com Name : None Ema i l : f i r s t . l a s t @examp l e . com Candida t e : Fi r s t L a s t No ma t ch Candidat e : Fi r s t Midd l e L a s t < f i r s t . l a s t @ examp l e . com> Name : Fi r s t M i dd l e L a s t Emai l : f i r s t . l a s t @exampl e . com Candidat e : Fi r s t М . L a s t < f i r s t . l a s t @ exampl e . com> Name : Fi r s t М . La s t Ema i l : f i rs t . l a s t @exampl e . com Candidat e : < f i r s t . l a s t @examp l e . com> Name : None Ema i l : f i rs t . l a s t @ examp l e . com

Вкл ючение флагов в ша б лоны В тех ситуациях, когда фла�·и не могут б ыть добавлен ы во время компиляции в ы ражения (например, когда шаблон передается в качестве аргумента библиотеч­ ной функции, которая будет компилировать е1·0 позднее) , их можно включить в саму строку в ы ражения. Например, чтоб ы включить режим поиска, нечувстви­ тельно го к регистру, в начало вы ражения следует добавить группу ( ? i ) . 1 . 3 . 7 . 5.

Листинг 1 .48. re flags emЬedded . ру _

_

import re text

=

' Thi s i s s ome text - - with punctua t i on . '

Г118ва 1. Текст

76 p a t t e rn r ' ( ? i ) \bT \w+ ' r e g ex re . compi l e ( p a t t e r n ) =

=

p r i n t ( ' Text p r i n t ( ' P a t te rn p r i n t ( ' Matches



.

1



1



1

.

.

' ' '

text ) pattern ) regex . f inda l l ( t e x t ) )

Поскольку флаги управляют вычислением или разбором всего выражения, они всегда должны указываться в начале выражения. $

pytho n З re_f l a g s_emЬedded . py

Text Pat t e rn Matches

wi t h punc tu a t i o n .

Th i s is some text ( ? i ) \ bT \w+ [ ' Th i s ' , ' t ext ' )

Сокращенные обозначения всех фла�·ов приведены в табл. 1 . 3. Флаги можно объединять пугем помещения их в одну группу. Например, груп­ па ( ? im ) включает нечувствительный к регистру поиск для многострочных строк. Таблица 1 .3 . Сокращенные обозначения флагов регулярных выражений Флаг

Сокращение

ASC I I

а

I GNORECASE

i

MULT I L I N E

т

DOTALL

s

VERBOSE

х

1 . 3 .8. Просмотр впер ед ил и назад Во многих случаях полезно иметь возможность находить соответствия одной части шаблона только при условии , что одновременно удается найти соответ­ ствие другой его части. Например, в выражении для разбора адресов электрон­ ной почты угловые скобки были обозначены как необязательные. В реальных си­ туациях угловые скобки должны встречаться парами , и совпадение должно быть возможным только при наличии или отсутствии одновременно обеих скобок. В модифицированной версии выражения для поиска совпадений с парой угловых скобок используется положите.льпъtй просмотр вперед (другое название положи­ тел ьная опережаю1JJ ал проверка) . Для этого вида просмотра используется синтак­ сис ( ? =ша блон ) . -

Листинг 1 .49. re l ook ahead . ру impo rt re addre s s 1 ' 1

=

re . compi l e (

# Имя состоит из букв и може т в ключать символы точки # в сокраще нных вариантах обращения и инициалах ( ( ? P ( [ \w . , J +\ s + ) * [ \w . , J +

,,

"

1..3. re: реrумрные выражениt1

77

\s+ ) # имя уже не является необязате л ь ным # П РОСМОТ Р В ПЕ РЕД # Адреса электронной почты заключ е ны в угло вые скобки , но # толь ко в том случае , если имеются обе скобки # или ни одной ( ?= ( < . * >$ ) # Остаток з а ключе н в угловые скобки

1

( [ � < ] . * [ � > ] $ ) # Ост аток * не * з а ключен в угло вые скобки

< ? # Н е обязатель ная от крывающа я угл о в а я скобка # Собс т в е нно электронный адре с : u s e rn ame @ doma i п . t l d ( ? P< ema i l > [ \w\ d . +- ] + # Имя пол ь зователя @ ( ( \w\d . ] + \ . ) + # Префикс име ни доме на ( com l o r g l edu ) # Огра нич е ние списка доме нов верхнего уровня

> ? # Н е обязател ь ная за крыв ающа я угло в а я скобка 1 1 1

1

r e . VERBOSE ) candi dates = [ u ' First L a s t < f i r s t . l a s t @examp l e . com> ' , u ' No Brackets f i rs t . l a s t @example . com ' , u ' Open Bracket < f i rs t . l a s t @ ex ampl e . com ' , u ' C l o s e Bracket f i r s t . l a s t @ ex ampl e . com> ' , for candi date in candida t e s : print ( ' Ca ndidate : ' , candida t e ) ma t ch = addre s s . sea rch ( candidate ) i f ma t ch : p r i n t ( ' Name : ' , ma tch . g roupd i ct ( ) [ ' name ' ] ) p r i n t ( ' Ema i l : ' , ma tch . g roupd i ct ( ) [ ' ema i l ' ] ) else : p r i n t ( 1 No ma tch ' )

В эту версию выражения внесено несколько существенных изменений . Во· первых, имя уже не является необязательным. Это не только означает, что один лишь адрес без имени не может образовывать совпадение, но и предотвраща­ ет нахождение некорректно отформатированных комбинаций "имя - адрес". Правило положительного просмотра вперед, вставленное после группы name , позволяет удостовериться в том , что либо оставшаяся част�, строки заключена в углов ы е скобки , либо отсутствуют непарные угловые скобки (т.е. у1'Ловые скобки должны быть либо в виде пары, либо отсутствовать) . Правило просмотра вперед оформлено в виде группы, но совпадение с этой группой не приводит к переме­ щению те кущей позиции вперед во входном тексте, поэтому остальная часть ша­ блона начинает обработку текста с той же позиции.

1"А888 1. Тексr

78

$ pythonЗ re_l o o k_ahe ad . py Candidat e : Fi r s t L a s t < f i r s t . l a s t @ e xampl e . com> Name : Fi r s t L a s t Ema i l : f i r s t . l a s t @ exampl e . com Candida t e : No B ra c ke t s f i r st . l a s t @examp l e . com Name : No B r a c ke t s Ema i l : f i r s t . l a s t @ e x amp l e . com Candida t e : Open B r a c k e t < f i r s t . l a s t @ex ampl e . com No ma t ch Candida t e : C l o s e B r a c k e t f i r st . l a s t @e xampl e . c om> No ma t ch

Просмотр вперед с отрицанием ( другое название - отрu.ц ате.л:ьная т�ережаю11Jая 1�роверка) , для котороl'о используется синтаксис ( ? ! ша блон ) , не должен совпадать

с текстом за текущей позицией. Например, шаблон, распознающий электронные адреса, можно видоизменить таким образом, чтобы он игнорировал адреса, начи­ нающиеся с noreply, которые обычно используются системами автоматической рассылки электронных сообщений. Листинг 1 . 50. r e_nega ti ve look ahead . р у _

_

impo rt re re . compi l e (

addre s s 1 1 1

# Адре с : u sername @doma i n . t l d # Игнорировать адре с а norep l y ( ? ! norep l y @ . * $ ) [ \ w\ d . +- ] + # Имя поль зователя @ ( [ \w\ d . ] + \ . ) + # Префикс имени доме н а ( com l o rg l edu ) # Ограничение с п и с к а доменов в ерхнего уро в н я

$

1 1 1

1

re . VERBOSE ) c andi da t e s [ u ' f i r s t . l a s t @ ex ampl e . c om ' , u ' noreply@examp l e . сот ' , =

f o r candidate i n candida te s : p r i n t ( ' Candidate : ' , c andida t e ) ma tch addre s s . s e a rch ( candi dat e ) i f mat ch : print ( ' Match : ' , candida t e [ mat ch . s t a r t ( ) : ma t c h . end ( ) ] ) else : p r i n t ( ' No mat ch ' ) =

1.3. re:

реrумрные выражения

79

Адреса, начинающиеся с noreply , не совпадают с шаблоном, поскольку не вы­ полняется условие опережающей проверки. $

pytho nЗ re_nega t i ve_l o o k_ahe a d . py

Candi da t e : f i rst . l a st @exampl e . c om Ma tch : f i r s t . l a s t @examp l e . com Candida t e : noreply@examp l e . com No ma t ch

Вместо выполнения опережающей проверки наличия строки noreply в части адреса , содержащей имя пользователя , можно было бы переписать шаблон та­ ким образом , чтобы после сопоставления с именем пользователя ныполнялся nporмornp назад с отрu.11,анu.ем (другое название отрu:цаrпелъная ретроспсктивиал проверка) , для которого используется синтаксис ( ? < ! ша блон ) . -

Листинг 1 . 51 . re ne9ative_look behind . py import re a ddre s s 1 1 1

re . c omp i le (

# Адре с : u s e rname @ doma i n . t l d [ \w \ d . +- ] + # Имя пол ь зов ателя # Игнорировать адре са n o re p l y ( ? < ! norepl y ) @ ( [ \w \ d . ] + \ . ) + # Пре фи кс име ни доме на ( com l o r g l edu ) # Ограниче ние спис ка доме н о в верхнего уро в н я

$

1 ' 1

re . VERBOSE ) cand i da t e s [ u ' f i r s t . l a s t @exampl e . c om ' , u ' n o rep l y @ example . com ' , =

f o r ca ndida t e in candida t e s : p r i n t ( ' Candidate : ' , c a ndidate ) ma t ch a ddre s s . sea rch ( candidate ) i f mat ch : p r i nt ( ' Ma t ch : ' , candi da t e ( ma t ch . s t a r t ( ) : ma t ch . e nd ( ) ] ) e l se : p r i n t ( ' No ma t ch ' ) =

Гмва 1. Тексr

80

Просмотр назад работает нескош.ко иначе, чем просмотр вперед, в том смысле, что выражение должно использовать шаблон фиксированной длины. Повторения допускаются, коль скоро задано их фиксированное количество (ни­ каких символов-заполнителей или диапазонов) . $

pythonЗ re_negat i ve_l ook_beh i nd . py

Candida t e : f i r s t . l a s t @ e xampl e . com Match : f i r s t . l a s t @ e xampl e . c om Candidat e : no repl y@examp l e . c om No ma t ch

Положительный просмотр назад можно использовать для поиска текста, сле­ дующего за шаблоном, исполыуя синтаксис ( ? \w+ ) \s+ ( ( [ \w . ] + ) \ s + ) ? # Необязател ь ное о тче с т в о или инициалы ( ? P< l a s t_name >\w+ ) \s+
' , u ' Di f ferent Name < f i r s t . l a s t @exampl e . com> ' , u ' Fi r s t Middl e Last < f i rs t . l a s t @ examp l e . com> ' , u ' Fi r s t М . L a s t < f i rst . l a s t @ e xamp l e . com> ' , =

for candidate i n candida t e s : print ( ' Candi da t e : ' , candida t e ) ma tch add r e s s . se a r ch ( candi da t e ) i f ma t ch : p r i nt ( ' Ma t ch n ame : ' , ma tch . groupdi c t ( ) [ ' f i r s t _name ' ] , end= ' ' ) p r i n t ( ma t ch . groupdi c t ( ) [ ' l a s t_name ' ] ) print ( ' Ma t ch ema i l : ' , ma t ch . groupdi c t ( ) [ ' ema i l ' ] ) else : print ( ' No ma t ch ' ) =

Выражение для проверки адресов компилируется с установленным флагом I GNORECASE, поскольку имена собственные обычно начинаются с прописной бук­ вы, тогда как в адресах электронной почты используется нижний регистр. $

python З re_r e f e r_t o_named_group . py

Candida t e : Fi r s t L a s t < f i r s t . l a s t @exampl e . com> Ma t ch name : F i r s t Last Ma t ch ema i l : f i r s t . l a s t @ exampl e . com Candida t e : Di f f e r e n t Name < f i r s t . l a s t @ exampl e . com> No ma tch Candidate : Fi r s t Midd l e La s t < f i r s t . l a s t @ examp l e . com> Ma t ch name : Fi r s t La s t Ma t ch ema i l : f i r s t . l a s t @ examp l e . com Candidat e : Fi r s t М . Last < f i r s t . l a s t @ exampl e . com> Ma t ch n ame : F i r s t Last Match ema i l : f i r s t . l a s t @exampl e . com

Другой механизм использования обратных ссылок в выражениях выбира­ ет шаблон в зависимости от того, было ли найдено совпадение для предыдущей группы. Шаблон для поиска адресов электронной почты можно скорректиро­ вать таким образом, чтобы угловые скобки требовались лишь при наличии име­ ни и не требовались, если проверяется сам электронный адрес. Проверка то1·0, б ыло ли найдено совпадение для группы, осуществляется с помощью синтакси­ са ( ? ( id) yes - expre s s ion 1 no-expre s s i on ) , где id - имя или номер группы, ye s - expre s s ion - шаблон, используемый , если группа имеет значение, а no­ expre s s ion - шаблон, используемый в противном случае.

Гмва 1. Tetcef

84 Листинг 1 . 55 . re id . py irnpor t re a ddre s s ' ' '

re . cornp i l e (

# Имя сос тоит из букв и може т в ключать символы точки # в сокращен ных вариа нтах обраще ния и и нициалах ( ? P< narne > ( ( \ w . ] + \ s + ) * [ \w . ] + )? \s*

"

"

# Адре са эле ктронной почты должны быт ь заключе ны в угловые # скобки , но толь ко в том случае , е сли найде но имя ( ? ( narne } # Остаток заключен в угловые скобки , поскол ь ку # имя присутствует ( ? P ( ? = ( < . * > $ } } } 1

# Остаток не за ключен в угловые скобки , посколь ку # имя о т сутствует ( ?= ( [ " < ] . * [ ">] $ } )

# На ходить угло вую скобку толь ко в том случае , е сли # опережающа я проверка обнаружила обе скобки ( ? ( b racket s } < l \ s * ) # Собств е н н о адре с : u s e r narne @dorna i n . t l d ( ? P< ema i l > [ \ w\d . + - ] + # Имя поль зователя @ ( [ \ w\d . J + \ . ) + # Пре фикс име ни доме на ( corn l or g l edu ) # Огра ничение списка доменов верхне г о уровня

)

# Находить угловую скобку тол ь ко в том случа е , е сли # опережающа я пров ерка обнаружила обе с кобки ( ? ( br a c ke t s } > l \ s * }

$

' ' ' re . VERBOS E ) candi da t e s [ u ' Fi r s t La s t < f i r s t . l a s t @ e xarnple . com> ' , u ' No Bra c ke t s f i r s t . l a s t @example . com ' , u ' Open Bracket < f i r s t . l a s t @e xampl e . com ' , u ' C l o s e B r a c ket f i r s t . l a s t @exampl e . com> ' , u ' no . b r a c k e t s @exampl e . com ' , =

1.3. re: реrумрные выражения

85

f o r candidate in candida t e s : p r i n t ( ' Candi da t e : ' , candida t e ) ma tch addre s s . s e a r c h ( candida t e ) i f ma tch : print ( ' Ma tch name : ' , ma t c h . g roupd i c t ( ) [ ' name ' ] ) print ( ' Ma tch ema i l : ' , ma t c h . groupd i c t ( ) [ ' ema i l ' ] ) else : p r i n t ( ' No ma t c h ' ) =

В этой версии выражения для ра:1бора адреса электронной почты использу­ ются две проверки. Если для группы name найдено совпадение, то проверка тре­ бует наличия обеих скобок и устанавливает группу bracket s . Е ' , t e xt ) )

Гмва 1. Текст

88

Для ссылки на текст, совпавший с шаблоном, можно использовать синтаксис нумерованных обратных ссылок { \ номер) . $ pythonЗ re_sub . py Text : Make t h i s * * bo ld* * . Th i s * * too * * . Bo l d : Make t h i s bo l d< /b> . Thi s too < / b > .

Именованные группы обозначаются в замещающем тексте с помощью синтак­ сиса \ g . Листинг 1 . 5 7. re suЬ named groups . ру import re bo l d

re . compi l e ( r ' \ * { 2 } ( ? P . * ? ) \ * { 2 } ' )

text

' Ma ke thi s * *bo l d* * .

Thi s * * too * * . '

pri nt ( ' Text : ' , t e x t ) print ( ' Bo l d : ' , bold . sub ( r ' \ g < / b > ' , t e xt ) )

Синтаксис \ g< имя> работает также с нумерованными ссылками, и его исполь­ зование позволяет устранить неоднозначность, создаваемую одновременным на­ личием номеров групп и окружающими литеральными ци ф рами. $ pythonЗ r e_sub_named_g roups . py Text : Make t h i s * * bold* * . Thi s * * t oo * * . Bo l d : Ma ke t h i s bold< /b> . Thi s too< /b> .

Количество выполняемых замен можно ограничить, передав функции sub ( ) значение count. Листинг 1 . 58. r e s uЬ coun t . py impp r t re bold

re . compi l e ( r ' \ * { 2 } ( . * ? ) \ * { 2 } ' )

text

' Ma ke thi s * *bol d * * .

Thi s * * too * * . '

print ( ' Text : ' , t e x t ) print ( ' Bo l d : ' , b o l d . sub ( r ' \ 1 < / b> ' , t e x t , count = l ) )

Будет выполнена всего одна замена, поскольку аргумент count равен 1 . $ pythonЗ re_sub_count . py Text : Make t h i s * * bo l d * * . Th i s * * too* * . Bol d : Make t h i s bold< /b> . Thi s * * t oo * * .

Функция subn ( ) аналогична функции sub ( ) , за исключением того, что она возвращает как измеt1енную строку, так и количество выполненных замен.

1.3. re: реrумрные выражен""

87

Листинг 1 . 59. re suЬn . ру irnport r e bold

re . cornpi l e ( r ' \ * { 2 } ( . * ? ) \ * { 2 } ' )

text

' Ma ke this * * bol d* * .

Thi s * * too * * . '

p r i nt ( ' T ext : ' , text ) p r i n t ( ' Bold : ' , b o l d . s ubn { r ' \ l < /b> ' , t e xt ) )

В этом примере шаблон поиска совпадает с текстом дважды. $ pythonЗ re_s ubn . py Text : Ma ke t h i s * * bo l d * * . This * * to o * * . Bo l d : ( ' Ma ke thi s bold< /b> . T h i s too . ' , 2 )

1 . 3 . 1 1 . Разбиение текста с по м ощью шаблонов Метод s t r . spl i t ( ) - один из тех, которые чаще всего используются для раз­ биения анализируемых строк. Однако в качестве разделителей он поддерживает лишь литеральные значения, в связи с чем иногда для этих целей приходится ис­ пользовать реI"улярные выражения. 1 lапример, во многих языках разметки 11ро­ сто1·0 текста разделителем абзацев служит последовательность двух или более символов перевода строки ( \ n ) . Именно слова "двух или более символов " в этом определении делают невозможным применение метода s t r . spl i t ( ) в подобных случаях. Страте1·ия идентификации абзацев с помощью функции f inda l l ( ) пред­ полаmет использование шаблона вида ( + ? ) \ n { 2 , } . _

Листинг 1 .60. re_;:iaragraphs findall . ру irnpo r t r e t e x t = ' ' ' P a r a g raph one о п two l i nes . P a r a g raph two .

P a r a g raph thre e . ' ' ' for nurn, pa ra i n enurne rate ( re . f i nda l l ( r ' ( . + ? ) \ n { 2 , } ' , text , f l a g s =re . DOTALL ) p r i n t ( num, repr ( pa ra ) ) print ( )

):

Этот шаблон не работает для абзацев, находящихся в конце входного текста, что подтверждается отсутствием текста "Paragrap l1 tl1ree. " в составе выведенной информации.

Гмве 1. Текст

88 $ pythonЗ re_pa r a g raphs_f i nda l l . py О ' Pa r a g raph one \ non two l i n e s . ' 1 ' Pa rag raph two . '

Расширение шаблона для учета того факта, что концом абзаца должен счи­ таться нс только удвоенный символ перевода строки, но и конец всего входного текста, позволяет решить эту проблему, но усложняет шаблон. Замена функции re . f i nda l l ( ) функцией re . sp l i t ( ) позволяет справиться с граничными услови­ ями и сохранить простоту шаблона. Л истинг 1 .61 . re_split . py import re text = ' ' ' Pa rag raph one on two l i ne s . P a r a g r aph two . P a r a g raph three . ' ' ' p r i n t ( ' With finda l l : ' ) for num, pa ra in enume r a t e ( re . f i nda l l ( r ' ( . + ? ) ( \ n { 2 , } 1 $ ) ' , text , f l ag s = r e . DOTALL ) ) : p r i nt ( num, repr ( pa ra ) ) p r i nt ( ) print ( ) p r i n t ( ' Wi t h spl i t : ' ) for num, pa r a i n enume r a t e ( re . spl i t ( r ' \ n { 2 , } ' , t ext ) ) : p r i nt ( num , repr { pa r a ) ) print ( )

Аргумент шаблона в функции sp l i t ( ) более точно выражает спецификацию разметки. Два и более символа перевода строки обозначают границу между абза­ цами во входной строке. $ pythonЗ re_spl i t . py With f i ndal l : О ( ' P a r a g raph on e \ non two l i n e s . ' , 1

( ' Parag raph two . ' ,

' \n\n\n ' )

2

( ' Pa rag raph three . ' ,

' ')

With spl i t : О ' Pa r a g r aph one \non two l i nes . '

' \n\n ' )

1.3. re: реrумрные выражения

89

1 ' P a r a g raph two . ' 2 ' Pa r a graph three . '

Заключение выражения в скобки для определения группы заставляет функ­ цию s p l i t ( ) работать аналогично методу s t r . pa r t i t ion ( ) , поэтому наряду с другими частями строки он возвращает также значения разделителя. Листинг 1 .62. re spli t_groups . ру _

import re text = ' ' ' Parag raph one on two l i ne s . P a r a g r a ph two . P a r a g raph three . ' ' ' p r i n t ( ' Wi t h spl i t : ' ) f o r num, para i n e nume rate ( re . spl i t ( r ' ( \ n { 2 , } ) ' , t e xt ) ) : p r i n t ( num, repr ( pa ra ) ) print ( )

Теперь в вывод включаются не только все абзацы , но и разделяющие их симво­ лы перевода строки. $

pythonЗ re_spl i t_g r oups . py

W i t h s pl i t : О ' Pa r a g raph one \ non t wo l ines . ' 1

' \n\n '

2 ' Pa r a g raph two . ' 3 ' \n\n\n ' 4 ' Pa r a g ra ph three . '

Дополнительные ссылки •

Раздел документации стандартной библиотеки, посвященный модулю re 7 .



Regu/ar Expression НОWГО (Andrew Kuchling). Введение в регулярные выражения для разработчиков Python. 8 Kodos (Phil Schwartz) . Интерактивная программа для тестирования регулярных выра­ жений. 9 pythex (Gabriel Rodriguez) . Веб-инструмент для тестирования регулярных выражений,





аналогичный Rubular.

7 8 9

https : / / docs . python . o r g / 3 . 5 / l ibra r y / re . html http : / / kodos . source forge . net pythex . org

Гмва 1. Текст

90 •

Википедия : Регулярные выражения 1 0 • Общее введение в теорию и практику реrуляр­ ных выражений.



l o c a l e ( раздел 1 5.2). Использование модуля l o ca l e для настройки языковых параме­ тров при работе с текстом в кодировках Unicode.



Uni codeda t a . Программный доступ к базе данных свойств символов Unicode.

1 .4. di ff l ib: сравнение последовательностей М о дуль di f f l i b содержит инструменты, предназначенные для вычисления и обработки различий между последовательностями. Он особенно полезен для сравнения текстов и включает функции, генерирующие отчет ы с использованием нескольких популярных форматов. В приведенных в э том разделе примерах используется текст, который хранит­ ся в модуле di f fl ib _da ta . ру. Листинг 1 .63. difflib_da ta . py t ex t l " " " Lo rem i ps um do l o r s i t ame t , cons e c t e t u e r adipi s c i n g e l i t . I nt e g e r e u l a cus a c cums an a r c u f e rmentum eui smod . Done c pu l v i na r por t t i to r t e l l u s . Al i quam venena t i s . Done c faci l i s i s pha r e t ra t o r t o r . I n n e c mau r i s e g e t magna consequa t conva l i s . Nam s e d s em v i t a e odi o pe l l e n t e s que i n t e rdum . S e d consequat vive r ra n i s l . S u s pendi s s e a r cu me tus , Ы a ndit qu i s , rhon cus а с , pha r e t r a eget , vel i t . Ma u r i s urna . Morbi nonummy mo l e s t i e o r c i . P r a e s e n t n i s i e l i t , f r i ng i l l a а с , s u s c ipi t non , t r i s t i que vel , mau r i s . Curab i t u r ve l l o rem id n i s l porta adipi s c i n g . Suspendi s s e eu l e ctus . In nunc . Du i s vu l put a t e t r i s t i que enim . Don e c qu i s l e c t u s а j u s t o imperdi e t tempus . " " " =

t e x t l_l i n e s

=

t e x t l . spl i t l i n e s ( )

t ex t 2 " " " Lo rem i psum do l o r s i t ame t , cons e c t e t u e r adipi s c i ng e l i t . I n t e g e r eu l a cu s a c cums an a rcu f e rmentum e u i smod . Donec pu l v i na r , port t i t o r t e l l u s . A l i quam venena t i s . Donec f a c i l i s i s pha r e t r a t o rtor . I n n e c mau r i s e g e t ma gna consequat conva l i s . Nam c r a s vitae mi vitae odio pe l l en t e sque inte rdum . S e d con s e quat v i v e r r a n i s l . Suspend i s s e a r cu me tus , Ы a ndit qu i s , rhoncus а с , pha r e t r a e g e t , ve l i t . Mau r i s urna . Mo rbi nonummy mo l e s t i e o r c i . P r a e s e n t n i s i e l i t , f r i ng i l l a ас , s u s c ipi t non , t r i s t i que vel , mau r i s . Curabi tur v e l l o rem id n i s l porta adipi s c i n g . Dui s vu lputate t r i s t i qu e e n im . Don e c qu i s l e ct u s а j us t o impe rdiet tempus . S uspendi s s e eu l e ctus . I n nunc . 1 1 1 1 11 =

t ex t 2 l i nes

=

t e x t 2 . spl i t l i n e s ( )

1 .4 . 1 . Сравн е ни е версий те кста Класс D i f fer работает с последовательностями текстовых строк, генерируя так называемые де.льтъt - инструкции, включающие различия между отдельными 10

https : / / ru . wi k i pe d i a . о r g / wi k i / Р е гул ярные_выраже ния

1А. dlffllЬ: сравнение nOCAeдoВ8181\ЫIOCJ8iii

91

строками в удобочиrасмом виде. Информация, выводимая классом D i f f e r по умолчанию , аналогична той , которая выводится командой d i f f в U пix. Она включает исходн ые входные значения обоих списков, в том числ е общие зна­ чения, и разметку, обозначаю щую внесенные изменения. •







Строки с префиксом - , которые встречаются в первой последовательно­ сти, но отсутствуют во второй. Строки с префюссом + , которые встречаются во второй последовательно­ сти, но отсутствуют в первой. В случае строк с частичными различиями между версиями используется дополнительная строка с префиксом ? , выделяющая изменение в новой версии. Если строка не изменилась, то она выводится с префиксом в виде дополни­ тельного пробела в левом столбце, что обеспечивает их выравнивание от­ носительно других строк, претерпевших изменения.

Разбиение текста на последовательности отдельных строк перед передачей их методу compare ( ) улучшает чиrаемость выводимых результатов по ' , d [ ' foo ' ] ) p r i n t ( ' ba r => ' , d [ ' bar ' ] )

Этот метод хорошо работает при условии, что использование одного и того же значения по умолчанию подходит для весх ключей. Он особенно удобен, если значением по умолчанию являеn�я тип, испол1.зуемый для агрегирования или аккумулирования таких значений, как списки , множества и даже целые числа. В документации стандартной библиотеки приведены п римеры , в которых тин de f а и 1 tdi ct используется именно таким способом. $

pyt hon З col l e c t i on s_de fau l t d i c t . py

d : de faul t d i c t ( < funct i o n de faul t_factory at О х 1 0 1 9 2 1 9 5 0 > , { ' foo ' : ' ba r ' ) ) foo = > bar b a r = > d e f a u l t va l ue

Дополн ительные ссыл ки 4 • de faul tdi ct . Примеры использования функции defaul tdi c t, приведенные в доку­

ментации стандартной библиотеки. •

Evolution of Default Dictionaries in Python 5 (James Tauber). Обсуждение того, ка к функ­ ция defaul tdi c t ( ) соотносится с другими средствами инициализации словарей.

2 . 2 .4. deque : двухсторонняя очередь Двухсторонняя очередь, deque, поддерживает добавление и удаление элемен­ тов на любом конце очереди. Более известные и чаще используемые стеки и оче­ реди представляют собой вырожденные формы двухсторонних очередей , в кото­ рых входные и выходные данные ограничены одним концом очереди. Листи нг 2.24. collections deque . ру _

irnpo r t col l e c t i o n s d = c o l l ection s . deque ( ' abcde fg ' ) p r i n t ( ' Deque : ' , d ) p r i n t ( ' Leng t h : ' , l e n ( d ) ) p r i n t ( ' Le f t end : ' , d [ O J ) 4

https : / /docs . python . o r g / 3 . 5 / l ib r a r y / co l l e c t i o n s . htrnl #defaul tdi ct-exarnpl e s http : / / j taube r . com/ Ь l og / 2 0 0 8 / 0 2 / 2 7 /evoluti on_o f_defaul t_d i c t i on a r i e s _in_ python / 5

ГМ188 2. С1руктуры AllНHlllX

1 18 p r i nt ( ' Ri ght end : ' , d [ - 1 ) ) d . rernove ( ' с ' ) p r i nt ( ' r ernove ( c ) : ' , d )

Поскольку двухсторонние очереди отнщ:ятся к линейным контейнерам, в чис­ ло поддерживаемых ими операций входят такие операции, 1юддерживаемые ти­ пом l i s t , как получе н ие содержимого с помощью метода _ge t i tem_ ( ) , опре­ деление размера (дли ны) очереди и удаление первого вхождения значения. $ pythonЗ col l e c t i o n s _deque . py De que : deque ( [ ' а ' , ' ь ' , , с , , , d , , , е , , , f , , , g , ] ) Lengt h : 7 L e f t end : а Ri ght end : g r ernove ( с ) : deque ( [ ' а ' , ' Ь ' , ' d ' , ' е ' , ' f ' , ' g ' ) )

2. 2.4. 1 . Доб авление элементов Двухсторонняя очередь может заполняться элементами на любом из ее кон­ цов, которые в терминологии Pytl1011 называются "левым" и "правым" концами. Листинг 2 .25. col lections_deque_popula ting . ру

irnport col l e c t i o n s # Доба вле ние с права dl col l e ct i ons . deque ( ) d l . extend ( ' abcde fg ' ) p r i nt ( ' ext end : ' , d l ) d l . append ( ' h ' ) print ( ' append : ' , d l ) =

# Добавление слева d2 = col l ect i on s . deque ( ) d2 . extendl e f t ( ra n g e ( 6 ) ) print ( ' ext endl e f t : ' , d2 ) d2 . append l e f t ( 6 ) print ( ' a ppendl e f t : ' , d2 )

Метод extendl eft ( ) расширяет очередь, итерируя по всем элементам пе­ редашюго ему итерируемо1·0 объекта и выпол няя по отношению к каждому из них операцию , э квивалентную той , которую выполняет метод appendl e f t ( ) . Ко н ечным результатом является добавление в очеред1> всей входной последова­ тельности в обратном порядке. $ python З co l l ect ion s_deque_popul a t i ng . py e x t end : deque ( [ ' а ' , append : deque ( [ ' а ' , extendl e ft : deque ( [ 5 , a ppend l e ft : deque ( [ б ,

'Ь' , 'Ь' , 4, 5,

' с' , 'd' ' с' , 'd' З, 2, 1, 4, З, 2,

, 'е' , , 'е', 0) ) 1, 0) )

'f' , ' f' ,

'g' ) ) 'g' , 'h' ) )

2.2. collectlons: момтейнерные 1ИПЫ А8ННЫХ

1 17

2.2.4.2.

Извлечение элементов Точно так же, в зависимости от применяемого алгоритма, двухсторонняя оче­ редь допускает извлечение элементов на любом из ее концов.

Листинr 2.26. collections_deque consumin9 . ру _

impo r t co l l e c t i on s print ( ' From t h e r i g h t : ' ) col l e ct ions . deque ( ' abcde f g ' ) d whi l e True : try: print ( d . pop ( ) , end= ' ' ) except I ndexE r ro r : break print =

print ( ' \ n From the l e ft : ' ) d co l l e c t i ons . deque ( range ( б ) ) wh i l e True : try: print ( d . pop l e f t ( ) , e nd= ' ' ) except I ndexError : brea k print =

Метод р о р ( ) удаляет и возвращает элемент, нахо;�ящийся на н равом кон це двухсторонней очереди; метод popleft ( ) вьшолняет аналогичную операцию па левом конце очереди. $

python З co l l e c t i ons_deque_con sumi n g . py

From the r i ght : g fedcba From the l e ft : 012345

Поскольку двухсторонние очереди потокобезопасны, возможно одновремен­ ное извлечение элементов на обоих концах двухсторонней очереди с исrюльзова­ нием независимых потоков. Листинr 2.27. collection s deque both ends . ру _

_

_

import c o l l e c t i ons impo rt threading imp o r t t i me cand l e

=

col l e c t i on s . deque ( range ( 5 ) )

def burn ( di re c t i on , nextSource ) : whi l e True : t ry :

1 18 next = nex t S ource ( ) except I ndexErro r : break else : p r i nt ( ' { : >B } : { J ' . fo rmat ( di rect i o n , next ) ) t ime . s l e ep ( O . l ) p r i nt ( ' { : >B J done ' . f o rma t ( d i rect i o n ) ) r e t urn l e f t = threading . Thread ( t a r g e t =burn , a rgs= ( ' Le ft ' , candl e . popl e f t ) ) ri ght = threading . Thread ( t a r g e t =burn , a r g s = ( ' Ri ght ' , candl e . рор ) ) l e f t . s t a rt ( ) r i ght . s t a r t ( ) left . j oin ( ) r i g ht . j o i n ( )

В этом пр имере потоки п оочередно удаляют элементы на каждом из ко н цов двухсторонней очереди , п ока она не станет пус той .

$

pythonЗ c o l l e ct i on s _deque_both_ends . py

Le f t : О Right : 4 Ri ght : 3 Le ft : 1 Ri ght : 2 L e f t done Ri ght done

2 . 2.4. 3 .

Прокрутка элементов Еще одним полезным свойством двухсторо нних очередей является возмож­ н ость прокрутки элементов с целью про пуска некоторых из них.

Листинг 2.28: collections_deque_rotate . ру import col l e ct i on s d = c o l l e ct i ons . deque ( range ( l O ) ) p r i n t ( ' Norma l : ' , d ) d = co l l e c t i ons . deque ( range ( 1 0 ) ) d . rotate ( 2 ) p r i n t ( ' Ri ght r o t a t i o n : ' , d ) d c o l l e c t ions . deque ( range ( 1 0 ) ) d . r o t a t e ( -2 ) p r i nt ( ' Le f t r o t a t i o n : ' , d ) =

Прокрутка двухсторонней очереди в п раво ( положи тельное значение ар гумен­ та ) приводит к удалению элементов на п равом конце очереди и добавлен и ю их на левом конце. Прокрутка влево ( отрицатель н ое значение ар гумента ) при водит

2.2. collectlona: lflОН18ЙН8РНЫ8 1МПЫ А8ННЫХ

1 18

к удалению элементов на левом конце очереди и добавлению их на правом. В дан­ ном случае полезной визуальной аналогией этих процессов может послужить кру­ говой циферблат. $

python3 c o l l e c t i o n s_de que _ rotat e . py

N o rma l deque ( [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] ) Ri ght r o t a t i o n : deque ( [ 8 , 9 , О , 1 , 2 , 3 , 4 , 5 , 6 , 7 ] ) L e f t r o ta t i on : deque ( [ 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , О , 1 ] )

2 . 2 .4.4. Ограничение длины очереди

Размер э кземпляра двухсторонней очереди можно ограничить так, чтобы ее длина никогда не превышала заданной величины. При достижении 0•1ередью указанной длины новые элементы добавляются за счет удаления существующих. Такое поведение очереди может быть ис11ользова110 для нахождения последних n элементов потока неопределенной длины. Листинг 2 . 29.

collections deque_шaxlen . ру _

imp o r t co l l e c t i o n s i mpo rt r andom

# Уста новить для random э а травочное эначение , чтобы обе спечить # получение одного и того же вывода при каждом эапуске сценария random . s e ed ( l )

c o l l e c t i ons . de que ( ma x l en=3 ) c o l l e c t i ons . de que ( ma x l e n= 3 )

dl d2

f o r i i n range ( 5 ) : n random . randi n t ( O , print ( ' n = ' , n ) d l . append ( n ) d2 . appe n d l e f t ( n ) print ( ' Dl : ' , dl ) p r i nt ( ' D2 : ' , d2 ) =

100)

Длина двухсторонней очереди поддержи вается постоянной независи мо от того, на каком конце добавляются элементы. $

python3 co l l ec t i on s_de que_max l en . py

n Dl : D2 : n Dl : D2 : n = Dl : D2 : n =

=

17 deque ( deque ( 72 deque ( deque ( 97 deque ( deque ( 8

[ 1 7 ] , max l en= 3 ) [ 1 7 ] , max l en=3 ) [ 1 7 , 7 2 ] , max l e n = 3 ) [ 7 2 , 1 7 ] , max l e n = 3 ) [ 17, 72, [ 97 , 7 2 ,

9 7 ] , max l e n= 3 ) 1 7 ] , max l e n= 3 )

Гмве 2. Сrрукrуры данных

1 20 Dl : D2 : n = Dl : D2 :

deque deque 32 deque deque

( [ 7 2 , 9 7 , 8 ] , max l e n= 3 ) ( [ 8 , 9 7 , 7 2 ] , max l e n=3 ) { [ 97 , 8, ( [32, 8 ,

3 2 ] , ma x l e n= 3 ) 9 7 ] , max l en=3 )

Дополнительн ые ссьU1КИ • •

Википедия: Двухсторонняя очередь6 . Обсуждение структуры двухсторонней очереди. deque Recipes7 . П римеры использования двухсторонних очередей в алгоритмах, приве­ денные в документации стандартной библиотеки.

2 . 2 . 5. namedtuple:

подкл асс

Tuple

с именованными полями

Дл я доступа к элементам стандартного типа tuple используются числовые ин­ дексы. Листинг 2 . 30. collections tuple . ру _

ЬоЬ = ( ' В оЬ ' , 3 0 , ' ma l e ' ) p r i n t ( ' Repre s en t a t i on : ' , ЬоЬ ) j a ne { ' Ja ne ' , 2 9 , ' fema l e ' ) p r i n t ( ' \ n Fi e l d Ь у i ndex : ' , j ane [ О ] ) =

p r i n t ( ' \ n Fi e l ds Ьу i ndex : ' ) f o r р i n [ Ьо Ь , j a ne ] : p r i nt ( ' { } i s а { } yea r o l d { } ' . fo rma t ( * p ) )

Этот способ доступа удобно использовать в случае простых структур данных. $ python3 co l l e c t i o ns_tupl e . py Rep r e s e nt a t i o n :

{ ' В оЬ ' , 3 0 ,

' ma l e ' )

Fi e l d Ьу inde x : Jane Fi e l d s Ьу i ndex : ВоЬ i s а 3 0 yea r o l d ma l e Jane i s а 2 9 yea r o l d fema l e

Однако попытки запомнить, какой индекс следует использовать дл я каждого значения, моrут приводить к ошибкам, особенно если многие поля кортежа кон­ струируются вдали от тех мест кода, в которых они ис п ользуются . И менованные кортежи обеспечивают возможность обращения к элементам не только по индек­ сам, но и по именам .

6 h t tp s : / / ru . wi kipedi a . o r g /wi ki /Двyx c тopoнняя_oчepeдь 7 https : / / do c s . python . o r g / 3 . 5 / l i b r a r y / co l l e c t i on s . html #deque- rec ipes

2.2.

COllectlonl: КОН1еАнерные 1ИПЫ A8HHlllA

1 21

2.2.5. 1 . Определение Ввиду того что экземпляры namedtup l e не используют словарей, их эффек­ тивность в отношении использования памяти та же, что и у экземпляров обыч­ ных кортежей . Каждый ти п и менованного кортежа представляется собствен­ ным классом, который создастся с 1юмощ1,ю функции-фабрики namedtupl e ( ) . Аргументами этой функции являются имя нового класса и строка, содержащая имена элементов. Листинг 2.31 . col lections namedtuple_J)er s on . ру _

import co l l e c t i o n s P e r son = c o l l e ct i o n s . namedtupl e ( ' P e r s o n ' ,

' name age ' )

ЬоЬ = P e r s o n ( name= ' Bob ' , а g е= З О ) p r i n t { ' \nRepre sent a t i on : ' , ЬоЬ ) j a ne = Pe r son ( name= ' Jane ' , a g e=2 9 ) p r i n t ( ' \ n Fi e l d Ь у name : ' , j ane . name ) p r i n t ( ' \ n F i e l d s Ьу i ndex : ' ) for р in [ ЬоЬ , j an e ] : p r i nt { ' { } i s ( } ye a r s ol d ' . forma t ( *p ) )

Как продемонстрировано в этом примере , к полям именованных кортежей можно обращаться не только по их позиционным индексам, как в случае обыч­ ных кортежей , но и по именам с использованием точечной нотации ( объект .

а трибут) .

$ pythonЗ col l e ct i on s_namedtuple_pe r son . py Repre sent a t ion : Pe rson ( name= ' Bob ' , а g е =З О ) F i e l d Ьу name : Jane F i e l d s Ьу i ndex : ВоЬ i s 3 0 ye a r s o l d J a n e i s 2 9 yea r s o l d

Как и обычный тип tuple, 1'И н namedtuple неизменяемый. Благодаря этому экземпляры кортежа могут иметь хеш-значения , что позволяет исполь::ювать их в качестве ключей в с.ловарях и включать в состав множеств. -

Листинг 2.32 . col lections namedtuple immutaЫe . ру _

_

import co l l e c t i on s Pe r son

=

c o l l e c t i on s . namedtupl e ( ' Pe r s on ' ,

pat = Per son ( name= ' Pa t ' , a g e= 1 2 ) p r i n t ( ' \nRepre sentat i o n : ' , pat ) pat . a ge

=

21

' name age ' )

Гмва 2. Струюуры А&ннwх

1 22

Попытки изменения значения посредством его именованного атрибута приво­ дят к возбуждению исключения Att ribut eError. $ pythonЗ col l e c t i on s_namedtup l e_immu t a Ы e . py Rep r e s e nt a t i on : Pe r s o n ( name = ' Pa t ' , a g e = 1 2 ) T r a ceba c k ( rno s t r e cent ca l l l a s t ) : Fi l e " co l l e c t i ons_narnedtup l e_immutaЫ e . py" , l i ne 1 7 ,

pat . a ge 21 At t ribu t e E r r o r : can ' t s e t a t t r i bute

in

=

2 . 2 . 5.2.

Недопустимые имена полей Допустимыми являются имена полей, которые не повторяются и не вступают в конфликт с ключевыми словами Руtlюп . Листинг 2.33.

collections namedtuple bad fields . ру _

_

_

import col l e c t i ons t ry : col l e c t ions . namedtup l e ( ' Pe r s on ' , except Va l u e E r r o r a s e r r : print ( e rr )

' narne c l a s s a g e ' )

try : ' name age a g e ' )

col l e c t i ons . narne dtup l e ( ' Person ' , e x cept Va l u e E r r o r a s e r r : print ( err )

Если в процессе синтаксического разбора имен полей анализатор обнаружива­ ет недопу12 } { : >12 } { : >12 } ' p r i nt ( fmt . fo rma t ( ' Al hex ' , ' Al ' , ' А2 hex ' , ' А2 ' ) ) p r i nt ( fmt . forma t ( ' - ' * 1 2 , ' - ' * 1 2 , ' - * 1 2 , ' - ' * 1 2 ) ) fmt ' { ! r : >12 } { : 12 } { ! r : >12 } { : 12 } ' f o r va l u e s i n z i p ( to_hex ( a l ) , a l , t o_he x ( a 2 ) , а 2 ) : p r i nt ( fmt . fo rma t ( * va l u e s ) ) =

'

=

Метод byte swap ( ) изменяет порядок следования байтов в элементах масси­ используя код, написанный на языке С и поэтому работающий намного эф­ фекти1шее по сравнению с обработкой дан ных в цикле с помощью кода на языке ва,

Руtlюп.

$ pyt hon3 a r r a y_b yteswap . py Al hex

Al

А2 hex

А2

Ь ' 7 8 56 3 4 1 2 ' Ь' 7 9563 4 12 ' Ь ' 7а563 4 12 ' Ь ' 7Ь563 4 1 2 ' Ь ' 7с563 4 1 2 '

3054 19896 3054 1 9 8 97 3054 19898 3054 1 9 8 99 3 054 1 9 9 0 0

Ь ' 1234 567 8 ' Ь ' 1234 567 9 ' Ь ' 1234 56 7 а ' Ь ' 12 3 4 567Ь ' Ь ' 1234 567с '

2018915346 2 0 3 5 6 92 562 2 0 5 2 4 6 97 7 8 2 0 6 92 4 6 994 2 0 8 6 02 4 2 1 0

------- ----- - ----------- -- ---------- - - - - - - ---- --

2.4.

heapq: 8NОРМ1М сортировки кучи

1 33

Дополн и тельные ссылки •

Раздел документации стандартной библиотеки, посвященный модулю a r ra y



s t ruct (раздел 2.7). Описание модуля s t ruct.



12 •

Python для численных расчетов13. NumPy - это библиотека Python, предназначенная для эффективной обработки больших наборов данных.



Замечания относительно портирования програ мм из Python модуля a r r a y (раздел

А.6.4).

2

в Python З, касающиеся

2.4. heapq: алгоритм сортировки кучи K'J'Ut это древовидная структура данных, в которой между дочерними и роди­ тельскими узами установлены отношения порядка сортировки. /t:1юичная куча мо­ жет быть представлена списком или массивом, организованным таким о бразо м, что дочерние узлы элемента N находятся в позициях 2* N+ 1 и 2* №2 (индексы от­ считываются от нуля) . Такая компоновка делает возможной реорганизацию кучи на месте, что избавляет от необходимости перераспределения больших объемов памяти при добавлении или удалении элементов. В кучах max-lteap гарантируется , что значение в любом родительском узле не может быть меньшим, чем значение в любом из двух его дочерних уалов. В кучах min-heap выполняется противоположное условие: значение в любом родительском узле не может быть большим, чем значение в любом иа двух его дочерних узлов. Модуль heapq в Pytlюn реализует кучу min-lieap. -

2 .4. 1 . Данные для примеров В примерах, приведенных в этом разделе, испольауются данные, хранящиеся в файле heapq_heapda ta . ру. Листинг 2.46. heapct_heapdata . py # Эти данные сгенерированы с помощью модуля ra ndom da t a = [ 1 9 , 9 , 4 , 1 0 , 1 1 )

Для вывода данных кучи испольауется файл heapq_ showtree . ру. Листинг 2.47. heapq_showtree . py impo r t ma th f r om io impo r t S t r i n g I O d e f s how_t ree ( t ree , t o t a l _wi dth= З б , f i l l = ' ' ) : " " " Красивый вывод дере ва . " " " output StringIO ( ) l a s t row = - 1 for i , n i n e nume rat e ( t re e ) : if i : row = i n t ( ma t h . f l o o r ( ma t h . l og ( i + 1 , 2 ) ) ) =

12 13

ht tps : / /docs . python . o r g / 3 . 5 / l i b r a r y / a r r a y . html www . s c ipy . o rg

1 34

rмва 2. CrPrfOYPW АВнных else : row О i f row ! = l a s t row : output . wr i te ( ' \ n ' ) columns 2 * * row co l_wi dth int ( ma th . f l oo r ( t o t a l_wi dth / co l urnn s ) ) output . wr i t e ( s t r ( n ) . ce n t e r ( co l_width, f i l l ) ) l a s t row row p r i nt ( output . getvalue ( ) ) p r i nt ( ' - ' * tota l_width ) p r i nt ( ) =

=

=

=

2.4. 2 . Соэдание кучи Суще 3 } : ' . fo rmat ( n ) ) heapq . heappush ( heap, n ) show_t re e ( heap )

При добавлении n кучу новых элементов из источника данных с помощью ме­ тода heappush ( ) в ней сохраняется установленный для нее порядок сортировки. $ pythonЗ heapq__h eappush . py ra ndom : add

[ 19, 9, 4 , 10,

19: 19

add

9: 9 19

add

4: 4

11]

2.4. heapq: 8А1'Орм1М сортировки кучи 19

add

135

9

10 : 4

10

9

19

add

11 : 4

10 19

9 11

Если данные уже находятся в памяти , то более эффективным способом пере­ становки элементов списка на месте является исполь:юванис метода heap i fy ( ) . Л истинг 2.49. heapq_heapify . ру impo r t heapq from heapq_showt ree impo r t show t re e from he apq_heapda t a impo rt data print ( ' ra ndom : ' , da t a ) heapq . heapi fy ( da t a ) print ( ' heapi f i e d : ' ) show_t r e e ( da t a )

Создание списка путем добавления по одному элементу за раз с соблюдением установленного для кучи порядка сортировки приводит к тому же результату, что и соаданис неупорядоченного списка с последующим вызовом метода heap i fy ( ) . $ python З heapq_heapi f y . py random heap i f i e d

[ 19, 9, 4 , 10, 11]

4

19

9 10

11

2.4. З . До ступ к содержимому ку чи Для удаления элемента с наименьшим значением из корректно организован­ ной кучи используется метод heappop ( ) .

°'888 2. С1рук1уры А8ННЫХ

1 38 Л истинг 2 . 50. heapq_heappop . py impo r t heapq f r om heapq_s howt ree import show t re e f r om heapq_he apda t a import da t a p r i n t ( ' ra ndom : ' , data ) heapq . heapi f y ( da t a ) pr i n t ( ' heapi f i e d : ' ) show_t ree ( da t a ) pr i n t f o r i i n range ( 2 ) : sma l l e s t heapq . heappop ( da t a ) p r i n t ( ' pop { : > 3 } : ' . fo rma t ( sma l l e s t ) ) show_t ree ( da t a ) =

В этом примере, адаптированном на основе документации стандартной библи­ отеки , методы heap i fy ( ) и heappop ( ) используются для сортировки числового списка. $ pythonЗ heapq_hea ppop . py ra ndom heap i f i e d

[ 19,

9, 4, 10, 11]

4 19

9 11

10

рор

4: 9 10

19

11

рор

9: 10 11

19

Для удаления существующих элементов и :Jамены и х новыми значениями в рамках одной операции используйте метод heapreplace ( ) . Листинг 2. 5 1 . heapq_heapreplace . py impo r t heapq f rom heapq_showt ree i mpo r t show t ree f rom heapq_heapda t a import da t a

1 37 heapq . heapify ( data ) print ( ' s t a r t : ' ) show_t r e e ( da t a ) f o r n in [ О , 1 3 ) : sma l l e s t heapq . heaprep l a ce ( da t a , n ) pr i nt ( ' repl a ce ( : >2 } w ith ( : >2 } : ' . forma t ( sma l l e st , n ) ) show_t ree ( da t a ) �

Замена элементов н а месте делает возможным поддержание постоянного раз­ мера кучи, как, например, в случае очереди заданий с приоритетом. $ pyt hon3 heapq_heaprep l a c e . py

start : 4

19

9 10

11

4 with

replace

О: о

9

19

10

11

replace

О wi th 1 3 : 9 10

13

19 11

2 .4.4. Получение наибольших и наименьших элемен тов кучи Модуль heapq предоставляет две функции , позволяю щ ие находить диапазоны наибольших и наименьших значений, содержащихся в итерируемом объекте. Листинг 2.52. hea�extrem.es . py import heapq f rom he apq_heapda ta import da t a p r i nt p r i nt p r i nt print print

( ( ( ( (

' al l : ' 3 l a rg e s t : ' f rom sort : ' 3 sma l l e s t : ' f rom sort :

' ' ' ' '

, , , , ,

da t a ) heapq . n l a rg e s t ( 3 , da t a ) ) l i s t ( reve rsed ( s o r t e d ( da ta ) ( - 3 : ) ) ) ) heapq . n sma l l e s t ( 3 , da ta ) ) s o r t e d ( da t a ) ( : 3 ) )

Г11881 2 . Сfрукtур ы АВННЫХ

1 38

Исполыование функций nlarge s t ( ) и n sma l l e s t ( ) наиболее эффективно лишь при относительно небольших значениях п > 1, однако в некоторых ситуаци­ ях они могут быть весьма кстати. $ pythonЗ heapq_ext reme s . py all 3 l a rg e s t f r om s o r t 3 sma l l e s t : f r om s o r t :

2 .4. 5 .

[ 1 9, [19, [ 1 9, [4, [4,

9, 4 , 10, 11] 11, 10] 11, 10] 9, 10] 9, 10]

Эффективное слияние отсортированных последовательностей

В случае небольших наборов данных объединение двух отсортированных по­ следователыюстей в одну не составляет большого труда. l i s t ( s o r t e d ( i t e r t o o l s . cha i n ( * da t a ) ) )

Однако в случае крупных наборов данных этот прием может потребовать ис­ пользования больших объемов памяти. Вместо сортировки всей объединенной последовательности метод me rge ( ) использует кучу для генерации новой после­ довательности по одному элементу за раз, используя для опре�еления о чередного элемента память фиксированного объема. Листинг 2.53. heapq_merge . ру imp o r t heapq impo rt random random . seed ( 2 0 1 6 ) da t a [] f o r i i n range ( 4 ) : new_da t a l i s t ( ra ndom . samp l e ( r a n g e ( l , 1 0 1 ) , 5 ) ) new_da t a . s o r t ( ) data . append ( new_da t a ) =

=

f o r i , d i n enume r a t e ( da t a ) : p r i nt ( ' { } : { } ' . f o rmat ( i , d ) ) p r i n t ( ' \nMe rge d : ' ) f o r i i n he apq . me rge ( * da t a ) : p r i n t ( i , end= ' ' ) print ( )

Поскольку реализация метода me rge ( ) использует кучу, расход памяти зависит от количества объединяемых последователыюстей, а не от количества элементов в каждой из них.

1 39 $ pytho n 3 heapq_me rge . py О: 1: 2: 3:

[ 33 , 58 , 7 1 , [ 10, 11, 17, [ 13, 18, 39, [20, 27 , 31,

88, 38 , 61, 42,

95] 91] 63] 45]

Merged : 1 0 1 1 1 3 1 7 1 8 2 0 2 7 3 1 3 3 3 8 3 9 4 2 4 5 58 6 1 6 3 7 1 8 8 9 1 9 5 Дополнительные ссылки •

Раздел документации стандартной библиотеки, посвященный модул ю heapq



Википедия :



Раздел отеки.

Куча (структура данных)1 5 •

2.6.3.

14



Об щее описание структур данных типа кучи.

Реализация очередей с приоритетом из класса Queue стандартной библ и­

2 . 5 . Ьi sect: поддержание сорти рованного состоя ния списков Модуль Ьisect реализует алгоритм вставки элементов в список, обеспечиваю­ щий подцержание сортированного состояния списка.

2 . 5 . 1 . Сортировка при вставке Ниже приведен простой пример, в котором список сортируется при вставке элементов с помощью метода insort ( ) . Листинr 2.54. Ьisect exaшple . ру _

import Ь i s ect # Последо в атель ность случайных чисел va l u e s = [ 1 4 , 8 5 , 7 7 , 2 6 , 5 0 , 4 5 , 6 6 , 7 9 , 1 0 ,

3,

84, 77, 1)

p r i n t ( ' New Pos Content s ' ) pr i n t ( ' - - -------- ' ) = [ ] f o r i i n va l ue s : po s i t i on Ь i s e ct . Ь i se ct ( l , i ) Ьi s e ct . i n s o r t ( l , i ) pr i nt ( ' { : 3 } { : 3 } ' . forma t ( i , po s i t ion ) , 1 )

1

=

В первом столбце вывода отображается новое случайное число, а во втором позиция, в которую вставляется это число. Остальная часть каждой строки пред­ ставляет текущее состояние отсортированного списка. 1 4 https : / / docs . python . o rg / 3 . 5 / l ib r a r y /heapq . html 1 5 https : / / ru . wi k ipedia . org /wi k i / Kyчa_ ( cтpyктypa_дaнныx )

Гм• 2. Сrрукrуры AВНHlllX

140 New

Pos

14 85 77 26 50 45 66 79 10 3 84 77 1

Cont e n t s

- - - - - - --

о [ 14 ] 1 [14, 1 [ 14, 1 [14, 2 [14, 2 [ 14 , 4 [ 14 , 6 [14, о [ 10, о [3, 9 [3, 8 [ 3, о [1,

85] 77' 85] 26, 77 , 8 5 ] 2 6 , 50, 7 7 , 8 5 ] 2 6 , 4 5, 50 , 77 , 8 5 ] 2 6 , 4 5, 50 , 6 6 , 7 7 , 8 5 ] 26, 45, 50, 66, 77 , 7 9, 85] 14, 26, 45, 50, 66, 77, 79, 85] 10, 14, 26, 45, 50, 66, 77, 79, 85] 10, 14, 26, 45, 50, 66, 77' 79, 84 , 85] 10, 14, 26, 45, 50, 66, 7 7 , 7 7 ' 79, 8 4 , 85] 3, 10, 14 , 26, 4 5, 50, 66, 77 , 77' 79, 8 4 , 8 5 ]

Это довольно простой пример. В действительности при небольших объемах данн ых быстрее и проще создать список и отсортировать его. Однако в случае длинных списков алгоритм сортировки при вставке обеспечивает значительную экономию времени и памяти , особенно если опе р ация сравнения �вух элементов списка требует проведения интенсивных вычислений.

2.5.2.

Обработка повторяющихся значений

Представленный перед этим результирующий набор включает повторяющее­ ся значение ( 77). Модуль Ь i s e c t обеспечивает два способа обработки повторяю­ щихся значений: новые значения могут вставляться либо слева от существующих, либо справа. В действительности функция insort ( ) является псевдонимом функ­ ции insort_ r igh t ( ) , которая всгавляет элемент после существующего значения. Соответствующая ей функция insort_le ft ( ) вегавляет элемент перед существу­ ющим значением. Листинг 2.55. Ьisect_example2 . ру import Ь i s e c t # По следов а тель ность случай ных чисел va l u e s [ 1 4 , 8 5 , 7 7 , 2 6 , 50 , 4 5 , 6 6 , 7 9 , =

10, 3, 84, 77, 1]

p r i n t { ' New Pos Contents ' ) print ( ' - --------- ' ) # Исполь зуются функции Ьi sect_l e ft ( ) и i n so rt _l e f t ( ) 1 = [] for i i n va l ue s : pos i t ion Ьi sect . Ь i sect l e f t ( l , i ) Ь i s e c t . i ns o r t_ l e f t ( l , i ) p r i n t { ' { : 3 } { : 3 } ' . fo rma t ( i , po s i t i on ) , 1 ) =

Применение функций Ь i sect _ l e f t ( ) и insort_ l e ft ( ) приводит к тому же результирующему отсортированному списку, но с другими позициями для повто­ ряющихся значений.

2.6. queue: nсmжобеэоП11Сt111t1 реамааЦ1U1 очерttАИ New

Pos

14 85 77 26 50 45 66 79 10 3 84 77 1

FIFO

1 41

Contents

-------о [14]

1 1 1 2 2 4 6 о

о

9 7 о

[14, [ 14, [ 14, [14, [14, [ 14 , [14, [ 10, [3, [3, [3, [1,

85] 77, 85] 26, 77, 85] 2 6 , 50 , 7 7 , 8 5 ] 26, 45, 50 , 7 7 , 8 5 ] 26, 4 5, 50, 66, 77 , 85] 26, 45, 50, 66, 77 , 79, 8 5] 14 , 2 6 , 4 5, 50, 66, 7 7 , 7 9 , 8 5 ] 1 0 , 1 4 , 2 6 , 4 5 , 50 , 66 , 7 7 , 7 9, 8 5] 10, 1 4 , 26, 4 5, 50, 66, 7 7 , 79, 8 4 , 8 5 ] 1 0 , 1 4 , 2 6 , 4 5, 50, 66, 7 7 , 77, 7 9, 8 4 , 8 5] 3 , 10, 14 , 2 6 , 45, 5 0 , 6 6 , 7 7 , 7 7 , 79, 8 4 , 85]

Дополнительные ссылки •

Раздел документации стандартной библиотеки, посвя щенный модулю Ь i s e c t 1 6 •



Википедия:

Сортировка вставками1 7 .

Описание ал горитма сортировки при вставке.

2.6. queue: потокобеэопасная реализация очереди FI FO Модуль queue предоставляет структуру данных с дисциплиной обслуживания FI FO (first iп, first out - " первым пришел - первым ушел") , пригодную для мно­ гопоточ1ю1·0 программирования. Ее можно использовать для безопасно1·0 обмена данными между 1ю·гоками-11роизводителями и потоками-потребителями. Для вы­ зывающе1·0 кода предусмотрена обработка блокировок, поэтому с одни м экзем­ пляром Queue может безопасно работать несколько потоков. Размер экземпляра Queue (количс(:тво содержащихся в нем элементов) можно ограничивать. Примечание В ходе дальнейшего обсуждения предполагается, что читатель у.же знаком с понятием очереди. Если это не так, рекомендУется предварительно изучить эту тему, воспользовав­ шись другими доступными источниками, прежде чем продол.жить чтение.

2.6 . 1 .

Базовая очередь FIFO

Класс Queue реализует базовый контейнер, работающий по принципу l r e f : r ( ) : de l e t i n g obj ( De l e t i ng < ma i n . Expens iveObj ect obj e c t at О х 1 0 0 7 Ы а 5 8 > ) r ( ) : None

2 . 8 . 2 . Функции обратного в ызова в слабых ссылках Конструктор re f поддерживает необязательный аргумент в виде ф ункции об­ ратного вызова, которая будет автоматически вызываться при удалении исходно­ го объекта. Лисrинг 2.65. weakref ref cal lback . ру _

_

impo r t weakre f c l a s s Expen s i veObj ect : def

del ( sel f ) : p r i n t ( ' ( De l e t i n g { ) ) ' . fo rma t ( s e l f ) )

de f c a l lback ( re f e rence ) : " " " Вызыв а е тся при удалении целе в о г о объе кта " " " p r i n t ( ' ca l lb a c k ( { ! r ) ) ' . fo rma t ( re f e rence ) ) obj Expe n s i ve Obj e ct ( ) r we a k re f . r e f ( obj , cal l b a c k ) =

=

pr i n t print pr i n t

( ' ob j : ' , obj ) ( ' re f : ' , r ) ('r() : ', r() )

p r i n t ( ' de l e t i ng obj ' ) de l obj print ( ' r ( ) : ' , r ( ) )

2.8. weakref: САВ6ые ССЫА1О1 на о6wнаы

1 53

Функция обратного вызова получает объект ссьшки в качестве аргумента, ког­ да ссьшка становится недействительной по причине удаления объекта. Одним из возможных применений этого средства является удаление объекта слабой ссылки из кеша.

$

pythonЗ wea kre f_ref_ca l lb a c k . py

obj : r e f : r ( ) : d e l e t i n g ob j ( D e l e t i ng ) c a l lba c k ( ) r ( ) : None

2 . 8 . 3. Завер ша ю щие операции при удалении объектов 1 lадежность управления ресурсами при удалении слабых ссылок можно по­ высить, связывая с объеКТ".ами функции обратного вызова при помощи функции fina l i z e ( ) . Экземпляр final i ze ( объек�инализатор) удерживается в памяти до тех пор, пока не будет удален связанный с ним объект, даже если в приложении отсутствуют ссылки на объек�инализатор. Листинг 2 .66. weakref final i z e . ру _

import we a k r e f

c l a s s Expe ns i veObj e c t : de f

del ( se l f ) : pr i nt ( ' ( De l e t i n g ( } ) ' . format ( s e l f ) )

d e f on_f i na l i z e ( * a rg s ) : print ( ' on _ f i na l i z e ( { ! r } ) ' . forma t ( a rgs ) ) obj Expe n s i veObj ec t ( ) weakre f . f i n al i z e ( obj , on_f i na l i z e , =

' e xtra a rgument ' )

d e l obj

Аргументами функции fina l i z e ( ) являются отслеживаемый объект, вызы­ ваемый объект, подлежащий вызову при удалении объекта сборщиком мусора, и любые позиционные или именованные аргументы, передаваемые вызываемому объекту.

$

pythonЗ wea kref_fina l i z e . py

( De l et ing < ma i n . Expens i ve Ob j e c t ob j e c t a t Ох 1 0 1 9Ы 0 f 0 > ) on_ f i n a l i z e(( • ex t ra a r gume nt ' , ) )

1"11888 2 . Струкrур ы AllHHWX

1 54

Экземпляр fina l i z e имеет перезаписываемое свойство atex i t , позволяющее управлять вызовом функции обратного вызова при выходе из программы, если она до этого не вызывалась. Листинг 2 .67. weakref_finalize_atexi t . py

import s y s import we a k r e f c l a s s Expe n s i ve Obj ect : def

del ( sel f ) : p r i n t ( ' ( De l e t i n g { } ) ' . forma t ( s e l f ) )

de f on_f i na l i z e ( * a rg s ) : print ( ' on_ f i na l i z e ( { ! r } ) ' . fo rma t ( a rg s ) ) obj Expen s iveObj ect ( ) f wea kre f . f i na l i ze ( ob j , on_ f i n a l i z e , f . atexit bool ( i nt ( s ys . a r gv [ l ] ) ) =

=

' ex t ra a rgument ' )

=

Поведению по умолчанию соответствует вызов функции обратного вызова. Это поведение можно изменить, задав для свойства а texi t ложное значение.

$

pythonЗ we a k r e f_f i n a l i z e_a t ex i t . py 1

on_ f i na l i z e ( ( ' extra a rgument ' , ) ) ( De l e t ing < ma i n . Expe n s i veObj ect ob j e ct a t О х 1 0 0 7 Ы О f 0 > )

$

pytho n З we a kr e f_f i n a l i ze_a t e x i t . py О

Предоставление экземпляру fina l i z e ссылки на объект приводит к тому, что она удерживается в памяти , поэтому сборщик мусора не удалит данный объект. Листинг 2.68. weakref final ize reference . ру _

_

import g c imp o r t wea kre f c l a s s Expens iveOb j ect : def

de l ( se l f ) : p r i nt ( ' ( De l e t i n g { } ) ' . f o rma t ( s e l f ) )

d e f on_ f i n a l i z e ( * a rg s ) : p r i n t ( ' on_f i n a l i z e { { ! r } ) ' . f o rma t ( a r g s ) ) obj = Expe n s i veObj e ct ( ) obj i d i d ( obj ) =

f wea kre f . f i na l i z e ( ob j , on_ f i na l i z e , obj ) f . atexit Fa l s e =

=

2.8. wellkret. CA86we ca.woc не о6ыtк1ы

1 55

de l obj for

о i n gc . get obj e c t s ( ) : i f i d ( o ) == o b j _i d : pr int ( ' found uncol l e c t e d obj e c t in g c ' )

К.'\к следует из этого примера, объект obj сохраняется в памяти даже после удаления прямой ссылки на него и остается видимым для сборщика мусора по­ средством объекта-финализатора f.

$

pytho n З wea kre f_f i na l i ze_re ference . py

found unco l lected obj e c t in gc

Использование связанно1·0 метода отслеживаемого объекта также может пре­ пятствовать надлежащему выполнению завершающих операций по освобожде­ нию системных ресурсов. Листинг 2 .69. weakref final i ze reference_method . ру _

_

impo rt gc impor t wea kre f

c l a s s Expe n s i veObj e c t : de f �de l � ( s e l f ) : pr int ( ' ( De l e t ing { } ) ' . f ormat ( se l f ) ) de f do_f i na l i z e ( s e l f ) : print ( ' do_f i na l i ze ' )

obj Expensi veObj e c t ( ) obj _ i d i d ( obj ) =



f we a kre f . f i na l i z e ( obj , obj . do_f i n a l i z e ) f . atexit Fa l s e =

=

de l obj for

о

i n gc . ge t_ob j e c t s ( ) : i f i d ( o ) == obj_i d : pr int ( ' found uncol lected obj e c t i n g c ' )

Поскольку вызываемым объектом, передаваемым функции fina l i ze ( ) , явля­ ется связанный метод экземпляра obj , объект-финализатор удерживает ссылку на obj , который поэтому не может быть удален и обработан сборщиком мусора.

$

pythonЗ we a k r e f_f i na l i ze_re ference_me t hod . py

found unco l l e c t e d ob j e ct in g c

rмва 2 . Структуры АВННЫХ

1 56

2 . 8 .4. Прокси-объекты Иногда вместо слабой ссылки удобнее использовать пртсси-обикт (или просто npmccu) . Прокси-объект ведет себя так, как если бы это был исходный объект, но он не должен вызываться до тех пор, пока не станет до('тупным объект, который он представляет. Вследствие этого прокси-объект можно передать библиотеке, которой не известно, что она получает ссылку вместо реального объекта. Листинг 2. 70. weakrefyroxy . py import we a k r e f

c l a s s Expe n s iveOb j ect : de f

init ( s e l f , name ) : s e l f . name = name

de f

de l ( se l f ) : p r i n t Т' ( De l e t i n g { } ) ' . f o rma t ( s e l f ) )

Expe ns iveObj ect ( ' My Obj e c t ' ) obj r weakre f . re f ( obj ) р we a kre f . proxy ( ob j ) =

=

p r i n t ( ' vi a p r i n t ( ' vi a p r i n t ( ' vi a del obj p r i n t ( ' vi a

obj : ' , obj . name ) r e f : ' , r ( ) . n ame ) proxy : ' , p . name ) proxy : ' , p . name )

В случае обращения к прокси-объекту после удаления объекта, который он представляет, возбуждается исключение ReferenceError.

$

pythonЗ weakre f_prox y . py

v i a obj : Му Obj e ct v i a r e f : Му Obj e ct v i a proxy : Му Ob j e ct ( De l e t i n g ) Tra ceba c k ( most r e c e n t ca l l l a s t ) : Fi l e " we a kre f_proxy . py " , l i ne 3 0 , in p r i n t ( ' vi a prox y : ' , p . name ) Re f e r en ce E r ror : w e a k l y - r e fe r e n ced obj e ct no l o n g e r e xi s t s

2 .8 . 5. Объекты кеширования Классы ref и proxy считаются "низкоуров11е11ыми". И хотя они удобны для ра­ боты со слабыми ссылками на отдельные объекты и позволяют избежать проблем с автоматическим удалением объектов, связанных циклическими ссылками , сбор­ щиком мусора, классы Wea kKeyDi c t i ona ry и WeakVa lueDic t i onary предоставля­ ют более подходящий API для со:щания кеша из нескольких объектов.

2.8. weaklef: см6ые ССЫАКМ на объекrы

1 57

Класс WeakValueDi c t ionary позволяет создать словарь, в котором значения представлены слабыми ссылками и могут автоматически удаляться сборщиком мусора, если они больше не используются другим кодом. Ниже приведен при­ мер, в котором явные вызовы сборщика мусора используются для демонстра­ ции различий между управлением памятью в случае обычного словаря и словаря WeakVa lueDi c tionary. Листинг 2.7 1 . weakref_valuedict . py iтport g c froт pp r i п t iтpo r t ppr iпt iтport we a kr e f gc . s e t_debug ( gc . DEBUG_UNCOLLECTABLE ) c l a s s Expe п s i veObj ect : de f

iпit ( s e l f , пате ) : s e l f . пaтe пате =

de f

repr ( se l f ) : r e t u r п ' Expeп s i veObj ect ( { } ) ' . fo rтat ( se l f . пame )

de f

de l ( sel f ) : priпt ( ' ( De l e t i п g { } ) ' . fo rma t ( s e l f ) )

de f deтo ( ca che_fa c t o r y ) : # Удержать объе кты для тог о , чтобы предотвратить # неме дле нное удаление слабых ссыл о к a l l_r e f s = { } # Создать кеш , исполь зуя фун кцию-фабрику p r i п t ( ' CACHE ТУР Е : ' , ca che_factory ) ca che = cache_f a c t o r y ( ) f o r пате i п [ ' опе ' , ' two ' , ' t h r ee ' ] : о = Expe п s i ve Ob j ect ( пaтe ) cache [ пame ] о a l l r e f s [ пame ] = о de l о # умен ь шить сче тчик ссылок =

a l l_re fs = ' , е пd= ' ' ) priпt ( ' ppr i п t ( a l l_re f s ) p r i пt ( ' \ п B e f o re , cache coпt a i п s : ' , l i s t ( cache . ke ys ( ) ) ) f o r пате , va lue i п cache . i t eтs ( ) : priпt ( ' { } = { ) ' . fo rтa t ( пame , value ) ) de l value # уменьшить сче тчик с сылок # Удалить все ссылки на объе кты, за исключ е нием т е х , # которые находятс я в кеше p r i п t ( ' \ п C l e a пup : ' ) de l a l l r e f s g c . col l e c t ( )

p r i n t ( ' \ n Af t e r , cache cont a i n s : ' , l i s t ( ca che . ke ys ( ) ) ) f o r name , va l u e i n ca che . i t ems ( ) : print ( ' {} { } ' . forma t ( name , va l ue ) ) print ( ' demo r e t u r n i n g ' ) return =

demo ( di c t ) print ( ) demo ( we a kre f . We a kVa l ue Di c t i o na r y )

Используемые в циклах for переменные, ссылающиеся н а кешируемые зна­ чения , должны удаляться явным образом, что и обусJiовливает необходимость уменьшения счетчиков ссылок на соответствующие объекты. Если этого не сде­ лать, то сборщик мусора не сможет удалить объекты, и они будут оставаться в кеше. Переменная a l l_re f s используется для сохранения ссылок с той целью, чтобы они не были преждевременно удалены сборщиком мусора. $ pythonЗ we a kr e f_va l uedi ct . py

САСНЕ ТУРЕ : < c l a s s ' d i ct ' > a l l_r e fs = { ' one ' : Expe n s i veObj e ct ( on e ) , ' three ' : Expe n s i ve Obj e c t ( th re e ) , ' two ' : Expe n s i veObj e c t ( two ) } Be f o r e , cache cont a i ns : [ ' two ' , ' one ' , two Expe n s i veObj ect ( two ) one = Expens i veObj e c t ( one ) three Exp e n s i veObj ect ( t h r e e )

' th r e e ' )

=

C l e a nup : Aft e r , cache cont a i n s : [ ' two ' , ' one ' , two Expe n s i veObj ect ( two ) one Expen s i veObj e c t ( one ) three Expe n s i veObj ect ( thr e e ) demo r e t u r n i n g ( De l e t ing Expens i veObj e c t ( two ) ) ( De l e t i ng Expens i veObj e c t ( on e ) ) ( De l e t i ng Expe n s i ve Obj e c t ( th r e e ) )

' th r e e ' )

= =

=

САСНЕ ТУРЕ : < c l a s s ' we a kr e f . We a kVa l ue D i c t i ona ry ' > a l l_r e f s { ' one ' : Expe n s i veObj e c t ( one ) , ' three ' : Expe n s i veObj e c t ( t h r ee ) , ' two ' : Exp e n s i ve Obj e c t ( two ) } =

Be f o r e , cache conta i n s : [ ' two ' , ' one ' , two = Expe n s i veObj e c t ( two ) one Expe n s i veObj e c t ( one ) Expens i veObj e c t ( th re e ) three =

C l e anup :

' th r e e ' ]

2.9. сору: C03AllH М8 дуб/lм КВТОВ о6ьектов

1 59

( De l e t i ng Expe n s i v e Obj e c t ( two ) ) ( De l e t i n g Expe n s i v e Obj e c t ( one ) ) ( De l e t i ng Expe n s i ve Ob j e ct ( t h re e ) ) Afte r , cache conta i n s : demo r e t u r n i n g

[)

П редупреждение

В разделе документации стандартной библиотеки, посвященном модулю w e a kr e f, содер­ жится следующее предостережение:

"Поскольку словарь We a kVa l u e D i c t i o n a r y создается поверх словаря Pyt­ hoп, его размер не должен изменяться при итерировании по элементам. Добиться га рантированного выполнения этого условия для словаря w e a k Va lue o i c t i onary неп росто, поскольку действия програ ммы при выполне­ нии итераций могут п риводить к "загадочному" исчезновению элементов (обусловленному побочными эффектами сборки мусора)". Дополительные ссылки •

24

Раздел документации стандартной библиотеки, посвященный модулю we a kr e f .



gc (раздел 1 7. 6). Модуль gc предоставляет интерфейс для доступа к средствам сборки мусора, предлагаемым интерпретатором.



РЕР 20525. Weak References. Предложения по улучшению слабых ссылок.

2.9.

сору:

создание дубликатов объектов

Модуль сору вклю•1ает две функции , сору ( ) и deepcopy ( ) , предназначенные для создания дубликатов существующих объектов.

2. 9 . 1 . Мелкие копии Функция сору ( ) создает мел:кую (поверхностную) ·копию объекта, которая пред­ ставляет собой новый контейнер, заполненный ссылками на содержимое исход­ ного объекта. Когда создается мелкая копия объекта l i s t , конструируется новый объект l i s t , к которому присоединяются элементы исходного объекта. Листинг 2. 72. сору_ sha 1 1 o w . р у impo r t сору impo rt func t o o l s @ funct o o l s . t o t a l _o rde r i n g c l a s s MyCl a s s : de f

init ( s e l f , name ) : s e l f . name narne =

24 25

ht tps : / / docs . python . o rg / 3 . 5 / l ibra ry/weakref . html www . python . o rg /dev / peps /pep- 0 2 0 5

180 de f �e q__ ( se l f , o the r ) : r e t u rn se l f . narne othe r . narne ==

de f �gt� ( se l f , o the r ) : return se l f . n arne > othe r . narne

MyC l as s ( ' а ' ) а rny_ l i s t [а] dup c opy . copy (rny_l i s t ) =

=

=

print print print print print print

( ( ( ( ( (

rny_l i s t : ' ' dup : ' dup i s rny_l i s t : ' dup == rny_l i s t : ' dup [ O ] i s rny_ l i s t [ O ] : ' dup [ O ] rny_l i s t [ O ] : ==

' , rny_l i s t ) ' , dup ) ' , ( dup i s rny_l i s t ) ) ' , ( dup rny_l i s t ) ) ' , ( dup [ O ] i s rny_l i s t [ O ] ) ) ' , ( dup [ O ] rny_l i s t [ O ] ) ) ==

==

В случае мелкого копирования дубликат экземпляра MyClas s не создается, по· этому ссылка, хранящаяся в переменной dup, указывает на тот же объск1� что и ссылка, хранящаяся в переменной my l i s t . _

$ pytho n З copy_sha l l ow . py rny_ l i s t : dup : dup i s rny_l i s t : dup rny_l i s t : dup [ O ] is rny_l ist [ O ] : dup [ O ] rny_ l i st [ O ] : ==

==

[ ] [ ] Fa l s e T rue T rue T rue

2.9 . 2. Глубокие копии Функция deepcopy ( ) создает г.лубаку'Ю ·к onu'IO объекта, которая представляет собой новый контейнер, заполненный копиями содержимого исходного объек­ та. Когда создается глубокая копия объекта l i s t , конструируется новый объект l i s t , а затем копируются элементы исходного списка, после чего эти копии при­ соединяются к новому списку. Замена выаова сору ( ) вызовом deepcopy ( ) приводит к результату, существен· но отличающемуся от предыдущего. Лисrинг 2.73. copy_deep . py irnport сору irnpo rt funct o o l s @ func t o o l s . t ot a l_o rde r i ng c l a s s MyCl a s s : de f

init ( se l f , narne ) : s e l f . narne narne =

:l.9. cow. СQ3А8НИе ду6Амкатое объекюв

181

def � e � ( s e l f , othe r ) : r e t u rn s e l f . name == o t he r . name def � gt� ( s e l f , othe r ) : r e t u rn s e l f . name > o t he r . name

а

= MyC l a s s ( ' а ' ) n y_ l i s t = [ а ] ::l ·up = copy . deepcopy (my_l i s t )

p rint p ri n t p ri n t p ri n t print p rint

( ( ( ( ( (

' my_l i s t : ' dup : ' dup i s my_l i s t : ' dup == my_ l i s t : ' dup [ O ] i s my_ l i s t [ O ] : ' dup [ O ] == my_l i s t [ O ] :

' ' ' ' ' '

, my_l i s t ) , dup ) , ( dup i s my_l i s t ) ) , ( dup == my_l i s t ) ) , ( dup [ O ] i s my_l i s t [ O ] ) ) , ( dup [ O ] == my_l i s t [ O ] ) )

Первый элемент списка уже не является ссылкой на тот же объект, но сравне· 11ие обоих объектов между собой показывает, что они равны. $ pythonЗ copy_deep . py

dup dup ::l up [ O ] is ::l up [ O ] ==

my l i s t : _ dup : i s my l i s t : _ = = my l i s t : _ my_ l i s t [ O ] : my l i s t [ O ] : _

[ < ma i n . MyC l a s s ob j e ct a t О х 1 0 1 8 а 8 7 Ь8 > ] � � [ ] Fa l s e T rue Fa l s e T rue

2 . 9.3 . Настройка копирования Процессами копирования можно управлять с помощью специальных методов сору_ ( ) и _deepcopy_ ( ) . __





Метод _сору_ ( ) вызывается без ар1ументов и должен возвращать мел· кую копию объекта. Метод _deepcop y_ ( ) вызывается с передачей ему вспомогательного сло­ варя и должен возвращать 1-лубокую копию объекта. Для управления рекур· сией каждый атрибуг, нуждающийся в глубоком копировании, должен 11ере­ дава1ъся функции сору . deepcopy ( ) вместе со вспомогательным словарем. (Далее вспомогательный словарь обсуждается более подробно.)

В приведенном ниже примере показано, как должны вызываться :iти методы.

П истинг 2.74. copy_hooks . ру

i mpo r t сору i mport funct o o l s

@ func t oo l s . t o t a l _orde ring cl a s s MyC l a s s :

rмва 2. Структуры А8ННЫх

182 def

init ( se l f , name ) : s e l f . name = narne

def _e q__ ( s e l f , othe r ) : othe r . n ame return s e l f . narne ==

de f _ g t ( s e l f , othe r ) : _ return s e l f . name > othe r . name de f _copy ( se l f ) : _ print ( ' _сору_ ( ) ' ) return MyC l a s s ( s e l f . name ) de f

а

=

sc dc

deepcopy ( s e l f , memo ) : print ( ' deepcopy_ ( ( ) ) ' . f o rrnat ( memo ) ) _ r e t u rn MyC l a s s ( copy . deepcopy ( s e l f . name , memo ) )

MyC l a s s ( ' а ' ) сору . сору ( а ) copy . deepcopy ( a )

Вспомогательный словарь (memo ) используется для отслеживания уже скопи­ рованных значений, чтобы избежать бесконечной рекурсии. $

pythonЗ copy_ h o o ks . py

_

сору () deepcopy _ ( ( ) )

2 . 9. 4. Рекурсия при глубоком копировании Чтобы избежать проблем с кuнированием рекурсивных структур данных, метод deepcopy ( ) использует вспомогательн ый словарь, с помощью кото1ю1·0 отслеживает уже скопированные данные. Этот словарь передается методу deepcopy ( ) , чтобы тот также мог просматривать его содержимое. В следующем примере показано, каким образом связанная структура данных, такая как направленный граф, может помочь в защите от рекурсии путем реализа­ ции метода _deepcopy_ ( ) . __

_

Листинг 2.75. copy_recursion . py impo r t сору class G raph : def

init ( s e l f , name , conne c t i ons ) : s e l f . name name s e l f . connec t i ons conne c t i on s =

=

de f a dd_ conn e ct i on ( s e l f , othe r ) : s e l f . connect i ons . appe nd ( o t he r )

2.9. сору: соwнме

� о6ьекrов

1 83

def

repr� ( s e l f ) : � re t u rn ' G raph ( name= { } , i d= { } ) ' . fo rrna t ( se l f . name , i d ( se l f ) )

de f

deepcopy ( s e l f , memo ) : p r i n t ( ' \ nCa l l ing deepcopy� for { ! r } ' . fo rmat ( s e l f ) ) � i f s e l f in memo : exi s t i ng = memo . get ( s e l f ) print ( ' Al ready cop i e d to { ! r } ' . f o rmat ( e x i s t i n g ) ) ret urn e x i s t i n g p r i n t ( ' Memo d i c t ionary : ' ) i f memo : for k, v in memo . i t ems ( ) : p r i nt ( ' { } : { } ' . f o rmat ( k, v ) ) else : ( empt y ) ' ) print ( ' dup G raph ( c opy . deepcopy ( s e l f . n ame , memo ) , [ ] ) p r i n t ( ' Copy ing to new obj e c t { } ' . fo rma t ( dup ) ) memo [ s e l f ] dup f o r с in s e l f . connect i o n s : dup . add_connect i o n ( copy . deepcopy ( c , memo ) ) r e t u rn dup =

=

1:oot = G raph ( ' root ' , [ ] ) = G raph ( ' a ' , [ roo t ] ) Ь = G raph ( ' b ' , [ а , roo t ] ) r o o t . add conne c t i on ( a ) root . add connect i on ( b ) а

=

dup

=

copy . deepcopy ( root )

Класс G r ap h включает ряд базовых методов ориентированного граф а. Экзем11ляр может быть инициализирован именем и списком существующих узлов, с ко­ торым свя:mно данное имя. Метод add connect ion ( ) используется для установ­ ления двунаправленных связей. Он также используется оператором глубокого копирования. _

Рис. 2.1. Глубокое копирование объекrа циклического графа

Метод deepcopy ( ) выводит сообщения, информирующие о его вызовах, осуществляет необходимое управление содержимым вспомогательного слова_

и

_

Гl\888 2. Сrруктуры AllHHЫX

1 64

ря. Вместо копирования всего списка он создает новый снисок и присоединяет к нему копии индивидуальных связей. Это гарантирует обновление вспомогатель­ ного словаря всякий раз, когда создается дубликат нового узла, и позволяет избе­ жать проблем с рекурсией или лишними копиями узлов. Как и перед этим, дан­ н ый метод возвращает копию объек-га. Представленн ы й на рис. 2. 1 граф включает несколько циклов, но обработка рекурсии с помощью вспомогательного словаря предотвращает переполнение стека при обходе узло1J. Ниже представлен вывод, получаемый при копировании корневого узла. $ python3 copy_r e cur s i o n . py Ca l l i n g �deepcopy� f o r Graph ( name=root , id=4 3 1 4 5 6 9 5 2 8 ) Memo d i c t i ona r y : ( empt y ) Copying t o new obj e c t G raph ( name=r o o t , i d= 4 3 1 5 0 9 3 5 92 ) Ca l l i n g �de epcopy� for Graph ( name=a , id= 4 3 1 4 5 6 9 5 8 4 ) Memo d i c t i ona r y : G raph ( name = r o o t , id=4 3 1 4 5 6 9 5 2 8 ) : Graph ( name= root , id=4 3 1 5 0 9 3 5 9 2 ) Copying to new obj ect G r aph ( name=a , id= 4 3 1 5 0 9 4 2 0 8 ) Ca l l i ng de epcopy� for Graph ( name= root , i d= 4 3 1 4 5 6 9 5 2 8 ) � Al ready copied to Graph ( name=roo t , i d= 4 3 1 5 0 9 3 5 9 2 ) Ca l l i n g �de epcopy� f o r G raph ( name=b , id=4 3 1 5 0 9 2 2 4 8 ) Memo d i c t i ona r y : 4 3 1 4 5 6 9 5 2 8 : G raph ( name=r o o t , id= 4 3 1 5 0 9 3 5 9 2 ) 4 3 1 5 6 9 2 8 0 8 : [ G raph ( name= r o o t , i d= 4 3 1 4 5 6 9 5 2 8 ) , Graph ( name=a , id=4 3 1 4 5 6 9 5 8 4 ) ] G raph ( name=ro ot , id=4 3 1 4 5 6 9 5 2 8 ) : Graph ( name=root , i d= 4 3 1 5 0 9 3 5 9 2 ) 4 3 1 4 5 6 9 5 8 4 : G raph ( name=a , id= 4 3 1 5 0 9 4 2 0 8 ) G raph ( name=a , id= 4 3 1 4 5 6 9 5 8 4 ) : Graph ( name=a , id=4 3 1 5 0 9 4 2 0 8 ) Copying to new obj e c t Graph ( name=b , i d= 4 3 1 5 1 7 7 5 3 6 )

Если при копировани и какого-либо узла корневой узел встречается второй раа, то метод deepcopy ( ) обнаруживает рекурсию и повторно ис пользует су­ щестнующее значение из 1к1юмогателыюго словаря, а не создает новый объек1� _

_

Дополнительные ссылки •

Раздел документации стандартной библиотеки, посвященный модулю

сору26.

2. 1 0. pprint: " красивая печать " структур данных Модуль pprint содержит средства "красивой печати", обес печивающие при­ влекательный внешний вид выводимого представления структур данных. Форма26

h t t ps : / / docs . python . o r g / 3 . 5 / l i bra r y / copy . html

1 85

·гировщик со:щает представления структур данных, пригодные для анализа ин· терпретатором и удобные для чтения человеком. По возможности вывод 01·ра· tшчивается одной строкой и выделяется отступами, если рас11ростра11яется на несколько строк. Во всех приведенных ниже примерах исполь3}'ются данные из модуля pprint_ da ta . py . .Листинг 2.76. pprint_data . py da ta

=

[

( 1, (2, ( 3, (4, ( 5,

{ ,а, { ,е, 'i1 [ m' [ 'о' ['r' '

2. 1 0. 1 .

: : : , ' '

'А' 'Е' ,I, 'n'

, ' , ])

'Ь' : 1f' : 1j1 :

'В' '

' F' '

'

с

'

:

'g' :

'С' , 'G' '

'd' : 'h' : 11' :

'J' ' 'k' : 'К' ' ' 'р' ' 'q' ] ) , ' s ' ' t ' 'u' , 'v' , 'х' , ' у' ' ,

D }) 'Н' ' 'L' }) ' '

'

'

1z1 ] ) '

Вывод на консоль

Доступ к простейшим возможностям модуля ppr i n t обеспечивае·г 11е11осред· ственное исполь:юванис функции ppr in t ( ) . Листинг 2 .77. pprint_pprint . py f r om ppr i n t import ppr i n t from ppr i nt_data import d a t a print ( ' PRINT : ' ) print ( da t a ) print ( ) print ( ' P PRINT : ' ) ppr in t ( da t a )

Функция ppr int ( ) форматирует объект и записывает его в поток данных, п _narne_ : rnyfunc c a l l e d rnyfunc w i t h : ( ' а ' , 3 ) pa r t i a l wi th narned defau l t : obj e c t : functool s . part i a l ( < func t i on rnyfunc at Ох 1 0 0 7 а б а 6 0 > , Ь= 4 ) func : < func t i on rny func a t О х 1 0 0 7 а ба 6 0 > args : ( ) keywords : { ' Ь ' : 4 } c a l l e d rnyfunc wi t h : ( ' pas s i ng а ' , 4 ) ca l l ed rnyfunc wit h : ( ' ove r r i de Ь ' , 5 ) pa r t i a l wi t h defaul t s : obj e c t : functoo l s . pa r t i a l ( < func t i on rnyfunc at О х 1 0 0 7 а б а 6 0 > , ' de f a u l t а ' , Ь= 9 9 ) func : a rgs : ( ' defaul t а ' , ) keywords : { ' Ь ' : 9 9 } c a l l e d rnyfunc wit h : ( ' de fault а ' , 9 9 ) ca l l ed rnyfunc wit h : ( ' default а ' , ' ov e r r i de Ь ' ) I ns u f f i c i ent a r gurne nt s : Гraceback ( rno s t recent ca l l l a s t ) : F i l e " S : \ Pr o j e c t s \ py\ t e s t . py" , l i ne 4 2 , i n pl ( ) Г ypeE r r o r : rnyfunc ( ) rni s s i n g 1 requ i r e d pos i t i ona l a rgurnent :

'а'

3. 1 . 1 .2. Копирование и добавление свойств функции По умолчанию объект partial не имеет атрибутов _name_ и _doc_, и их отсутствие затрудняет отладку декорированных функций. Функция update_ wrapper ( ) позволяет добавлять и копировать атрибуты из исходной функции в объект part ial. Л истинг 3.2. functool s upda te wrapper . ру _

_

irnport fun c t o o l s

def rny func ( a , Ь=2 ) : " Do cs t r i n g for rny func ( ) . " print ( ' c a l l e d rnyfunc wi t h : ' ,

(а, Ь) )

ГА8В8 З. Аморм1мы

174 d e f show_de t a i l s ( name , f ) : " Показать детали вызываемо г о о бъекта . " print ( ' { } : ' . fo rma t ( name ) ) print ( ' obj e ct : ' , f ) p r i nt ( ' name : ' , e nd= ' ' ) try: p r i n t ( f . name e x cept At t r ibuteEr r o r : name ) ) pr int ( ' ( no print ( ' doc ' repr ( f . doc ) ) print ( ) '

show_de t a i l s ( ' myfun c ' , my func ) p l = funct o o l s . pa rt i a l ( myfun c , Ь=4 ) show_de t a i l s ( ' raw wrappe r ' , p l ) p r i nt print print print

( ( ( (

' Upda t i n g wrappe r : ' ) ' a s s i gn : ' , funct o o l s . WRAPPER ASS IGNMENТ S ) ' updat e : ' , funct o o l s . WRAPPER U P DAT ES ) )

=

funct o o l s . upda t e_wrappe r ( p l , myfunc ) show_de t a i l s ( ' upd a t e d wrappe r ' , p l )

Значения по умолчанию атрибутов, добавляемых в функцию-обертку, опреде­ ляются с помощью константы WRAPPER_ASS I GNMENTS уровня модуля , тогда как значения по умолчанию, обновляющие соответствующие атрибуты функции­ обертки, - с помощью константы WRAP PER_UPDATES. $

pyt h o n З funct o o l s_updat e_wrappe r . py

my fun c : obj e c t : < funct i on my func at О х 1 0 1 8 а ба 6 0 > name : myfunc doc ' Do c s t r i n g for myfunc ( ) . ' raw wrappe r : obj e c t : funct oo l s . part i a l ( < funct i on myfunc a t О х 1 0 1 8 а ба б 0 > , Ь= 4 ) name : ( no _name_ ) ' pa r t i a l ( func , * a r g s , * * keywords ) - new funct i o n w i th doc part i a l app l i ca t i o n \ n of the g i ven a rgume n t s and ke ywo rds . \ n ' Upda t i n g wrappe r : assign : ( ' modu l e anno t a t i on s ') ',) upda t e : ( ' dic t

name

_qua l n ame

doc

upda t e d wrappe r : obj e c t : fun c t o o l s . pa r t i a l ( < func t i on myfunc a t О х 1 0 1 8 а ба б 0 > , Ь= 4 ) _name_ : myfunc _doc_ ' Do c s t r i ng for myfunc ( ) . '

3.1. functools: инструменn.� ,,,.,. маниnуАированм фунКЦИRми

175

3 . 1 . 1 .З. Другие вызываемые объекты

Класс par t i a l позволяет работать с любыми вызываемыми объектами, а не только с автономными функциями. Листинг 3.3. functool s callaЫe . ру _

irnport funct o o l s

c l a s s MyCl a s s : " Демонстрационный кл асс для funct o o l s " de f

cal l ( se l f , е , f= б ) : " D o c s t r i n g f o r MyC l a s s . ca l l " ca l l e d obj ect wit h : ' , ( s e l f , е , f ) ) print ( '

def show_de t a i l s ( narne , f ) : " Показать детали вызыв а емого о бъе к т а . " p r i nt ( ' { } : ' . fo rrna t ( narne ) ) obj ect : ' , f ) print ( ' print ( ' narne : ' , end= ' ' ) try : print ( f , narne ) except At t r ibut eEr� r : p r i nt ( ' ( no narne ) ) p r i n t ( ' _doc _ ' , repr ( f . _doc_ ) ) r e t urn '

о = MyC l a s s ( ) show_det a i l s ( ' inst ance ' , о ) о ( ' е goes here ' ) print ( ) р funct o o l s . part i a l ( o , e= ' de f a u l t f o r е ' , f= B ) funct o o l s . upda te_wrappe r ( p , о ) show_de t a i l s ( ' i n s t ance wrappe r ' , р ) р() =

В следующем примере объекты part i a l создаются из экземнляра класса с по­ мощью метода ca l l ( ) . $ pythonЗ functool s_ca l l a Ы e . py i n s t a nce : obj e c t : narne : ( no narne ) ' Dernons t r a t i on c l a s s for fun c t o o l s ' doc ca l l e d obj e ct w i t h : ( , ' е g o e s here ' , 6 )

ГА888 3 . ААl'орМУМЫ

178

i п s t aпce wr appe r : obj e c t : fuпctoo l s . pa r t i a l ( , e = ' de fault f o r е ' , f=B ) пате : ( по пате ) ' Demoпs t r a t i oп c l a s s for fuпct ool s ' doc c a l led obj e c t wi t h : ( < �rna iп . MyC l a s s obj ect at О х 1 0 1 1 Ы с f 8 > , ' de f a u l t for е ' , 8 )

3. 1 . 1 .4. Методы и функции В то время как функция part i a l ( ) возвращает вызываемый объект, готовый к непосредственному использованию, функция partialmethod ( ) возвращает объект, готовый к использованию в качестве несвязанного метода объекта. В сле­ дующем примере одна и та же автономная функция добавляется в качестве атри­ бута класса MyClass дважды: один раз с помощью функции par t i a lme thod ( ) , как method l ( ) , и второй раз с помощью функции par t i a l ( ) , как method2 ( ) . Листинг 3.4. functools_partialmethod . ру iтport fuпc t oo l s de f s t aпda l oпe ( s e l f , a=l , Ь=2 ) : "Ав т ономная функци я " priпt ( ' c a l l e d s t aпda l oпe wi t h : ' , ( se l f , а , Ь ) ) i f s e l f i s поt Nопе : priпt ( ' s e l f . a t t r = ' , s e l f . at t r ) c l a s s MyCl a s s : " Демонстрационный класс дпя fuпct o o l s " de f

iпit ( se l f ) : sel f . attr ' i п s t aпce a t t r i but e ' =

me t hodl тe t hod2

fuпctool s . part i a lme t hod ( s t aпda loпe ) fuпct o o l s . part i a l ( s t a пdal oпe )

о = MyC l a s s ( ) p r i п t ( ' s t aпda l oпe ' ) s t aпdal oпe ( Noпe ) priпt ( ) priпt ( ' тethodl a s pa r t i a lтe thod ' ) о . тe thodl ( ) priпt ( ) priпt ( ' тe thod2 as pa r t i a l ' ) t ry : o . тe thod2 ( ) except Type E r r o r a s e r r : pr i п t ( ' ERROR : [ } ' . forrna t ( e r r ) )

3.1. functuols: мнсrрумеtПЫ /11114 маниnу11Мрованмя фунКЦМflмм

1 77

Метод me thodl ( ) может вызываться для экземпляра MyC l a s s , и экземпляр передается ему в качестве первого аргумента точно так же, как в случае методов, определенных обычным способом. Метод me thod2 ( ) не задан как связанный, 110зтому аргумент s e l f должен передаваться ему явным образом, иначе вызов при­ ведет к возбуждению исключения TypeError.

$ pyt hon З funct o o l s_pa r t i a lme thod . py

s t anda l one ca l l ed s t a ndal one wi th :

( None , 1 , 2 )

metho d l a s pa r t i a lmethod ca l l ed s t anda l one w i t h : ( , 1 , 2 ) se l f . a t t r i n s tance a t t ri bute

. MyCl a s s obj e ct at

=

method2 a s p a r t i a l E RROR : s t anda l one ( ) mi s s ing 1 requi red p o s i t i ona l a rgume n t : ' se l f '

3 . 1 . 1 . 5. Наделение декоратора свойствами декорируемой функции Обновление свойств функции-обертки особенно полезно в случае декорато­ ров, поскольку это позволяет сделать ее похожей на исходную "голую" функцию.

Пистинг 3. 5 . functool s_wraps . ру

i::nport funct o o l s

d e f show_de t a i l s ( name , f ) : " Пока з а т ь детали вызываемо го объе кта . " p r i nt ( ' ( ) : ' . f o rma t ( name ) ) print ( ' obj e ct : ' , f ) name : ' , end= ' ' ) print ( ' try: p r i n t ( f . name except AttributeEr r o r : p r i n t ( ' ( no name ) ' ) print ( ' doc ' repr ( f . doc ) ) print ( )

d e f s i mp l e_decora t o r ( f ) : @ funct oo l s . wraps ( f ) de f decorated ( a= ' de c o r a t e d defaul t s ' , b= l ) : print ( ' decora t e d : ' , ( а , Ь ) ) ' , e nd= ' ' ) print ( ' return f ( a , Ь=Ь ) return decorated

def myfunc ( a , Ь=2 ) :

1 78

ГА888 3. Аморитмы "myfunc ( ) i s not comp l i ca t e d " p r i nt ( ' myfunc : ' , ( а , Ь ) ) return

# Исходная фун кци я show de t a i l s ( ' myfunc ' , myfunc ) my func ( ' unwrapped , default Ь ' ) my func ( ' unwrapped , p a s s i n g Ь ' , 3 ) p r i nt ( ) # Явное упа ковыв а ни е wrapped_myfunc = simp l e_de cora t o r ( myfun c ) show_de t a i l s ( ' wr appe d_myfunc ' , wrapped_myfunc ) wrappe d_myfunc ( ) wr appe d_myfunc ( ' a r g s to wrapped ' , 4 ) p r i nt ( )

# Упаковывание с помощью синтакси са де коратора @ s imp l e_de c o r a t o r de f de c o r a t e d_myfunc ( a , Ь ) : my func ( a , Ь ) return

show_de t a i l s ( ' decorated_my func ' , decorated_myfunc ) decor a t e d_my func ( ) de c o r a t e d_myfunc ( ' a r g s t o decora t e d ' , 4 )

Модуль funct o o l s предоставляет декоратор wraps ( ) , который пр и меняет функцию update wrappe r ( ) к декорируемой функции. _

$ pyt h o n 3 funct o o l s_wraps . py my func : obj ect : name : myf unc ' myfunc ( ) is not comp l i ca t e d ' doc myfunc : myfun c :

( ' unwrapped , de f a u l t Ь ' , 2 ) ( ' unwrapped , p a s s i n g Ь ' , 3 )

wrapped_my func : obj e ct : < funct i o n myfunc at Ox l 0 1 2 e 6 2 f 0 > name : my func �do c� ' myfunc ( ) is not comp l i ca t e d ' deco r a t ed : my func : deco r a t ed : myfunc :

( ( ( (

' decorated defaul t s ' , 1 ) ' decorated de fa u l t s ' , 1 ) ' a r g s t o wr apped ' , 4 ) ' a r g s t o wrapped ' , 4 )

3.1. func:tools: инс:rрументы NtЯ М8НМnуАИро88НИR фунlСЦИflМИ

179

decorated-rnyfunc : obj e ct : < funct i o n decora ted_rny func at О х 1 0 1 2 е 6 4 0 0 > �narne_ : de c o r a t ed_rnyfunc None doc decora t e d : rnyfunc : decora t e d : rnyfunc :

( ( ( (

' decorated de fau l t s ' ' decorated de f au l t s ' ' a r g s t o decora t e d ' , ' a rg s t o decor a t e d ' ,

, 1) , 1) 4) 4)

3 . 1 . 2 . Сра в нение В Руtlюп 2 классы могут определять метод cmp ( ) , который возвращает значение - 1 , О или 1 , в зависимости от того, является ли объект соответствен­ но меньшим, равным или большим, чем элемент, с которым он сравнивается. В Руtlюп 2. 1 появился API методов расширенного сравнения (_l t_ ( ) , l e ( ) , eq_ ( ) , _n e ( ) , _gt ( ) и g e ( ) ) , которые выполняют сравнение и возвращают булево значение. В Руtlюп 3 от метода _cmp ( ) отказались как от устаревшего в пользу этих новых методов, и модуль functoo l s предоставляет и н­ струменты, которые упрощают написание классов, совместимых с требованиями к операциям сравнения в Pytlюn 3. _

_

_

_

_

_

_

_

_

_

3 . 1 .2.1 . Расширенное сравнение API расширенного сравнения проектировался с таким расчетом, чтобы обе­ спечить максимально эффективную реализацию каждого теста для кла othe r . va l

pr int ( ' Methods : \n ' ) pprint ( i nspe c t . ge tmemЬe r s ( MyObj e c t , i n spect . i s func t i on ) ) а Ь

MyObj ect ( l ) MyOb j e ct ( 2 )

p r i nt ( ' \ nCompa r i s on s : ' ) Ь ' , ' а >= Ь ' , ' а > Ь ' ] : f o r expr i n [ ' а < Ь ' , ' а ) , ' _ge_ ' , < func t i o n _ge_fr om_g t at О х 1 0 1 2 е 2 5 1 0 > ) , ' _g t_ ' < f unct i o n MyObj e c t . _gt_ a t О х 1 0 1 3 9 а 5 1 0 > ) , ' init ' , < func t i on MyOb j e c t . init a t Ох 1 0 1 3 9а 4 0 0 > ) , ' < f unc t i o n l e_from_g t at О х 1 0 1 2 е 2 5 9 8 > ) , ' le ' , < funct i o n _lt_f rom_gt a t О х 1 0 1 2 е 2 4 8 8 > ) ] ' lt

Compa r i s o n s : а < Ь : t e s t i n g _g t (1, 2) t e s t i n g _e q_ ( l , 2 ) resul t o f а < Ь : True а = Ь : Fa l s e

Э.1. functooll:

ММСl'РУМ•Н1Ы NtR манмnуАМроеанМJ1 фyltКIUlflMM

181

а > Ь : t e s t in g gt (1, 2) result о�а >""°Ь : Fa l se

3 . 1 .2.2. Порядок сортировки Поскольку функции сравнения старого стиля признаны уст-сt рсвшими в Pyt­ }1011 3, аргумент стр в функциях наподобие s o r t ( ) больше 11е поддерживается. Функция cmp to key ( ) обеспечивает преобразование старых функций в функ­ ции, которые возвращают 'IСЯЮЧ r.(}j1mиp081'1i" определяющий позицию 061.ек'l'"а в ко­ нечной последовательности. _

_

Листинг 3.7. funatool s_cmp_to_key . py import funct o o l s

c l a s s MyObj e c t : de f

init ( se l f , va l ) : se l f . va l = va l

de f

str ( se l f ) : return ' MyObj ect ( { } ) ' . f o rma t ( se l f . va l )

de f compa re_obj ( a , Ы : " " " Функция сра внения старого стиля . "" p r i n t ( ' compa ring { } and { } ' . f o rmat ( а , Ы ) i f a . va l < b . va l : return - 1 e l i f a . va l > ь . va l : return 1 return О "

# За ставить функцию ключа и с п ол ь з о ва ть функцию cmp_t o_ke y ( ) get_key = funct o o l s . cmp_t o_ke y ( c ompare_obj )

de f g e t_ke y_wrappe r ( o ) : " Функция-обертка для get_ke y , разрешающа я инструкции вывода . " new key g e t ke y ( o ) p r i n t ( ' ke y_wrappe r ( { } ) - > ! ! r } ' . fo rmat ( о , new_ke y ) ) return new_key =

obj s = [ MyObj ect ( x ) for х i n range ( 5 ,

О,

-1 ) ]

for о i n s o r t e d ( obj s , ke y=g e t_ke y_wrappe r ) : print ( o )

fАМВ З. Аморм1мw

1 82

Обычно функцию cmp_t o key ( ) используют непосредственно, но в данном примере привлекается функция-обертка, обеспечивающая вывод дополнитель­ ной информации при вызове функции сопоставления . Как следует из приведенных ниже результатов, выполнение функции s o r ted ( ) начинается с вызова функции ge t key_wrappe r ( ) для каждого элемента после­ довательности с целью генерации ключей. Ключами, возвращаемыми функцией стр_ to _ key ( ) , являются экземпляры определенного в модуле functools класса, который реализует API расширенного сравнения с использованием передавае­ мой ему функции сравнения старого стиля. Созданные ключи используются для сортировки последовательности путем их сравнения. _

_

$ pythonЗ funct o o l s_cmp_t o_ke y . py ke y_wrappe r ( My0bj e c t ( 5 ) ) - > < functo o l s . K e yWr appe r Ох 1 0 1 1 с 5 5 3 0 > ke y_wr appe r ( MyObj e ct ( 4 ) ) -> < functo o l s . KeyWr apper Ох 1 0 1 1 с 55 1 0 > ke y_wr appe r ( MyObj e ct ( З ) ) - > < funct o o l s . KeyWr appe r Ox 1 0 1 1 c 5 4 f 0 > ke y_wr appe r ( My0bj e ct ( 2 ) ) - > < funct o o l s . KeyWrappe r Ох1 0 1 1с5390> ke y_wr appe r ( MyOb j e ct ( l ) ) - > comp a r i ng MyObj ect ( 4 ) and MyObj e c t ( S ) cornpa r i ng MyObj e c t ( З ) and My0b j e c t ( 4 ) comp a r i n g My0bj ect ( 2 ) and MyObj ect ( З ) comp a r i ng MyObj ect ( l ) and My0bj ect ( 2 ) MyObj ect ( l ) My0bj e c t ( 2 ) MyObj ect ( З ) My0bj ect ( 4 ) MyObj e c t ( S )

obj e c t at obj e c t at obj ect a t obj e c t a t obj e ct a t

3 . 1 . 3. Кеширование Декоратор lru_cache ( ) обертывает функцию кешем LRU (от англ. kast recently used - "вытесняется элемент, неиспользованный дольше всех"). Аргументы функ­ ции используются для создания хеш-значения, которое сопоставляется с резуль­ татом. Последующие вызовы функции с теми же аргументами будут заменяться извлечением соответствующего значения из кеша. Кроме того, этот декоратор добавляет в функцию методы , обеспечивающие проверку состояния (cache_ info ( ) ) и очистку (cache_clear ( ) ) кеша. Листинг 3.8. functools lru_cache . py _ import funct o o l s

@ func t o o l s . l ru_cache ( ) de f expens ive ( a , Ь ) : p r i nt ( ' e xpe n s ive ( ! } ,

{ } )

'

. fo rmat ( а , Ь ) )

3.1. functools: инструмеН1Ы А/1Я маниnуАИ рованИR �кцинми

1 83

return а * Ь МАХ = 2 p r i n t ( ' Fi r s t s e t o f c a l l s : ' ) for i i n range ( МАХ ) : for j in range ( МАХ ) : expens ive ( i , j ) p r i n t ( expens ive . ca che_i n fo ( ) ) p r i n t ( ' \nSe cond set of c a l l s : ' ) for i i n range ( МAX + 1 ) : f o r j in range ( МAX + 1 ) : expen s i ve ( i , j ) print ( expe n s i ve . cache_i nfo ( ) ) p r i n t ( ' \ nCl ea r i ng ca che : ' ) expe n s i ve . ca che_cl e a r ( ) p r i n t ( e xpen sive . ca che_i n f o ( ) ) p r i n t ( ' \nTh i r d set o f ca l l s : ' ) f o r i i n range ( МAX ) : f o r j in range ( МАХ ) : e xpe n s i ve ( i , j ) p r i n t ( expens ive . ca che_in f o ( ) )

В этом примере выполняется серия вызовов функI\ИИ expen s i ve ( ) в наборе вложенных циклов. При повторных вызовах функции с одними и теми же значе­ ниями аргументов результаты появляются в кеше. При повторном выпол нении циклов после очистки кеша значения должны вычисляться :�ново.

$

py thon З fun c t o o l s_l ru_ca che . py

First set of cal ls : expe n s i v e ( O , 0 ) expe n s i ve ( O , 1 ) expens i ve ( l , 0 ) expe n s ive ( l , 1 ) Cache i n f o ( h i t s = O , mi s s e s = 4 , max s i ze= l 2 8 , cur r s i z e= 4 ) Se cond set o f ca l l s : expe n s i ve ( O , 2 ) expe n s i ve ( l , 2 ) expe n s i v e ( 2 , 0 ) expe n s ive ( 2 , 1 ) expen s i v e ( 2 , 2 ) C a che i n f o ( hi t s= 4 , mi s s e s = 9 , maxs i z e = l 2 8 , currs i z e=9 ) C l e a r i n g cache : Cache i n f o ( h i t s = O , mi s s e s= O , max s i z e = l 2 8 , curr s i z e = O ) Third s e t of ca l l s :

ГА888 Э . АN'орИNЫ

1 84 expe n s i ve expens ive expens ive expens ive C a che i nfo

( O, 0) (O, 1) (l, 0) (l, 1) ( h i t s= O , mi s s e s = 4 , maxs i ze= 1 2 8 , cu r r s i z e=4 )

Неограниченный рост размера кеша в длительно выполняющихся процессах можно предотвратить, задав его максимальный размер. Размер по умолчанию 128 записей, но его можно регулировать для каждого кеша с помощью аргумента maxs i ze. Листинг 3.9. functools_lru_cache_expi re . py impor t funct o o l s

@ func t o o l s . l ru_ca che ( maxs i z e=2 ) de f expens i ve ( a , Ь ) : p r i n t ( ' ca l l e d expens ive ( { } , return а * Ь

{ } ) ' . f o rma t ( а , Ь ) )

de f ma ke_ca l l ( a , Ь ) : p r i nt ( ' ( { } , ( } ) ' . fo rma t ( a , Ь ) , end= ' ' ) pre_h i t s expens i ve . cache_i nfo ( ) . h i t s expens ive ( a , Ь ) p ost_h i t s expe ns ive . ca che_ i nf o ( ) . hi t s i f post_h i t s > pre_hi t s : p r i n t ( ' ca che h i t ' } =

=

p r i n t ( ' Es t a Ы i s h the cache ' ) ma ke_ca l l ( l , 2 ) ma ke_ca l l ( 2 , 3 ) p r i n t ( ' \nUse cached i t ems ' ) ma ke_ca l l ( l , 2 ) ma ke_ca l l ( 2 , 3 ) pr i n t ( ' \nCompute а new value , t r i gg e r i n g cache expi r a t i o n ' ) ma ke_ca l l ( 3 , 4 ) print ( ' \nCache s t i l l cont a i ns one o l d i t em ' ) ma ke_ca l l ( 2 , 3 ) print ( ' \nOlde s t i t em needs t o Ье recomputed ' ) ma ke_ca l l ( 1 , 2 )

В этом примере размер кеша ограничен двумя записями. При использовании третьего набора уникальных аргументов ( 3 , 4 ) самая давняя из записей заменяется новым результатом.

3.1. functools: ИНСfРУМ8Н1Ы NtЯ ман иnумровани11 фун1СЦИ11ми

1 85

$ python3 func t o o l s_l ru cache_ exp i re . py E s t a Ы i sh the cache ( 1 , 2 ) cal l ed expens ive ( l , 2 ) ( 2 , 3 ) ca l l ed expe nsive ( 2 , 3 ) U s e ca ched it ems ( 1 , 2 ) cache h i t ( 2 , 3 ) cache h i t C ompute а new va l u e , t r i gg e r i n g c a che expi r a t i on ( 3 , 4 ) ca l l ed expensive ( 3 , 4 ) Cache s t i l l cont a i n s one o l d i t em ( 2 , 3 ) cache h i t O l d e s t i t em ne eds to Ь е re computed ( 1 , 2 ) ca l l ed expensive ( l , 2 )

Ключи дл я кеша, управляемоl'о функцией l r u_cache ( ) , должны быть хеширу· емыми , и поэтому такими же должны быть ар1ументы, передаваемые функции , которая обернута кешем. Листинг 3 . 1 0. functool s_lru_caohe_arguments . py import funct o o l s @ func t o o l s . l ru_ca che ( maxsi ze=2 ) d e f expe n s i ve ( a , Ь ) : print ( ' ca l l e d expen s ive ( { } , return а * Ь

{ } ) ' . forma t ( а , Ь ) )

de f ma ke_c a l l ( а , Ь ) : p r i nt ( ' ( { } , { } ) ' . fo rma t ( a , Ы , e nd= ' ' ) pre_h i t s = expens ive . ca che_i n f o ( ) . hi t s expe n s i ve ( а , Ы po s t h i t s = e xpen s ive . cache- i n f o ( ) . h i t s i f pos t_hi t s > pre_h i t s : p r i n t ( ' ca che hi t ' ) ma ke_ca l l ( 1 , 2 ) try : ma ke_ c a l l ( [ 1 ) , 2 ) except Type Error a s e r r : print ( ' ERROR : { } ' . forma t ( e r r ) ) try : ma ke_c a l l ( l , { ' 2 ' : ' two ' } ) e x cept Type E r r o r a s err : print ( ' ERROR : { } ' . forma t ( e r r ) )

Г/1888 З. Аморитмы

1 88

Если функции передается объект, который нельзя хешировать, возбуждается исключение TypeError. $ python З funct o o l s_l ru_cache_argument s . py ( 1 , 2 ) ca l l ed expe n s i v e ( l , 2 ) ( [ 1 ] , 2 ) ERROR : unha sha Ы e t ype : ' l i st ' ( 1 , { ' 2 ' : ' two ' ) ) ERROR : unha shaЫe t ype :

' di ct '

3 . 1 .4. Редукция набора данных Функция reduce ( ) получает вызываемый объект и последовательнщ�ть дан­ ных в качестве аргументов. Результатом ее работы является единственное значе­ ние, основанное на вызове объекта со значениями из последовательности и нако­ плении результатов. Листинг 3 . 1 1 . functool s reduce . ру _

imp o r t fun c t o o l s

de f do_reduce ( a , Ь ) : print ( ' do_reduce { { } , return а + Ь

{ 1 ) ' . forma t ( а , Ь ) )

data range ( l , 5 ) p r i n t ( da t a ) r e s u l t = funct o o l s . reduce ( do_reduce , dat a ) p r i n t ( ' resu l t : { ) ' . f o rma t ( re s ul t ) ) =

В этом примере суммируются числа, образующие входную последователь­ ность. $ pytho n З func t o o l s_reduce . py range ( 1 , 5 ) do_reduce ( l , 2 ) do_re duce ( З , 3 ) do_reduce ( б , 4 ) resul t : 1 0

Необязательный аргумент ini t i a l i zer помещается перед последовательно­ стью и обрабатывается наряду с другими элементами. Это может быть использо­ вано для обновления ранее вычисленного значения новыми входными данными. Листинг 3 . 1 2 . functool s reduce_ini tiali zer . ру _

import funct o o l s

de f do_re duce ( a , Ь ) : print ( ' do reduce ( { } ,

{ } ) ' . fo rma t ( a , Ь ) )

1 87 ret urn а + Ь d a t a = range ( l , 5 ) p r i n t ( da t a ) r e s u l t = func t o o l s . reduce ( do_reduce , data , 9 9 ) p r i n t ( ' r e s u l t : { } ' . f o rma t ( re s u l t ) )

В этом примере ранее найденная сумма , равная 99, используется для инициа­ лизации значения , вычисляемого функцией reduce ( ) . $ pythonЗ funct o o l s_reduce_i n i t i a l i z e r . py range ( l , 5 ) do_reduce ( 9 9 , 1 ) do_reduce ( l O O , 2 ) do_reduce ( l 0 2 , 3 ) do_reduce ( 1 0 5 , 4 ) re s u l t : 1 0 9

Последовательности с единственным элементом редуцируются до этого значе­ ния , если не предоставлен аргумент i n i t i a l i zer. В отсутствие этого аргумента пустые списки генерируют ошибку. Листинг 3 . 1 3. funatools reduae short sequenaes . ру _

_

import funct o o l s

de f do_reduce ( a , Ы : p r i n t ( ' do_reduce ( ( } , return а + Ь

( } ) ' . f o rma t ( a , Ь ) )

pr i n t ( ' S i ng l e i t em i n s e quence : ' , func t o o l s . r educe ( do_reduce , ( 1 ) ) ) p r i n t ( ' S i ng l e i t em i n s e quence w i th i n i t i a l i z e r : ' , functoo l s . reduce ( do_reduce , ( 1 ) , 9 9 ) ) p r i n t ( ' Empty s e quence w i th i n i t i a l i z e r : ' , funct o o l s . reduce ( do_reduce , [ ) , 9 9 ) ) try : print ( ' Empt y s eque nce : ' , functo o l s . reduce ( d o_reduce , except TypeError a s e r r : p r i n t ( ' ERROR : { ) ' . fo rma t ( e r r ) )

[) ) )

Поскольку аргумент ini t i a l i zer выступает в качестве значения по умолча­ нию и при этом сочетается с новыми значениями , сели входная последователь­ ность не является пустой , очень важно тщательно проверять, используется ли он надлежащим образом. Если в сочетании значения по умолчанию с новыми значе-

1 88

11'888 з. AN'opИlllbl

ниями нет смысла, лучше перехватывать исключение TypeError, чем передавать аргумент initiali zer. $ pythonЗ funct o o l s_reduce_sho r t_se que nces . py S i n g l e i t em in sequence : 1 do_reduce ( 9 9 , 1 ) S i n g l e i tem i n sequence w i th i n i t i a l i z e r : 1 0 0 Emp t y s e quence w i t h i n i t i a l i z e r : 9 9 ERROR : reduce ( ) o f emp t y sequence w i t h no i n i t i a l va l ue

3 . 1 . 5 . Обоб щенные функции В языках программирования с динамической типизацией, таких как Pyt l1011, часто возникает необходимость в изменении характера выполнения операций в зависимости от типа ар1ументов, особенно если речь идет о проведении разли­ чий между списком элементов и одиночным элементом. Непосредственная про­ верка типов не вызывает затруднений, однако в тех случаях, когда различия в поведении могут быть вынесены в отдельные функции, модуль functoo l s предо­ ставляет декоратор singledi spatch ( ) , который обеспечивает регистрацию на­ бора обобtijtтных фу·н кций с возможностью автоматического выбора нужной функ­ ции на основании типа первого аргумента. Листинг 3. 1 4. functools_singledi spatch . py imp o r t funct oo l s

@ funct o o l s . s ingl edi spa t ch def myfunc ( a rg ) : p r i n t ( ' de f a u l t myfunc ( { ! r ) ) ' . f o rma t ( a r g ) )

@myfunc . re g i s t e r ( i nt ) def myfunc_int ( a rg ) : p r i n t ( ' myfunc_i nt ( { ) ) ' . forma t ( a rg ) )

@myf unc . re g i s t e r ( l i s t ) de f myfunc_l i s t ( a rg ) : p r i n t ( ' my func_l i s t ( ) ' ) f o r i t em i n a r g : p r i n t ( ' { ) ' . forma t ( i tem) ) my func ( ' s t r i n g a r gument ' ) myfunc ( l ) myfunc ( 2 . 3 ) myfunc ( [ ' а ' , ' Ь ' , ' с ' ] )

Атрибут regi ster ( ) новой функции служит дополнительным декоратором для регистрации альтернативной реализации. Первая функция, обернутая деко-

1 81

ратором s ing ledi spatch ( ) , является реализацией по умолчанию, если не обна­ ружена никакая другая функция, специфическая для данного типа, как в случае типа float в этом примере. $

pythonЗ funct o o l s_s i n g l e d i spa t c h . py

de f a u l t my func ( ' s t r ing argument ' ) myfunc_int ( l ) de faul t my func ( 2 . 3 ) myfunc_l i s t ( ) а ь с

Если не удается найти точного соответствия типу, определяется порядок на­ следования и используется наиболее соответствующий тип. Лисrинг 3 . 1 5 . funatool s_s ingledi spatah_mro . py impo r t funct o o l s

class А: pa s s

class В (А) : pa s s

class С (А) : pass

class D ( B ) : pa s s

class Е ( С, D ) : pa s s

@ func t o o l s . s i ng l e d i spatch def my func ( a rg ) : p r i nt ( ' de faul t my func ( { ) ) ' . forma t ( a rg . �c l a s s� · � name � ) ) @myfunc . re g i s t e r ( A ) def myfunc_A ( a rg ) : p r i n t ( ' myfunc_A ( { } ) ' . f o rma t ( a rg . � cl a s s � · �name � ) ) @myfunc . re g i s t e r ( B )

ГАавв З. Амормтмw

1 90 d e f myfunc_B ( a r g ) : p r i n t ( ' myf unc_B ( { ) ) ' . f o rma t ( a r g . � c l a s s� · � name � ) ) @myfunc . re g i s t e r ( C ) de f myfunc_C ( a r g ) : print ( ' myfunc_C ( { } ) ' . fo rma t ( a rg . �c l a s s� · � name� ) ) myfunc (А ( ) myfunc ( В ( ) myfunc ( С ( ) myfunc ( D ( ) myfunc ( E ( )

) ) ) ) )

В этом примере для классов D и Е не находится точного соответствия с ка­ кой-либо зарегистрированной обобщенной функцией , и выбор функции зависит от иерархии классов. $ pythonЗ funct o o l s_s i n g l e d i spa t c h_mr o . py

myfunc_A ( A ) my func_B ( B ) myfunc_C ( C ) my func_B ( D ) myfunc_C ( E ) Дополнительные сс ылки • •







Раздел документации стандартной библиотеки, посвященный модулю func t o o l s 1 • Методы расширенного сравнения 2 • Описание методов расширенных операций срав­ нения в справочном руководстве Python. lsolated @memoize 3 (Ned Batcheldeг). Статья, посвященная созданию декораторов ме­ моизации, которые хорошо сочетаются с блочными тестами. РЕР 4434 • Single-dispatch generic functions. Оп исание динамической диспетчеризации вызовов обобщенных функций на основании одного аргумента . Модуль inspect (раздел 1 8.4). API интроспекции для активных объектов.

3 . 2 . i tertool s : функции-ите р ато р ы Модуль i t e r too l s включает ряд функций, предназначенных для обработки последовательностей. Их прообразом послужили аналогичные средства таких языков функционального программирования, как Clojuгe, Haskell, APL и SML. Указанные функции предназначены для повышения эффективности использова1 2 3 4

https : / /docs . pyt hon . o r g / 3 . 5 / l i brary/ f unct o o l s . html https : / /docs . python . o r g / r e f e re n c e / da t amode l . html #obj ect . lt http : / / nedba t che l d e r . com/Ьl o g / 2 0 1 6 0 1 / i s o l a t e d_memo i ze . h tml www . python . o r g /dev /peps /pep- 0 4 4 3

3.2. lt8rtools: фунКЦИIНfТ81J8ТОРЫ

191

ния памяти и ускорения выполнения операций. Их также можно использова1ъ со­ вместно для создания более сложных итерационных алгоритмов. Основанный на итераторах код обеспечивает лучшие характеристики исполь­ зования памяти, чем код, основанный на использовании списков. Поскольку итератор не возвращает данные до тех пор, пока они не потребуются , отпадает необходимость в хранении всего набора данных в памяти. Такая модель отложен­ ной обработки сглаживает отрицательное влияние подкачки и других побочных эффектов, связанных с обработкой больших объемов данных, на производитель­ ность. Кроме функций, определенных в модуле i t ertoo l s , в приведенных ниже при­ мерах используются некоторые встроенные функции, предназначенные для рабо­ ты с итераторами.

3 . 2. 1 . Объединение и разделение итераторов Функция chain ( ) получает несколько итераторов в качестве аргументов и соз­ дает итератор, который поочередно обрабатывает все входные итсрируемые объ­ екты , возвращая их элементы, как сели бы они принадлежали одному входному итератору. Листинг 3 . 1 б. i tertools_chain . ру f rorn i t e rt o o l s irnp o r t * for i in cha i n ( [ l , 2 , 3 ] , p r i n t ( i , end= ' ' } print ( }

[ 'а' ,

'Ь' ,

'с' ] ) :

Функция cha in ( ) упрощает обработку нескольких последовательностей, не требуя предварительного конструирования объединенного списка. $ python3 i t e rtoo l s_cha i n . py 1 2 3 а Ь с

В тех случаях, когда объединяемые итерируемые объекты неизвестны заранее или должны определяться в режиме отложенных вычислений, для конструирова­ ния цепочки итераторов можно использовать функцию chain . from i teraЫe ( ) . _

Листинг 3 . 1 7. i tertool s chain_from_i teraЫe . py f rorn i t e rt o o l s irnp o r t *

de f rna ke_ i t e raЫe s_t o_cha i n ( ) : yi e l d [ 1 , 2 , 3 ) yield [ ' а ' , ' Ь ' , ' с ' ]

for i in cha i n . f r orn i t e raЬle ( rna ke i t e r a Ы e s - to - cha i n ( ) ) : p r i n t ( i , end= ' 1 1 print ( }

rNUl8 з. ААrоритмы

192 $ python3 i t e r tool s_chain_from_i t e r aЫ e . py 1 2 3 а Ь с

Встроен ная функция zip ( ) возвращает итератор , объединяющий элементы нескольких итераторов в кортежи. Лисrинг 3 . 1 8. i tertools_z ip . py for i in z ip ( [ l , 2 , 3 ) , p r i nt ( i )

[ 'а' ,

'Ь' ,

'с' ] ) :

Как и в случае других функций, входящих в состав этого модуля, возвращаемое зна•1ение представляет собой итерируемый объект, выдающий значения по одно­ му за раз. $ python 3 i t e r t o o l s_z i p . py (1, (2, (3,

'а' ) 'Ь' ) 'с' )

Функция z i p ( ) прекращает работу, l(ак тош,ко исчерпывается один из вход· ных итераторов. Чтобы обеспе•1ить обработку всех входных элементов даже в тех случаях, когда итераторы вырабатывают разное количество значений, используй· те функцию z ip_longe s t ( ) . Листинг 3 . 1 9. i tertool s_zip_longe s t . py from i t e rt o o l s import * rl r2

range ( 3 ) range ( 2 )

p r i n t ( ' z ip s t op s e a r l y : ' ) p r i n t ( l i st ( zip ( r l , r 2 ) ) ) rl r2

range ( 3 ) range ( 2 )

p r i n t ( ' \ n z i p_l onge s t proc e s s e s a l l of t h e va lues : ' ) p r i n t ( l i s t ( z i p_longe s t ( r l , r 2 ) ) }

По умолчанию функция z ip_ l onge s t ( ) подставляет значение None вместо от· сутствующих значений. Чтобы определить другое rюдстановочное значение, ис· пользуйте аргумент f i l lvalue. $ pytho n 3 i t e rt ool s_z i p_longes t . py

zip s t op s e a r l y : [ (0, 0) , (1, 1 ) ] z i p_l onge s t proce s s e s a l l of the value s : [ ( О , О ) , ( 1 , 1 ) , ( 2 , None ) J

З.2. ltertoola: функции-итераторы

1 93

Функция i s l ice ( ) создает итератор, который возвращает итератор, выдаю­ щий входные элементы в соответствии с заданными индексами. Листинг 3 . 20 . i tertool s_i sl i ce . py from i t e r t o o l s import * p r i n t ( ' S t op at 5 : ' ) f o r i i n i s l i c e ( range ( l 0 0 ) , 5 ) : p r i n t ( i , end= ' ' } print ( ' \n ' ) p r i n t ( ' S t a rt at 5 , S t op a t 1 0 : ' ) f o r i i n i s l ice ( range ( l O O ) , 5 , 1 0 ) : p r i n t ( i , end= ' ' ) print ( ' \ n ' ) print ( ' Ву tens to 1 0 0 : ' ) f o r i in i s l ice ( range ( 1 0 0 ) , О , 1 0 0 , print ( i , e nd= ' ' ) print ( ' \n ' )

10) :

Функция i s l ice ( ) имеет те же аргументы , что и оператор взятия среза спи­ ска: start, s top и step. Аргументы start и step - необязательные. $

pythonЗ i t e r t o o l s_i s l i ce . py

S t op at 5 : о 1 2 з 4 S t a r t at 5 , S t op at 1 0 : 5 6 7 8 9 Ву tens t o 1 0 0 : о 10 20 30 4 0 50 60 7 0 8 0 90

Функция tee ( ) позволяет создать несколько независимых итерато р ов (110 умолчанию - 2) на основе одного и того же итерируемого объекта. Листинг 3.21 . i tertool s_tee . ру f rom i t e r t o o l s import * r = i s l i c e ( count ( ) , 5 ) i l , i 2 = tee ( r } p r i nt ( ' i l : ' , l i s t ( i l ) ) pr i nt ( ' i 2 : ' , l i s t ( i 2 ) )

Семантика функции tee ( ) аналогична семантике утилиты tee в Опiх, которая повторяет значения, читаемые из входного потока, и запи(ывает их в именован­ ный файл или стандартный вывод. Итераторы, возвращаемые функцией tee ( ) , могут быть использованы с целью передачи одного и того же набора данных не­ скольким алгоритмам для их параллельной обработки.

ГА888 З. ААrормтмы

1 94

$

pyt h o n З i t e r t o o l s _ t e e . py

il: i2 :

(0, 1, 2, [О, 1, 2,

З, З,

4] 4]

Новые итераторы, созданные функцией tee ( ) , разделяют данные с исходным итератором, и поэтому после их создания исходный итератор использоваться нс должен. Листинг 3. 22 . i tertool s_tee error . ру _

f r om i t e rt o o l s impo rt * r i s l i ce ( count ( ) , 5 ) i l , i 2 = tee ( r ) =

pr i nt ( ' r : ' , e nd= ' ' ) for i in r : p r i n t ( i , e nd= ' ' ) if i > 1 : brea k p rint ( ) print ( ' i l : ' , l i s t ( i l ) ) pr i nt ( ' i 2 : ' , l i s t ( i 2 ) )

Значения, обработанные ранее исходным итератором, не будут возвращаться вновь созданными итераторами. $

pythonЗ i t e r t o o l s_tee_e r r o r . py

r: О 1 2 il : ( З, 4 ) i2 : ( 3 , 4 )

3 . 2 . 2 . П ре образование входных данных Встроенная функция m a p ( ) создает итератор, который вызывает заданную преобразующую функцию с аргументами , определяемыми зна•1ениями входных итераторов, и возвращает результаты. Этот процесс прекращается после исчер­ пания значений любого из входных итераторов. Листинг 3. 2 3. i tertool s_map . py de f t ime s_two ( x ) : re turn 2 * х

def mu l t i ply ( x , у ) : return ( х , у , х * у )

print ( ' DouЫ e s : ' )

3.2. ltertooll: ""IСЦМtНn'81)8ТОрЫ

1 05

for i in map ( t ime s_two , range ( S ) ) : print ( i ) p r i n t ( ' \ nMul t ip l e s : ' ) r l = range ( S ) r2 = range ( S , 1 0 ) for i in map (mul t ip l y , r l , r 2 ) : p r i nt ( ' { : d } * { : d } { : d } ' . fo rma t ( * i ) ) =

p r i nt ( ' \nStopp i n g : ' ) rl range ( S ) r 2 = range ( 2 ) for i i n map ( mul t i p l y , r l , r2 ) : print ( i ) =

В первом примере лямбда-функция умножает входные значения н а 2 . В о вто­ ром примере лямбда-функция перемножает пару аргументов, получаемых из неза­ висимых итераторов, и возвращает кортеж, включающий исходные аргументы и вычисленное значение. Выполнение третьего примера прекращается после соз­ дания двух кортежей ввиду исчерпания второ1·0 диапазона. $

python3 i t e r t oo l s_map . p y

DouЫ e s : о 2 4 6 в

Mul t ip l e s : о о * 5 6 1 * 6 2 * 7 14 24 3 * в 4 * 9 36 S t opping : (0, о, 0) (1, 1, 1)

Функция starmap ( ) аналогична функции map ( ) , но имеет всего два аргумента: лямбда-функцию и последовательность кортежей, задаваемую с помощью синтак­ сиса * , в которой каждый кортеж предоставляет аргументы лямбда-функции. Листинг 3 . 24. i tertools_s tarmap . py f r om i t e r t oo l s import values = [ ( 0 , 5 ) , for

*

(1, 6) ,

(2, 7) ,

(3, 8) ,

(4, 9) ]

1 in s t a rmap ( l arnЬda х , у : ( х , у , х * у ) , values ) : pr i nt ( ' { } * { } { } ' . fo rma t ( * i ) ) =

rмве з. АN'ормтмы

1 88

Если преобразующая функция , передаваемая функции map ( ) , вызывается как f ( i l , i 2 ) , то преобразующая функция , передаваемая фунющи starmap ( ) , вызы­ вается как f ( * i ) . $ python3 i t e r t o o l s _ s t a rmap . py о 1 2 3 4

* * * * *

о 6 14 24 36

5 6 7 8 9

3 . 2 . З . С оздание новых зна чений Функция coun t ( ) создает итератор, вырабатывающий бесконечную последо­ вательность целых чисел . Начальное значение может быть указано в качестве ар­ гумента ( по умолчан ию О ) . Аргумент для задания верхней границы не предусмо­ трен ( более полный коптрош. над резулнгирующим набором значений обес н ечи­ иает истроешшя фуню�ия range ( ) ) . -

Л истинr 3 . 2 5 . i tertool s_count . py f rorn i t e rt o o l s irnpo r t

*

for i in z ip ( count ( l ) , print ( i )

[ 'а',

'Ь' ,

'с' ] ) :

Выполнение примера прекращается по исчерпании элементов списка, задани качестие ар!'умента.

1ю1·0

$ python3 i t e r t o o l s_count . py

(1, (2, (3,

'а' )

'Ь' )

'с' )

Аргументами s tart и s tep функции count ( ) могут быть любые числовые зна­ чения , допускающие операцию сложения. Листинг 3.26. i tertool s_count_s tep . py irnp o r t f r a c t i ons f rorn i t e r t oo l s irnpo r t * start s t ep

= =

f r a c t i on s . Fra c t i o n ( l , 3 ) f r a c t i o n s . Fra c t i o n ( l , 3 )

for i i n z ip ( count ( s t a r t , s t e p ) , p r i n t ( ' ( } : ( } ' . fo rrna t ( * i ) )

[ 'а' ,

'Ь' ,

'с' ] ) :

В этом п р им ере начальное значение и шаг представлены объектами Fraction из модуля fract ion.

107 $ pytho n 3 i t e r t o o l s_count_s t e p . py 1/3: а 2/3: ь 1: с

Функция cycle ( ) создает итератор, по вто ряющи й содержимое аргументов бесконечное количество раз. Поскольку ата функц и я должна запоминать все со­ держимое входного итератора, она может потреблять много памяти в случае длинных входных последовательностей. Листинг 3.27. i tertool s_cycle . ру f r orn i t e r t o o l s irnport * f o r i in z i p ( range ( 7 ) , c y c l e ( [ ' a ' , print ( i )

В атом примере выполнение счетчика.

'Ь' ,

'с' ] ) ) :

цикла 1 1 реры1шется по исчерпании з11 а • 1 е п и й

$ pyt hon3 i t e rtool s_cycl e . py (0, (1, (2, (3, (4,

'а' )

(5,

'с' ) ,а, )

'Ь' )

'с' ) 'а' )

'Ь' )

(6,

Функция repea t ( ) создает итератор , возвращающий одно каждом об ращен ии к н е му.

и

то же значение

при

Листинг 3 . 28. i ter tool s_repeat . py f r orn i t e r t o o l s irnport * f o r i in repeat ( ' ove r-and-ove r ' , 5 ) : pr i n t ( i )

Итератор, созданный фун кц ие й repeat ( ) , может nозnращат1, данные беско­ нечное число раз, однако количество п овто рений можно ограничит�. с помощью необязательного аргумента t imes. $ pyt hon3 i t e rt o o l s_repe a t . py over-and-over ove r - a nd-over ove r - a nd - ove r over-and- ove r over-and- ove r

1"А88 3. A.voptnl8ol

1 98

Функцию repeat ( ) удобно использовать совместно с функциями z ip ( ) и map ( ) , ее.ли со значениями , возвращаемыми другими итераторами, должно соче­ таться некое инвариантное значение. Листинг 3.29. i tertool s_repeat_z ip . py f r om i t e r t o o l s import * for i , s in z i p ( count ( ) , repea t ( ' ove r - a nd-ove r ' , 5 ) ) : print ( i , s )

В этом примере значение счетчика объединяется с константой , возвращаемой функцией repea t ( ) . $

pythonЗ i t e rt o o l s_repeat_z ip . py

ove r - and-over 1 over-and- over 2 over-and- over з ove r - and-over 4 ove r - and- over о

В этом примере функция map ( ) используется для умножения на 2 чисел в диа­ пазоне 0-4. Листинг 3.30. i tertool s_repeat_map . py f r om i t e r t o o l s import * for i in map ( l amЬda х, у : ( х , у , х * у ) , repeat ( 2 ) , range ( 5 ) ) : print ( ' ( : d } * { : d } = ( : d } ' . fo rma t ( * i ) )

В данном случае итератор, возвращаемый функцией repeat ( ) , не нуждается в явном ограни•1ении числа повторений, поскольку обработка с помощью функции map ( ) прекращается сразу же, как только исчерпывается любой из ее входных итераторов, а функция range ( ) возвращает только пять элементов. $

pyt honЗ i t e rt o o l s_repea t_map . py

2 2 2 2 2

* о * 1 * 2

* з

* 4

о 2 4 6 8

3. 2 .4. Фильт рация Функция dropwhile ( ) создает итератор, который начинает воспроизводить элементы входного итератора сразу же после того, как для заданного условия бу­ дет получено ложное значение.

181 Листинг 3 . 3 1 . i tertools_dropwhile . ру f r om i t e r t oo l s import * de f should_drop ( x ) : print ( ' Te s t i ng : ' , х ) return х < 1 f o r i in dropwh i l e ( should drop , pr i nt ( ' Yi e l d i ng : ' , i )

[-1, О,

1 , 2, -2] ) :

Функция dropwhi l e ( ) не тестирует все элементы входной последовательно­ сти. Как только условие принимает ложное зна•1еп ие, опа начинает возвращать все оставшиеся элементы. $ pythonЗ i t e r t o o l s _dropwh i l e . py

T e s t ing : - 1 T e s t ing : О T e s t ing : 1 Yielding : 1 Y i e l ding : 2 Yielding : -2

Действия функции t a kewh i l e ( ) противоположны действиям функции dropwh i l e ( ) . Она создает итератор, который выда ' , а ) print ( ) с = i concat ( c , d ) print ( ' c i concat ( c , d ) = > ' , с ) =

В приведенных примерах продемонстрирована работа лишь некоторых И3 этих функций. Для получения более подробных сведений о соответствующих функциях обратитесь к документации стандартной библиотеки . $ а ь с d

pyt hon3 ope ra t o r _i np l a c e . py -1 5.0 [ 1 , 2 , 3] [ ' а' , 'Ь' ,

'с' ]

а = i add ( a , Ы = > 4 . 0 с = i concat ( c , d ) = > [ 1 , 2 , 3 ,

'а',

'Ь' ,

'с']

3 . 3 . 6 . Функции доступа к элементам и атрибутам Одна И3 наиболее необычных возможностей, предлагаемых модулем operator, свя3ана с понятием "получателей свойств" (getters). Это понятие относится к вы3Ываемым об·ьектам , которые со3Даются во время выполнен ия программы и пред­ назначены для получения атрибутов объектов или содержимого последователь­ ностей. Получатели свойств особенно полезны при работе с итераторами или генераторами последовательностей , поскольку работают быстрее и потребляют меньше памяти , чем лямбда-функции и функции Руtlюп. Листинг 3. 50. operator_attrgette r . py f r orn ope r a t o r irnp o r t * c l a s s MyObj : " " " Обра зец класса для a t t r g e t t e r '""'

з.з. operвtor: фуНКЦМОНаl\ЬНЫЙ интерфейс веtрОеННЫХ операторов

21 5

de f � i n i t ( se l f , arg ) : supe r ( ) . init () sel f . arg a rg =

def � repr� ( se l f ) : return ' MyOb j ( { } ) ' . fo rmat ( se l f . a r g )

1 = [ MyObj ( i ) for i in range ( 5 ) ] p r i n t ( ' obj ect s : ' , 1 ) # Извлечение значения ' a r g ' из каждого объекта g = a t t rget t e r ( ' ar g ' ) v a l s = [ g ( i ) for i i n 1 ] print ( ' a rg va l ue s : ' , va l s ) # Сортировка с исполь з о в а нием a r g 1 . reve r s e ( ) print ( ' reve rs ed : ' , 1 ) p r i n t ( ' s o r t e d : ' , s o r t ed ( l , key=g ) }

Получатели атрибугов работают аналогично лямбда-функции вида lamЬda x , n= ' a t t rname ' : ge t a t t r ( x , n ) . $ pyt h o n З operator _ a t t rget t e r . py

ob j e c t s a r g va l ue s : reve r sed sorted

[ MyObj [О, 1, [ MyObj [ MyObj

(0) 2, (4) (O)

, MyObj ( 1 ) , MyObj ( 2 ) , MyObj ( 3 ) , MyObj ( 4 ) ] З, 4] , МуОЬj ( З ) , MyObj ( 2 ) , MyObj ( 1 ) , MyObj ( 0 ) ] , MyObj ( 1 ) , MyObj ( 2 ) , М уОЬj ( З ) , MyObj ( 4 ) ]

Получатели элементов работают анало1·ично лямбда-функции вида l amЬda x , y= S : х [ у ] . Листинг 3. 5 1 . operator_i temgetter . py

from ope r a t o r impo r t

*

1 = [ di ct ( va l = - 1 * i ) f o r i in range ( 4 ) ] p r i n t ( ' D i c t i onarie s : ' ) p r i n t ( ' o r i g i nal : ' , 1 ) i t emge t t e r ( ' va l ' ) g va l s [ g ( i ) for i i n 1 ] p r i n t ( ' value s : ' , va l s ) p r i n t ( ' s o r t ed : ' , s o r t ed ( l , key=g ) ) =

=

print 1 = [ ( i , i * - 2 ) f o r i i n range ( 4 ) ] print ( ' \ nTupl e s : ' ) p r i n t ( ' o r i g i na l : ' , 1 ) i t emge t t e r ( l ) g va l s = [ g ( i ) for i i n 1 ] print ( ' v a l ue s : ' , va l s ) s o r t ed : ' , s o r t e d ( l , key=g ) ) print ( ' =

Гмва з. АN'орИNы

21 8

Получатели элементов работают как с последовательностями, так и с отобра­ жениями . $ python3 ope r a t o r_ i t emge t t e r . p y Di c t i onar i e s : o r i g i na l : [ { ' va l ' : 0 } , { ' va l ' : - 1 } , va l ue s : [ О , - 1 , - 2 , - 3 ] s o r t e d : [ { ' va l ' : - 3 } , { ' va l ' : - 2 } , Tupl e s : o r i g i na l : values : s o rted :

{ ' va l ' : - 2 } ,

{ ' va l ' : - 3 } ]

{ ' va l ' : - 1 } ,

{ ' va l ' : 0 } ]

[ ( О , 0 ) , ( 1 , -2 ) , ( 2 , -4 ) , ( 3 , - 6 ) ) [ О , -2 , - 4 , - 6 ] [ ( 3 , - 6 ) , ( 2 , -4 ) , ( 1 , -2 ) , ( 0 , 0 ) ]

3 . 3 . 7. С оч ета н ие о ператор ов с п ол ьз овател ьскими кл ассами Функции, содержащиеся в модуле operator, выполняют свойственные им опе­ рации с использованием стандартных интерфейсов Pytlюn. Таким образом, они способны работать с пользовательскими классами так же, как и со в 9 . 2 f } ' . fo rma t ( s t a r t } ) print ( ' e nd { : > 9 . 2 f } ' . f o rma t ( end } ) { : > 9 . 2 f } ' . f o rmat ( end - s t a rt } } pr i n t ( ' span

Для монотонных часов начало отсчета не определено, поэтому возвращаемые ими значения полезны л ишь для выполнения вычислений совместно со значени­ ями других часов. В данном примере функция monoton i c ( ) используется для из­ мерения длительности паузы. $ python3 t ime_mon o t oni c . py start end span

172336 . 14 172336 . 24 0 . 10

4. 1 .4. Процессорное время В то время как функция time ( ) возвращает значение текущего времени, функ­ ция c l o c k ( ) возвращает значения процессорного времени, которые отражают нремя, фактически затраченное процессором на обработку выполняющейся про­ граммы.

241

4.1. tlme: сисrемное времн

Л истинг 4.5. time_clock . py i mp o r t hashl ib i mpo r t t ime 1 Данные , исполь зуемые для расчета контроль ных сумм md5 data = open ( f i l e , ' rb ' ) . r ead ( )

f o r i in range ( 5 ) : h hashlib . sha l ( ) p r i n t ( t ime . ct ime ( ) , ' : ( : 0 . 3 f } ( : 0 . 3 f } ' . f o rma t ( t ime . t ime ( ) , t ime . cl o c k ( ) ) ) f o r i in range ( 3 0 0 0 0 0 ) : h . upda t e ( da t a ) c ksum h . digest ( ) =

=

В этом примере форматированные значения ct ime ( ) выводятся на каждой итерации цикла вместе со значениями с плавающей точкой, возвращаемыми функциями t ime ( ) и clock ( ) .

Примечание

Если захотите выполнить данный пример на своем комп ьютере, вам, возможно, придется увеличить количество итераций внутреннего цикла или использовать более крупный на­ IЮр данных, чтобы заметить разницу в выводимых значениях. $ python3 time_cl o c k . py

Sun S un S un S un S un

Aug Aug Aug Aug Aug

14 14 14 14 14

14 : 14 : 14 : 14 : 14 :

10 : 32 1 0 : 32 1 0 : 33 10 : 33 10 : 33

2 01 6 2016 2016 2016 2016

1 4 7 1 1 98 2 3 2 . 327 1 4 7 1 1 9 8 2 32 . 7 05 1 4 7 1 1 98 2 3 3 . 08 6 1 4 7 1 1 98 2 3 3 . 4 66 1 4 7 1 1 98 2 3 3 . 8 4 2

0 . 033 о . 409 0 . 787 1 . 166 1 . 54 0

Как правило, в периоды бездействия программы отсчет процессорного време­ ни не производится.

Л истинг 4.6. time_clock_sleep . ру i mport t ime t emp late

=

' ( } - ( : 0 . 2f } - { : 0 . 2f } '

p r i n t ( t empl a t e . f o rmat ( t ime . ctime ( ) , t ime . t ime ( ) , t ime . cl o ck ( ) )

for i in range ( 3 , О , - 1 ) : p r i n t ( ' S l e ep i ng ' , i ) t ime . s l eep ( i ) p r i n t ( templ a t e . format ( t ime . ct ime ( ) , t ime . t ime ( ) , t ime . cl o c k ( ) )

IАева 4. � м времн

242

В этом примере цикл выполняет совсем небольшой объем работы , органи­ зуя короткие паузы после каждой итерации. Значение, возвращаемое функцией t ime ( ) , увеличивается даже во время бездействия приложения, чего нельзя ска­ зать о значениях, возвращаемых функцией clock ( ) . $

python3



t ime_c l o c k_s l e e p . py

1 4 : 1 0 : 34 2 0 1 6 - 1 4 7 1198234 . 2 8 - 0 . 03

Sun Aug 1 4 S l eeping 3 Sun Aug 1 4 S l eeping 2 Sun Aug 1 4 S l eeping 1 S un Aug 1 4

14 : 1 0 : 37 2 0 1 6 - 1 4 7 1 198237 . 2 8 - 0 . 03 14 : 10 : 39 2016 - 1471198239 . 29 - 0 . 03 1 4 : 10 : 40 2016 - 147 1198240 . 29 - 0 . 03

Вызов функции s leep ( ) приостанавливает выполнение текущего потока и за­ прашивает для него ожидание до тех пор, пока он не будет пробужден системой. Если программа выполняется только в одном потоке, то вызов этой функции фак­ тиче d2 ) =

Гмва 4. fJ/n8 и 11Р8Мf1

Подцерживаются все операторы сравнения. $ python3 d a t e t ime_compa r i n g . py T ime s : t l : 12 : 55 : 0 0 t2 : 1 3 : 05 : 00 t l < t 2 : T rue Da te s : dl : 2 0 1 6- 0 7 - 1 0 d2 : 2 0 1 6 - 0 7 - 1 1 d l > d2 : Fa l s e

4. 2 .6. Объединение значений даты и времени Для хранения значений, включающих оба компонента, дату и время , исполь­ зуйте класс da t e t ime. Как и в случае класса da t e , для создания экземпляров da tet ime из других распространенных значений предусмотрено несколько удоб· ных методов класса. Листинг 4.23. datetime date time . ру _

impo rt d a t e t ime p r i n t ( ' Now : ' , d a t e t i me . da t e t ime . now ( ) ) p r i n t ( ' Today : ' , da t e t ime . dat e t ime . today ( ) ) p r i n t ( ' UTC Now : ' , da t e t ime . da t e t ime . utcnow ( ) ) print

F I ELDS [ ' yea r ' , ' month ' , ' da y ' , ' hou r ' , ' minute ' , ' s e cond ' , ' mi c ro s econd ' , =

d d a t e t i me . da t e t ime . now ( ) f o r a t t r i n F I ELDS : p r i n t ( ' { : 1 5 } : { } ' . fo rma t ( a t t r , get a t t r ( d , a t t r ) ) )

Как и можно было ожидать, экземпляр da t e t ime имеет все атрибуты, свой­ ственные как экземпля рам da te, так и экземплярам t ime. $ python3 d a t e t ime_datet ime . py Now 2 0 1 6- 0 7 - 1 0 1 0 : 4 4 : 5 5 . 2 1 5 6 7 7 Today 2 0 1 6- 0 7 - 1 0 1 0 : 4 4 : 5 5 . 2 1 5 7 1 9 UTC Now : 2 0 1 6 - 0 7 - 1 0 1 4 : 4 4 : 5 5 . 2 1 5 7 3 2 year 2016 7 month day 10 hour 10 minute 44

4.2. datetlme:

манмnуАМрованме эначенмнмм А8ТЫ м времени

s e cond m i c r o s e cond

55 2 1 6 1 98

Как и класс da t e , класс da t e t ime предоставляет удобные методы класса для создания новых экземпляров. В их число входят методы f rornordinal ( ) и frorntime s t amp ( ) . Листинг 4.24. da te time_da tetime_comЬine . ру i mpo r t da t e t ime t da t e t ime . t ime ( l , 2 , print ( ' t : ' , t ) =

3)

d date time . da t e . today ( ) print ( ' d : ' , d ) =

dt da t e t ime . da t e t ime . comЬi ne ( d , t ) p r i n t ( ' dt : ' , d t ) =

Метод cornЬine ( ) создает экземпляр dat e t irne и з одного экземпляра d a t e и одного экземпляра t irne. $

python3 d a t e t ime_da t e t ime_comЬ i n e . py

01 : 02 : 03 t 2 0 1 6-07 - 1 0 d dt : 2 0 1 6 - 0 7 - 1 0 0 1 : 0 2 : 0 3

4.2. 7. Форматирование и анализ значений По умолчанию в качестве строкового представления объекта da tet irne исполь­ зуется формат IS0-860 1 (ГГГГ-ММДДТЧЧ : ММ : СС . мммммм ) . Функция s t r f t ime ( ) по· зволяст генерировать другие форматы. Листинг 4. 25. datetime_datetime_s trptime . py impo rt d a t e t ime f o rma t = " % а %Ь %d % H : % M : % S % У " today da t e t ime . da t e t ime . today ( ) p r i n t ( ' I SO : ' , t o da y ) =

s = today . s t r ft ime ( f o rma t ) p r i n t ( ' s t r f t ime : ' , s ) d = d a t e t ime . da t e t ime . s t rpt ime ( s , f o rma t ) p r i n t ( ' s t rpt ime : ' , d . s t r f t ime ( f o rma t ) )

Для преобразования форматированных строк в экземпляры da t e t ime исполь­ зуйте функцию dat e t irne . s t rpt ime ( ) .

rАВ88 4. /J1JТВ м время

258 $ python 3 da t e t ime_da t e t ime_s trpt ime . py 2 0 1 6- 0 7 - 1 0 1 0 : 4 4 : 55 . 3 2 52 4 7 rso s t rf t ime : Sun Jul 1 0 1 0 : 4 4 : 5 5 2 0 1 6 s t rp t ime : Sun Ju l 1 0 1 0 : 4 4 : 5 5 2 0 1 6

Те же коды форматирования можно использовать в мини-языке форматирова­ ния строк Pythoп2, помещая их после двоеточия ( : ) в поле спецификации формата. Листинг 4.26. datetime_form.at . py i mpo r t datet ime t oday da t e t ime . d a t e t ime . t oda y ( ) p r i n t ( r so : toda y ) p r i n t ( ' fo rma t ( ) : { : % а % Ь % d % H : % M : % S % Y } ' . f o rma t ( toda y ) ) = 1

1

,

Таблица 4. 1 . Коды формата функций s trptiшe O /s trftime ( ) Символ Наз начен ие Сокращенное название дня недели

' Wed '



Полное название дня недели

' Wedne sday '

%w

Десятичное представление дня недели: от о (воскресенье) до 6 (суббота)

'3'

%d

Десятичное представление числа месяца (дополнение ведущим нулем)

' 13 '



Сокращенное название месяца

' Jan '

%8

Полное название месяца

' January '

%m

Десятичное представление месяца

' 01 '



Десятичное представление года без указания века

' 16'



Десятичное представление года с указанием века

' 2016'



Десятичное представление часа (24-часовая шкала)

' 17 '

%I

Десятичное представление часа (1 2-часовая шкала)

1

05 '



АМ/РМ (до полудня/после полудня)

' РМ '



Десятичное представление минут

' 00 '

%S

Десятичное представление секунд

' 00 '

%f

Десятичное представление микросекунд

' 0 00000 '

Смещение относительно времени UТС для объектов, получающих информацию о часовом поясе

' -0500 '

%z %Z

Название часового пояса

' EST '

%j

Десятичное представление дня года

' 0 13 '

Номер недели в году

1

%W



02 '

Дата и время в представлении текущей локали

' Wed Jan 1 3 17 : 0 0 : 0 0 2016'

Дата в представлении текущей локали

' 0 1 /13/1 6 '



2

П ри ме р



ht tps : / /docs . python . o rg / 3 . 5 / l ibrary/ s t r i ng . html # fo rma t spec

4.2. datetlme: •нмnумр088ние аначениRми А81Ы и времени

257 Окон1�ание 1пабл.

Сиt1вол

Назначение

в представлении текущей локали



Время

%%

Литеральный символ %

4. 1

П р имер

' 1 7 : 0 0 : 00 ' '

%

'

Каждый код формата представления даты и времен и должен начинаться с пре­ фикса % . Все 11оследующие двоеточия интерпретируются как J1итеральные симво­ лы и включаются в вывод. $ python 3 datet ime_f o rma t . py I SO : 2 0 1 6 - 0 7 - 1 0 1 0 : 4 4 : 5 5 . 3 8 9 2 3 9 forma t ( ) : Sun Jul 1 0 1 0 : 4 4 : 5 5 2 0 1 6

Представленные в табл. 4 . 1 коды форматирования соответствуют моменту времени 17:00 13 января 2016 года для часового пояса U .S. /Easterп.

4.2.8. Час о в ы е п о яса В классе da tet ime часовые пояса представлены подклассами t z in fo. Посколь­

ку t z info - абстрактный базовый класс , приложение должно определить соб­

ственный подкласс и предоставить соответствующие реализации нескольких ме­ тодов. МодуJ1ь da t e t ime включае1· тривиа.11ьную реаJ1изацию в классе t ime zone, в ко­ торой используется фиксированное смещение относитеJ1ь110 UТС. Эта реализа­ ция не 1 1оддерживает различные значения смещения в различные дни I'Ода, что, на11ример, требуется для учета перехода на летнее время или в тех сJ1учаях, когда смещение относительно UTC может меняться со временем. Листинr 4.27. da te time_timezone . py impo r t da t e t ime mi n б = d a t e t ime . t ime zone ( da t e t ime . t imed e l t a ( hours=- 6 ) ) p l u s б = d a t e t ime . t ime zone ( da t e t ime . t ime de l t a ( hours= 6 ) ) d = date t ime . da t e t ime . now (mi n б ) pr i n t (mi n б , ' : ' , d ) p r i n t ( da t e t ime . t ime z one . ut c , ' : ' , d . a s t ime zone ( da t e t ime . t ime z one . ut c ) ) p r i n t ( p l u s 6 , ' : ' , d . a s t ime z one ( p l u s 6 ) ) # Преобразовать в текущий си стемный ч а с о в ой пояс d_s y s t em = d . a s t ime zone ( ) : ' , d_s ys tem) pr i n t ( d_s ystem . t z i n f o , '

Преобразован ие значения dat e t ime одного часово�·о пояса в друl'Ой осущест­ вляется с помощью метода a s t ime zone ( ) . В предыдущем примере представле­ ны два разных часовых 11ояса, смещенных на 6 часов по обе стороны от времени UTC, которое представлено атрибутом utc класса date t ime . t ime zone. В послед­ ней строке вывода отоб ражается значение системного часового пояса, получен­ ное посредством вызова метода a s t ime zone ( ) без аргумента.

258

rA888 4. ,_,,, и время

$ pythonЗ d a t e t irne_t ime z one . py UTC- 0 6 : 0 0 UTC+O O : O O UTC + 0 6 : 0 0 E DT

2 0 1 6-07-10 2 0 1 6- 07 - 1 0 2016-07-10 2016-07-10

08 : 4 4 14 : 44 20 : 44 10 : 44

: : : :

55 . 495995-06 : 5 5 . 4 9 5 9 95+0 0 : 5 5 . 4 95 9 95+ 0 6 : 55 . 4 95995-04 :

00 00 00 00

П римечание

Сторонний модуль pyt z 3 предлагает лучшую реализацию для работы с часовыми пояса­ ми. Он поддерживает именованные часовые пояса и обновляет базу данных смещений относительно UTC в случае принятия теми или иными государствами решений об их изме­ нении. Дополнительные сс ылки • •

• • •



4

Раздел документации стандартной библиотеки, посвященный модулю da t e t irne . Замечания относительно портирования программ из Pythoп 2 в Pythoп 3, касающиеся модуля datet ime (раздел А.6. 1 3). c a l enda r (раздел 4.3). Описание модуля cal endar. t i me (раздел 4. 1 ). Описание модуля t ime. 5 dateut i l . Утилита dateut i l , разработанная компанией Lablx, расширяет модуль dat e t ime, добавляя в него новые возможности. 6 pyt z • База данных и классы для создания объектов da t e t irne, имеющих доступ к акту­

альной информации о часовых поясях. •

Википедия: Пролептический rриrорионский колендорь7 . Описание григорианской ка­ лендарной системы.



Википедия : /50 860 1 8. Стандарт, определяющий правила числового представления значений даты и времени.

4.3. calendar: работа с датами Модуль calendar определяет класс Ca lendar, инкапсулирующий вычисление таких значений, как даты дней недели в заданном месяце или году. В дополнение к этому классы TextCalendar и HTMLCa lendar позволяют полу чать предваритель­ но отформатированный вывод.

4. 3 . 1 . Примеры форматирования Метод prrnonth ( ) - это простая функция, обеспечивающая форматированный текстовый вывод для указанного месяца. 3

h t t p : / /pyt z . s ourceforge . ne t /

4

h t t ps : / / docs . python . o rg / 3 . 5 / l ibr a r y / da t e t ime . html

5

http : / / l aЬix . o r g /python-da teut i l http : / /pyt z . sou rce forge . ne t /

6 7 8

h t tps : / / ru . wi kipedi a . org /wi ki /Пpoл eптичecкий григорианский календарь -

h t tps : / / ru . wi kipedi a . o r g / wi k i / I S O 8 6 0 1

-

Л истин r 4.28. calendar textcalendar . ру _

impo r t c a l endar с cal enda r . TextCa l e nda r ( ca l enda r . SUNDAY J c . p rmonth ( 2 0 1 7 , 7 ) =

В следующем примере для класса TextCa lendar в соответствии с принятым в США соглашением в качестве начального дня недели установлено воскресенье (Su11day) . По умолчанию используется принятое в Европе соглашение, в соответ­ ствии с кото р ым началом недели считается понедельник. Ниже приведен резуль­ тат выполнения этого примера. $ python3 c a l enda r_t e x t c a l enda r . py Jul y 2 0 1 7 S u М о T u W e Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 2 6 27 28 29 30 31

Аналогичную НТМL·таблицу можно получить с помощью класса HTMLCalenda r и метода formatmont h ( ) . В этом случае визуализируемый вывод вы1:11ядит в основ· ном так же, как и его текстовая вер8 } '

for а , Ь , re l_t o l in I N PUTS : close mat h . i s c l o s e ( a , Ь , r e l_t o l = r e l_t o l ) t o l erance = re l_t o l * max ( abs ( a ) , abs ( b ) ) abs_di f f = abs ( a Ь) p r i n t ( fmt . forma t ( a , Ь , re l_t o l , ab s_di f f , t o l e r ance , c l o se } } =

-

Результат сравнения значений О . 1 и О . 0 9 оказывается отрицательным из-за ошибки , связанной с представлением значения О . 1 . $ pythonЗ ma t h_i s c l o s e . py -

а ь re l --- - - - 1000 . 00 900 . 00 90 . 0 0 100 . 00 9 . 00 10 . 00 1 . 00 0 . 90 0 . 10 0 . 09 --

-

-

----

tol

--------

0 . 10 0 . 10 0 . 10 0 . 10 0 . 10

abs ( a - b ) t o l e ra n c e c l o s e ---- - -- - - - - T rue 100 . 00 1 00 . 00 T rue 1 0 . 00 10 . 00 T rue 1 . 00 1 . 00 T rue 0 . 10 0 . 10 0 . 01 0 . 01 Fa l s e - - - --

-

---

-

Чтобы использовать фиксированный или "абсолютный" допуск, следует пере­ дать функци и i s close ( ) аргумент abs_tol вместо ар1-умента rel_to l . Листинг 5. 36. math_i sclose_aЬs_tol . py import ma t h I N PUTS [ ( 1 . 0 , 1 . 0 + le-07 , ( 1 . 0 , 1 . 0 + l e- 0 8 , ( 1 . 0 , 1 . 0 + l e- 0 9 , =

le- 08 } , le-08 } , le-08 ) ,

print ( ' { : л 8 } { : л l l } { : л 8 } { : л 1 0 } { : л 8 } ' . fo rmat ( ' а ' , ' Ь ' , ' abs_t o l ' , ' abs ( a -b ) ' , ' cl o s e ' ) print ( ' { : - л 8 } , _ ,

, _ ,

{ : -л l l } { : - л 8 } { : -л l О } - , '), '- , '

'

'

{ : - л 8 } ' . fo rma t (

' -

for а , Ь , abs_t o l i n INPUT S : close math . i s c l o s e ( a , Ь , abs_t o l =abs_t o l ) abs_d i f f = abs ( a - Ь ) p r i nt ( ' { : 8 . 2 f } { : 1 1 } { : 8 } { : 0 . 9 f } { ! s : > 8 } ' . fo rmat ( а , Ь, abs_t o l , ab s_di f f , cl o s e } ) =

В случае абсолютного допуска разница между входными значениями должна быть меньше заданного значения.

6.4. mвth: М818М81Мческие ФУн�аuси

$ pythonЗ ma t h_i s c l o s e_abs_t o l . py а

--------

ь

-----------

a b s ( а -Ы close Fa l s e le-08 0 . 000000100 le-08 0 . 000000010 True True le-08 0 . 000000001

abs t o l

- -- - - - - -

1 . 00 1 . 0000001 1 . 00 1 . 00000001 1 . 00 1 . 000000001

-------

--

--------

Значения nan и inf представляют специальные случаи. Листинг 5.37. math_i sclose_inf . ру import ma t h print print print print

( ( ( (

' na n , nan : ' , ' na n , 1 . 0 : ' , ' in f , inf : ' , ' inf, 1 . 0 : ' ,

ma t h . i s cl o se ma t h . i s c l o se ma th . i s c l o se ma t h . i s c l os e

( ma t h . na n , ( ma t h . na n , ( ma t h . i n f , ( ma t h . i n f ,

ma t h . na n ) } 1.0) } ma t h . i n f ) ) 1 .0) }

�Jначение nan не может быть близким ни к какому другому значению, включая самого себя. Значение inf близко только к самому себе. $ pyt honЗ ma t h_i s cl o s e_i n f . py n a n , nan : Fa l s e nan , 1 . 0 : Fa l se i n f , i n f : T rue i n f , 1 . 0 : Fa l se

5 .4 . 4. Преобр аз о вание значений с плавающей точкой в целые числа Модуль math включает три функции, предназначенные для преобразования зна•1ений с плавающей точкой в целые чисJ1а. В этих функциях испольауются раз­ н ые подходы к выпшшению да111ю1·0 преобразования, и каждая из них оказывае1"' ся удобной в тех иJ1и и ных обстоятельствах. Самая простая из них - функция trunc ( ) которая отбрасывает дробную часть, оставляя только целочисленную часть значения. Функция floor ( ) окру­ гляет значение до наибольшего из предшествующих 1�елых чисел, а функция ce i l ( ) - до наименьшего из следующих целых чисел. ,

Листинг 5 . 3 8. math_inteqers . py impo r t ma t h HEADI N G S ( ' i ' , ' i nt ' , ' t run k ' , ' f l o o r ' , ' ce i l ' ) p r i n t ( ' ( : л S } { : л 5 } { : л 5 } { : л 5 } { : л 5 } ' . f o rma t ( * HEADI NGS ) ) p r i nt ( ' { : - л S } ( : - л 5 } { : - л 5 } { : - л S } { : - л S } ' . fo rma t ( =

1 1

1 1

' 1

t 1

1 1

)} fmt

' { : 5. 1f}

{ : 5 . 1 f } { : 5 . 1f }

{ : 5 . 1f}

{ : 5. 1f} '

ГА888 15. Матем81Мка TEST VALU ES - 1 . 5, -0 . 8, -о . 5, -0 .2, о,

0.2, 0 . 5, О. В, 1,

for i i n TEST VALUES : p r i n t ( fmt . f o rma t ( i, int ( i ) , ma th . t runc ( i ) , ma th . f l o o r ( i ) , ma t h . ce i l ( i ) , ) )

Функция trunc ( ) эквивалентна непосредственному преобразованию в тип

int.

$ pythonЗ ma t h_ i n te g e r s . py i

int

t runk fl oor ce i l

----- - - - - -

-1 . 5 -о . в -о . 5 -0 . 2

-1 . О о. о

-1 . О

о.о

о.о

о.о

о.о

-1 . 0

о.о

-2 . О -1 . 0 -1 . О -1 . О

о . о

о . о

о . о

о.о

0.2 0.5

о . о

о.о

о.о

о.о

о.о

о.о

о.в

о.о 1.0

о.о

о.о

1.0

1.0

1.0 1.0 1.0 1.0

1.0

о.о

о.о о.о

о.

о

5.4 . 5 . Ал ьтернативные представления значе н ий с плавающей то ч кой Функция modf ( ) получает единственный аргумент в виде числа с плавающей точкой и возвращает кортеж, содержащий дробную и целую части входного зна­ чения . Листинг 5 . 39. math_modf . ру impo r t ma t h f o r i i n range ( б ) : p r i nt ( ' { } / 2 = { } ' . fo rma t ( i , ma th . modf ( i / 2 . 0 ) ) )

5А. mвth: 11111'81181ИЧ8СКМ8 �IЩll И

217

Оба числа в возвращаемом значении .являются числами с плавающей точкой.

$

python3 math_mod f . py (0. (0. (о. (о. (О. (0.

0/2 1/2 2 /2 3/2 4 /2 5/2

О, 5, о, 5, О, 5,

О. 0) о. 0) 1. о) 1 . о) 2 . 0) 2.0)

Функция frexp ( ) возвращает ман·rиссу и экспоненту числа с 1V1авающей точ­ кой. Эту функцию можно использовать д11я соадания представления значения в более переносимой форме. Листинг 5.40. math_frexp . py impo r t ma t h p r i nt ( ' { : " 7 } { : " 7 ) { : " 7 } ' . f o rma t ( ' x ' , ' m ' , ' е ' ) ) pri nt ( ' { : - " 7 } { : - " 7 ) { : - " 7 } ' . forma t ( ' ' , ' ' , ' ' ) ) for х i n [ 0 . 1 , 0 . 5 , 4 . 0 ] : m, е = ma t h . f rexp ( x ) p r i nt ( ' { : 7 . 2 f } { : 7 . 2 f }

{ : 7 d } ' . fo rmat ( x , m, е ) )

Функция frexp ( ) использует формулу х = m * 2 * * е и возвращает значения m и е.

$

python 3 ma th_frexp . py

х - -- -0. 10 0 . 50 4 . 00 -

-

-

m ---- о . во 0 . 50 0 . 50 -

---

е -

---

-3 о 3

Функция ldexp ( ) - обратная 110 оnюшению к функции frexp ( ) . Листинг 5.41 . math_ldexp . py import ma t h p r i nt ( ' { : " 7 } { : " 7 } { : " 7 } ' . fo rmat ( ' m ' , ' е ' , ' х ' ) ) p r i n t ( ' { : - " 7 ) { : - " 7 } { : - " 7 } ' . fo rma t ( " , " , " ) ) IN PUT S [ ( 0 . 8, -3) , ( 0 . 5, 0 ) , ( 0 . 5, 3) , =

f o r m, е i n INPUТ S : х = ma th . l dexp (m, е ) p r i nt ( ' { : 7 . 2 f } { : 7 d }

{ : 7 . 2 f } ' . fo rmat ( m , е , х ) )

Гм• 5. Мвntмаntкв

Используя ту же формулу, что и функция frexp ( ) , функция ldexp ( ) получает значения мантиссы и :экспоненты и возвращает число с плавающей точкой. $ pythonЗ ma th_l dexp . py х е m - - -- - -- - - - ---- - - --- - 0 . 10 -3 о . во о 0 . 50 0 . 50 4 . 00 0 . 50 з

5 .4.б. З н ак числа Абсолютная величина числа - :это значение числа с отброшенным знаком. Для вычисления абсолютной величины чис.ла с плавающей точкой используется функция fabs ( ) . Листинг 5.42. ma th_faЬs . ру imp o r t ma t h print print print print

( ma t h . ( ma t h . ( ma t h . ( ma th .

fabs fabs fabs fabs

( -1 . 1 ) ) (-0 . 0 } ) ( 0 . 0} ) (l.1) }

С практической точки зрения абсолютная величина числа с плавающей точ· кой представляется положительным значением. $ pythonЗ ma th_fabs . py 1.1 о.о о.о

1.1

Если требуется определить знак числа, будь то для присвоения его набо­ ру значений или для сравнения с другими значениями, используйте функцию copys ign ( ) , позволяющую установить знак для любого корректного значения. Листинг 5 .43. math_copys ign . py imp o r t ma t h HEADINGS ( 'f' , 's', '< О' , '> О' , '= О' ) pr i nt ( ' { : л S } { : л 5 ) { : л 5 } { : л 5 } { : л 5 } ' . f orma t ( * HEADI NGS ) ) pr i n t ( ' { : - л 5 } { : - л 5 } ( : - л 5 ) { : - л 5 ) { : - л 5 } ' . f orma t ( =

1 '

' 1

) ) VALUES [ -1 . О, О . О, 1 . О, =

1 1

1 1

1 1

5.4. math: 11818МIПМЧ8СК11е фунiсWtИ float float float float

( ( ( (

' -inf ' ' inf ' ) ' -nan ' ' nan ' )

211

), , ), ,

for f in VALUES : s i n t { ma t h . copys i g n { l , f ) ) p r i nt ( ' { : 5 . l f } { : 5d } { ! s : 5 } { ! s : 5 } f , s ' f < о ' f > о ' f == о , ) ) =

{ ! s : 5 } ' . fo rma t (

Дополнительная функция наподобие copys ign ( ) нужна по той причине, что непосредственное сравнение nan и -nan с другими значениями не работает.

$

python З math_copys i gn . py f -1 . О о.о

1.0 -inf inf nan nan

s




о

- 1 T rue 1 Fa l s e 1 Fa l s e - 1 True 1 Fal s e - 1 Fa l se 1 Fa l s e

о

Fa l s e Fa l se True Fa l s e True Fa l se Fa l se

о

Fa l se True Fa l s e Fa l s e Fa l s e Fa l s e Fa l s e

5.4. 7. Рас п ростране нны е виды вы ч исле н ий Точное представление значений с плавающей точкой в двоичном виде в па· мяти машины довольно проблематично. Некоторые значения вообще не могут быть представлены точно. Кроме того, чем чаще такое значение используется в расчетах, тем выше вероятность того, что влияние ошибки представлен ия будет только возрастать. Модуль math включает функцию, предназначенную для вычис· ления суммы последовательности чисел с плавающей точкой с использованием эффективного алгоритма, ко1'0рый минимизирует подобные ошибки. Листинг 5 . 44 . math_fsum . py

import math va l u e s = [ О . 1 ]

*

10

p r i n t ( ' I nput va lue s : ' , val u e s ) p r i n t ( ' s um ( )

:

{ : . 2 0 f } ' . forma t ( s um ( va l ues ) ) )

s = о.о for i i n value s : s += i p r i n t ( ' f o r - l o op

{ : . 2 0 f } ' . forma t ( s ) )

pri n t ( ' ma th . f s um ( )

{ : . 2 0 f } ' . fo rma t ( ma t h . f s um { va l ue s ) ) )

Гмва 5. МатеМ81МIС8

300

Для последовательности 10 значен и й , каждое из которых равно О . 1 , ожидае­ мое значение суммы равно 1 . О. Однако ввиду того, что значение О . 1 невозможно представить точно в машинной памяти , эти ошибки, если не использовать функ­ цию fsum ( ) , накапливаются в сумме. $ python 3 math_f sum . py I nput v a l u e s : ( 0 . 1 , 0 . 1 , 0 . 1 , 0 . 1 , 0 . 1 , 0 . 1 , 0 . 1 , s um ( ) 0 . 99999999999999988898 fo r - l oop 0 . 99999999999999988898 ma th . fsum ( ) 1 . 00000000000000 0 0 0000

0 . 1,

0 . 1, 0 . 1]

Функцию fac t o r i a l ( ) обычно используют для расчеrа количества перестано­ вок или сочетаний элементов последовательности. Факториал положительного числа п, обозначаемый как п!, равен проиаведени ю всех натуральных чисел от 1 до п включительно и рассчитывается по рекуррентной формуле ( n- 1 ) ! * n, в ко­ торой , в соответствии с общепринятым соглашением, принимается , что О ! == 1 . Листинг 5 .45. math_factorial . ру import ma th for i in [ О , 1 . 0 , 2 . 0 , 3 . 0 , 4 . 0 , 5 . 0 , 6 . 1 ] : t ry : p r i n t ( ' { : 2 . 0 f } { : 6 . 0 f } ' . fo rma t ( i , ma t h . fa c t o r i a l ( i ) ) ) except Va l u e E r r o r a s e r r : p r i n t ( ' E r r o r computing fact o r i a l ( { } ) : { ) ' . f o rma t ( i , e r r ) )

Функция factor ial ( ) работает только с целыми числами, но может получать также аргументы в виде чисел с плавающей точкой, коль скоро они могут быть преобразованы в целые значения без потери значения. $ python3 ma th_f a c t o r i a l . py 1 1 1 2 2 3 6 4 24 120 5 E r r o r compu t i ng fact o r i a l ( 6 . 1 ) : fac t o r i a l ( ) onl y a c cept s i n t e g ra l va l ue s о

Функция garnma ( ) похожа на функцию fact o r i a l ( ) , но работает с веществен­ ными числами, а факториал вычисляется для значения аргумента, уменьшенного 1 ) ! ). на 1 (гамма-функция равна ( n -

Листинг 5 .46. math_gamma . py irnpo r t ma th for i i n [ О , t ry :

1 . 1, 2 . 2,

3 . 3, 4 . 4,

5 . 5,

6 . 6] :

5.4. mllth: метем81Мческие фун1СЦМм

301

p r i nt ( ' ( : 2 . l f } ( : 6 . 2 f } ' . fo rma t ( i , rna t h . gaпuna ( i ) ) ) except Va l ue E r r o r a s e r r : p r i n t ( ' E r r o r cornput i ng gaпuna ( ( } ) : ( } ' . fo rmat ( i , e r r ) )

Нулевое значение аргумента недопустимо, поскольку оно приводит к отрица­ тельному начальному значению факториала.

$

python3 rna t h_gaпuna . py

E r r o r cornput i ng gaпuna ( O ) : rna th doma in e r r o r О . 95 1.1 1 . 10 2.2 2 . 68 3.3 4.4 1 0 . 14 5 . 5 52 . 3 4 6 . 6 344 . 70

Функция lgaroma ( ) возвращает натуральный логарифм абсолютной величины гамма-функции для входного значения. Листинг 5 .47. math_ lgamma . py import rna th for i in [ О , 1 . 1 , 2 . 2 , 3 . 3 , 4 . 4 , 5 . 5 , 6 . 6 ] : try : print ( ' ( : 2 . l f l { : . 2 0 f } ( : . 2 0 f } ' . fo rrna t ( i, rnath . l g aпuna ( i J , rnath . l og ( rna t h . gaпuna ( i ) ) , ) ) except Va l u e E r r o r a s e r r : p r i n t ( ' Er r o r comput i ng l g arnma ( { } } : { } ' . fo rma t ( i , e rr ) )

Использование функции lg arnrna ( ) поаволяет увеличит�, точность конечного ре­ аультата по сравнению с независимым вычислением гамма-функции и логарифма. $ python3 rna t h_l garnma . py E r r o r cornpu t i ng l garnma ( O ) : rna t h dorna i n e r ro r 1 . 1 -0 . 04987 2 4 4 12 5984 036103 -0 . 04 9872 4 4 12 59839972 45 2 . 2 0 . 0 9 6 9 4 7 4 6 67 9 0 63 8 2 5 92 3 0 . 0 9 694 7 4 6 6 7 90 6 3 8 6 6 1 6 8 3 . 3 0 . 98709857 7894 7 3387513 0 . 987 09857 789473409717 4 . 4 2 . 3 1 6 1 0 3 4 9 1 4 2 4 8 57 2 7 4 6 9 2 . 3 1 6 1 0 3 4 9 1 4 2 4 8 5 7 2 7 4 6 9 5 . 5 3 . 9578 1 3 9 67 6 1 8 7 1 651080 3 . 957 8 1 3967 6 1 8 7 1 60667 1 6 . 6 5 . 8 42 6 8 0 0 5 52 7 4 632522 3 6 5 . 8 4 2 68 00552 7 4 632 5 2 2 3 6

Оператор деления по модулю ( % ) вычисляет остаток от деления (например, 5

% 2 = 1 ) . Встроенный оператор яаыка хорошо работает с целыми числами , но,

как и в случае многих дру1·их операций , вьшолняемых над числами с плавающей точкой, с промежуточными вычислениями связана проблема точности представ­ ления чисел этого типа в машинной памяти , что может приводить к потере дан-

П\вва 5. Мвтемвwмка

302

ных. Функция fmod ( ) предоставляет более точную реализацию делен ия по мо­ дулю для значений с плавающей точкой. Листинг 5.48. math fmod . ру _

import ma th pri n t ( ' ( : " 4 } ( : " 4 } { : " 5 } { : " 5 } ' . f o rma t ( , х , , , у , , , % , , , fmod ' ) ) p r i n t ( ' ( : - " 4 } ( : - " 4 } { : - " 5 } { : - " 5 } ' . f o rmat ( , _ , '-' ' _ , , ,_, } ) I N PUTS = (5, 2} , ( 5 , -2 ) , ( -5, 2 } ,

for х , у i n I N PUT S : print ( ' ( : 4 . l f } ( : 4 . l f }

( : 5. 2f}

( : 5 . 2 f } ' . fo rma t (

х,

у, х % у,

rna t h . fmod ( x , у ) ,

)) Потенциальным источником недоразумений является тот факт, что алгоритм, используемый функцией fmod ( ) для вычисления остатка, отличается от алгорит­ ма, используемого оператором % , в результате чего получаемые с их помощью ре­ зультаты могут отличаться знаками. $ pythonЗ ma t h_fmod . py у

х

%

frnod

5 . 0 2 . 0 1 . 00 1 . 00 5 . 0 -2 . О -1 . 00 1 . 00 -5 . О 2 . 0 1 . 0 0 - 1 . 0 0

Функция gcd ( ) позволяет находить наибольший общий делитель двух чисел. Листинг 5 .49. math_gcd . py irnport ma t h print print print print print

( ma t h . gcd ( l O , ( ma th . g cd ( l O , ( ma t h . gcd ( 5 0 , ( rna t h . gcd ( l l , ( ma t h . g cd ( O ,

8) ) 0) ) 225) ) 9) ) 0) )

Если оба значения равны

О,

результат равен О .

5.4. rnath: матемаnеческме функции

303

$ python3 ma th_gcd . py 2 10 25 1 о

5.4. 8. Экс п о н е нты и логарифм ы Экспоненциальные кривые встречаются в экономике, физике и других обла­ стях науки. В Pytlюn имеется встроенный оператор возведения в степень ( * * ) , н о в тех случаях, когда другой функции необходимо передать аргумент в виде вызы­ ваемого объекта, может оказаться полезной функция pow ( ) . Листинг 5 . 50. math_;:>ow . py imp o r t ma t h INPUT S = [ # Типичное исполь зов а ние (2, 3) , (2 .1, 3.2) 1

# Всегд а 1

( 1 . О, 5) , (2 . О, 0) , # Не число

( 2 , f l oa t ( ' nan ' ) ) ,

# Корни ( 9 . О, О . 5) , ( 2 7 . 0, 1 . 0 / 3 ) ,

for х , у in I N PUT S : print ( ' { : 5 . l f } * * { : 5 . 3 f ) х , у , ma t h . pow ( x , у ) ) )

=

{ : 6 . 3 f } ' . fo rma t (

Результатом возведения 1 в любую степень, как и результатом возведения лю­ бого числа в степень О . О , все1'Да является 1 . О . В большинстве случаев операции с nan ("не число") возвращают значение nan. Если показатель степени меньше 1 , функция pow ( ) вычисляет корень. $ python3 ma t h_pow . py 2. 2. 1. 2. 2.

0 1 0 0 0

** ** ** ** **

3 . 00 0 3 . 2 00 5 . 000 О . ООО nan

8 . 000 10 . 742 1 . 000 1 . 0 00 nan

ГАаве 5. М818М8n4М

304 9.0 27 . О

** **

0 . 50 0 0 . 333

3 . 00 0 3 . 00 0

Ввиду того что необходимость в вычислении квадратных корней ( показатель степени 1 / 2 ) возникает довольно часто, для этой операции предусмотрена от­ дельная функция. Листинг 5 . 5 1 . math_sqrt . py impor t ma t h print ( ma t h . s qrt ( 9 . 0 ) ) print ( ma t h . s q r t ( 3 ) ) t ry : p r i n t ( ma t h . sqrt ( - 1 ) ) e x cept Va l u e E r r o r a s e r r : p r i n t ( ' Cannot compute sqrt ( - 1 ) : ' , e rr )

Вычисление квадратных корней и з отрицательных значений требует исполь­ зования комплексных чисел, с которыми модуль math не работает. Любая 1юпыт­ ка вычислить квадратный корень из отрицатслыю1·0 числа приводит к ошибке Va lueError. $ python3 ma th_sqr t . py 3.0 1 . 7 32 05 0 8 0 7 5 6 8 8 7 7 2 Cannot c ompute sqrt ( - 1 ) : ma t h doma i n e r ror

Логарифмическая функция находит такое значение у, при котором х Ь * * у. По умолчанюо функция log ( ) вычисляет натуральный логарифм (с основани­ ем е ) . Если используется второй аргумент, то он задает основание логарифма. =

Листинг 5 . 52 . math_log . py import ma t h print ( ma th . l og ( 8 ) ) print ( ma th . l og ( 8 , 2 ) ) print ( ma th . l og ( 0 . 5 , 2 ) )

Логарифмы для значений х меньше 1 являются отрицательными значениями. $ python3 math_l og . py 2 . 07 9 4 4 1 54 1 6 7 9 8 357 3.0 -1 . О

Существуют три разновидности функции log ( ) . При заданном 11редсгавлении числа с плавающей точкой и ошибках округления значение, получаемое с помо­ щью функции log ( х , Ь ) , имеет ограниченную точность, особенно для некоторых оснований логарифмов. Функция log l O ( ) вычисляет результат вызова функции log ( х , 1 0 ) , используя более точный алгоритм, чем функция log ( ) .

305 Л исrинг 5. 53. math_loqlO . ру import ma t h p r i n t ( 1 { : 2 } { : л 1 2 } { : л l О ) { : л 2 0 } { : 8 } 1 . fo rmat ( 1 i 1 , 1 х 1 , 1 a c cur a t e 1 , 1 inaccura t e 1 , 1 mi sma t ch 1 , ) ) p r i nt { 1 { : - л 2 } { : - л 1 2 } { : - л l О } { : - л 2 0 } { : - л 8 } 1 . fo rma t { 1 1 1 ', 1 1, 1 1, 1 1 ! )

f o r 1 1n range ( O , 1 0 ) : х ma t h . pow ( l O , i ) accurate = ma th . l og l O ( x ) i na c curate - ma t h . l o g ( x , 1 0 ) ma tch = 1 1 i f i nt ( i na ccura t e ) == i e l s e 1 * 1 p r i n t ( 1 { : 2 d } { : 1 2 . l f } { : 1 0 . 8 f } { : 2 0 . 1 8 f } { : л 5 } 1 . format { i , х , a c cu r a t e , i naccurate , ma t c h , ) ) =

Замыкающими сим волами 1·аты.

*

в строках вывода обозначены неточные резуль·

$ pythonЗ ma t h_l o g l O . py х

i

a ccurate

i naccurate

0 . 00000000 1 . 00000000 2 . 00000000 3 . 00000000 4 . 00000000 5 . 00000000 6 . 00000000 7 . 00000000 8 . 00000000 9 . 00000000

0 . 000000000000000000 1 . 000000000000000000 2 . 000000000000000000 2 . 999999999999999556 4 . 000000000000000000 5 . 000000000000000000 5 . 999999999999999112 7 . 000000000000000000 8 . 000000000000000000 8 . 9999999999999982 2 4

misma t ch

- - - -- - - - - - - - --- -- - - - - - - - - -- - - - - - - - - - -- - -- - - - - - - -- -

о 1 2 з

4 5 6 7 8 9

1.0 10 . О 1 00 . 0 1000 . О 10000 . О 1 00000 . О 1000000 . О 10000000 . 0 100000000 . О 1000000000 . О

*

*

*

Аналогично функц ии log l O ( ) , функция log2 ( ) 11ычисляет эквивалент резуль· тата вызова функции ma th . log ( х , 2 ) •

Л истинг 5. 54. math_loq2 . ру i mport ma t h pr i n t ( 1 { : > 2 } { : л 5 } { : л 5 } 1 . fo rma t { 1 i 1 , 1 х 1 , 1 1 092 1 , ) ) p r i n t ( 1 { : - л 2 ) { : - л 5 } { : - л 5 ) 1 . fo rmat ( 1 ' 1 ' ' ' ) ) f o r i in range ( O , 1 0 ) : х = ma th . pow ( 2 , i)

ГА888 5. М818Мвтмка

308 result ma t h . l og2 ( x ) p r i n t ( ' { : 2 d } { : 5 . l f } ( : 5 . l f } ' . f orma t ( i , х , r e s ul t , )) =

В зависимости от платформы, встроенные и специал ы1ые функции могут обеспечивать более высокие показатели производительности и точности , ис­ пользуя преимущества специальных алгоритмов для основания 2, •1ем более об­ щие функции .

$

python3 ma t h_l o g2 . py i

х

l og2

1.0 1 2.0 4.0 2 в.о 3 4 16. 0 5 32 . 0 6 64 . 0 7 12В . О в 2 56 . О 9 512 . 0 о

о.о 1.0 2.0 3.0 4.0 5.0 6.0 7.0 в.о

9.0

Функция log l p ( ) используется для вычисления рядов Ньютона-Меркатора (натуральный логарифм 1 + х ) . Листинг 5.55. math_loglp . py import ma t h

х = 0 . 00000000000 00000000000001 pr i nt ( ' x : ' , х) print ( ' 1 + х : ' , 1 + х) p r i n t ( ' l og ( l +x ) : ' , ma t h . l og ( l + х ) ) pr i nt ( ' l og l p ( x ) : ' , ma th . l o g l p ( x ) )

Функция loglp ( ) дает более точные результаты для значений х , близких к нулю, поскольку использует алгоритм , который компенсирует ошибки округле­ ния, возникающие при выполнении начальной операции сложения.

$

python 3 ma t h_l o g l p . py

le-2 5 х 1.0 1 + х l og ( l +x ) : О . О loglp ( x ) : le-25

Функция ехр ( ) вычисляет экспоненциальную функцию ( е * * х ) .

307 .Листинr 5. 5 6. math_exp . py impo rt ma th х = 2

fmt p r i nt print print

' { : . 2 0f} ' ( fmt . fo rmat ( math . e * * 2 ) ) ( fmt . format ( math . pow ( ma t h . e , 2 ) ) ) ( fmt . forma t ( ma t h . exp ( 2 ) ) )

Подобно другим специальным функциям, функция ехр ( ) использует алго­ ритм, позволяющий получать более точные резуш,таты, чем эквивалентная ей универсальная функция math . pow (math . е , х ) . $ pyt hon3 ma t h_exp . py 7 . 389056098 93064 951 876 7 . 38 9056098 93064 951 8 7 6 7 . 3 8 9056098 930650 4 0694

Функция expml ( ) - обратная по отношению к функции loglp ( ) и вычисляет выражение е * * х 1. -

Листинr 5. 57. math_expml . ру impo r t ma t h

х = 0 . 0000000000000000000000001 print ( x ) print (ma t h . exp ( x ) - 1 ) print ( ma t h . expml ( x ) )

При малых значениях х выполнение вычитания как отдельной операции при­ водит к потере точности , аналогично функции loglp ( ) . $ python3 ma t h_expml . py le-25 о.о

le-2 5

5.4. 9. Углы Несмотря на то что в повседневной жизни углы измеряют в градусах, в мате­ матике и /\ругих научных областях в качестве стандартной единицы измерения углов используют радианы. Радиан - это угол между двумя лучами, выходящими из центра окружности , которому соответствует длина дуги окружности, равная радиусу. Длина окружности равна 27tr, поэтому радианы связаны с числом 7t, которое часто используется в тригонометрических расчетах. Существование этого отно-

308

шения приводит к тому, что радианы исполь:Jуются в тригонометрии и диффе­ ренциальном исчислении, придавая формулам более компактный вид. l)>адусы мож но преобра:ювать в радианы с помощью функции radians ( ) . Листинг 5 . 58. math_radians . py irnport rna t h p r i nt ( ' { : л 7 } { : л 7 } { ; л 7 1 ' . fo rrna t ( ' Degrees ' , ' Ra di a n s ' , ' Expe c t e d ' ) ) print ( ' { : - л 7 } { : - л 7 1 { : - л 7 } ' . fo rrnat ( "

"

"

))

I N PUT S [ (0, О) , ( 3 0 , rnat h . pi / 6 ) , ( 4 5 , rnat h . p i / 4 ) , { 6 0 , rnat h . pi / 3 ) , ( 9 0 , rnat h . p i / 2 ) , ( 1 8 0 , rna t h . p i ) , ( 2 7 0 , 3 / 2 . О * rna th . pi ) , ( 3 6 0 , 2 * rna t h . p i ) , =

for de g , e xpe cted in I N PUT S : p r i nt ( ' { : 7 d } { : 7 . 2 f } { : 7 . 2 f l ' . f orrna t ( deg, rna th . r adians ( deg ) , expe ct e d , ) )

Формула преобразования выглядит так: rad = grad

*

1t / 1 8 0 .

$ pyt hon3 rna t h_radi a n s . py Deg rees Radi ans Expe cted - -- - - - - - - -- - - - - - - - - - о 0 . 00 0 . 00 0 . 52 0 . 52 30 о. 79 45 о . 79 1 . 05 1 . 05 60 90 1 . 57 1 . 57 180 3 . 14 3 . 14 270 4 . 71 4 . 71 360 6 . 28 6 . 28

Для пр еоб ра :ю вания радианов в градусы используйте функцию degrees ( ) . Листинг 5 . 59. math_deqree s . py import rna t h I N PUT S [ (0, 0) =

1

6.4. math: МаmtМ81М"8СIСМ8 функции ( ma t h . p i / 6 , 3 0 ) , ( math . pi / 4 , 4 5 ) , ( ma t h . pi / 3 , 6 0 ) , ( ma t h . pi / 2 , 9 0 ) , ( math . pi , 1 8 0 ) , ( 3 * ma t h . pi / 2 , 2 7 0 ) , ( 2 * ma t h . pi , 3 6 0 ) ,

p r i n t ( ' { : " 8 } { : " 8 } { : " 8 } ' . forma t ( ' Ra d i a n s ' , ' De g r e e s ' , ' Expe c t ed ' ) ) print ( ' { : - " 8 } { : - " 8 } { : - " 8 } ' . fo rma t ( ' ' , ' ' for rad , expe cted i n I NPUT S : print ( ' { : 8 . 2 f } { : 8 . 2 f } { : 8 . 2 f } ' . fo rma t ( ra d , mat h . d e g ree s ( rad ) , e xpe c t e d , ) )

' ') )

Формула этоl'О преобразования выглядит так: deg

$

=

r ad * 1 8 0 / 7t .

pyt hon3 ma t h_degr e e s . py

Ra di ans Deg r e e s Expe cted - ------- - - - --- - - - - - - - - - 0 . 00 0 . 00 0 . 00 30 . 00 30 . 0 0 0 . 52 4 5 . 00 4 5 . 00 0 . 79 60 . 00 60 . 00 1 . 05 90 . 00 90 . 0 0 1 . 57 180 . 00 180 . 00 3 . 14 270 . 00 4 . 71 270 . 00 6 . 28 360 . 00 3 60 . 0 0

5 .4. 1 О. Тригоно метрия Тригонометрические функции связывают у1�ы треугольника с длинами его

Fa l s e Fa l s e < i o . S t r i n g I O obj e c t a t Ox1 0 0 7 a 3 d 3 8 > True

Для принудительного сброса содержимого буфера и записи его па диск следует вызвать метод ro l lover ( ) или fileno ( ) . Листинr 6 . 5 5 . tempfile_SpooledTemporaryFile_expli c i t . py

import t empf i l e with t emp f i l e . Spoo l e dTempora ryFi l e ( max_ s i ze=l O O O , mode= ' w+t ' , encoding= ' ut f- 8 ' ) a s temp : print ( ' t emp : ( ! r } ' . format ( t emp ) ) for i in range ( З ) : t emp . wr i t e ( ' This l i ne i s repea t e d ove r and ove r . \n ' ) print ( temp . _r o l l e d , t emp . _fi l e ) print ( ' rol l i ng ove r ' ) t emp . rol l ov e r ( ) print ( t emp . _rol l e d , t emp . _f i l e )

Поскольку размер буфера в этом примере намно1·0 прсвыша codecs предоставляет потоковый и файловый интерфейсы для пере· кодировки текстов ых данных между разли•шыми представлениями. Чаще всего его исполь.зуют для работы с текстом в представлении Unicode, 110 доступны так· же другие кодировки, используемые для других целей.

6. 1 0. 1 . Основы U nicode CPytlюn 3.х различает ·техстовъtе и байтов·ьtе строки. Экземпляры byt e s исноль· зуют последовательности 8-битов ы х байтовых значений. В противоположность этому внутренний механизм Pytl1011 использует представление строк типа str в виде последовательностей кодовых точек Uнicode. Для представления каждой ко12

h t t p s : / / d o c s . python . o rg / 3 . 5 / l i b r a r y /mmap . html

6.10. oodec8: КОАМРО88НМ 11 At№W4P0118Hмe С1JКЖ

387

довой точки используются 2 или 4 байта, в зависимости от опций, заданных при компиляции кода Pythoп. При выводе значений типа s t r они кодируются с использованием одной из не­ скольких стандартных схем, чтобы последовательность байтов можно было впо· следствии восстановить в виде той же самой текстовой счюки. Байты кодирован· но1·0 значения не обязательно должны совпадать со значениями кодовых точек, и схема кодирования определяет способ преобразования между обоими наборами значений. Кроме того, чтение данных Uпicode требует знания кодировки, чтобы поступающие байты могли быть преобразованы во внутреннее представление, ис· пользуемое классом unicode. Наиболее распространенными схемами кодирования для западноевропейских языков являются UTF·8 и UTF- 1 6, основанные соответственно на использовании последовательностей 1· и 2·байтовых значений соответственно для представле­ ния каждой кодовой точки. Для работы с другими языками более :iффективны дру1·ие кодировки , в которых большинство символов представлено кодовыми точками , требующими более двух байтов. Дополнител ьные ссылки

Для получения более подробной информации о Unicode обратитесь к списку дополни­ тельных ссылок, приведенному в конце этого раздела, и, в частности, загляните в раздел Unicode НОWГО справочной системы Python.

6. 1 0. 1 . 1 . Кодировки Смысл кодировок станови·гся наиболее понятным при изучении различных последовательностей байтов, получаемых в результате кодирования одной и той же строки различными способами. Ниже приведена функция, которая исполь­ зуется в последующих примерах для форматирования текстовой строки с целью приведения ее к виду, наиболее удобному для чтения. Листинг 6.93 . codecs_to_hex . py import Ь i n a s c i i

d e f t o_h e x ( t , nbyt e s ) : " " " Отформатировать т е к с т t как последова тель ность значений длиной nbyt e s , ра зделе нных пробелами . "" " char s_pe r_i t em nbyt e s * 2 h e x_ve r s i o n Ь i n a s c i i . he x l i fy ( t ) r e turn Ь ' ' . j o i n ( hex_ve r s ion [ s t a r t : s t a r t + cha r s_pe r i t em] fo r s t a r t i n range ( O , l e n ( h e x_v e r s i o n ) , ch a r s_pe r_ i te m ) =

=

if

name == ' ma i n ' : print ( t o_he x ( b ' abcde f ' , 1 ) ) print ( t o_h e x ( b ' abcde f ' , 2 ) )

ГА88 &. ФаЙ№88R смсrема

388

Данная функция ипюльзует модуль Ьina s c i i для получения шестнадцатерич­ ноr представления входной байтовой строки , а затем вставляет пробелы между каждыми nbytes байтами , прежде чем вернуть значение. $ python3 codec s_to_hex . py Ь ' 61 62 63 64 65 66 ' Ь ' 6162 6364 6566 '

Первый пример начинается с вывода текста ' fran9ai s ' с использованием "сырого" ( Raw ) п р едс тавлен ю1 класса un i code, за которым следует вывод имен символов, извлекаемых из базы данных lJ нicode. В следующих двух строках кода исходная строка кодируется с испольаованием схем UT1''-8 и UTl''- 1 6 и результиру­ ющие байты выводятся в виде шестнадцатеричных значений. Листинг 6.94. codecs_encodings . ру

impo r t uni codeda ta f rom code c s t o hex impo r t t o hex text

=

' f ranca i s '

p r i n t ( ' Raw : { ! r } ' . fo rma t ( te xt ) ) for с in t e x t : p r i n t ( ' { ! r } : { } ' . fo rma t ( c , uni codeda t a . name ( c , с ) ) ) p r i nt ( ' UT F- 8 : { ! r } ' . fo rma t ( t o_hex ( t ext . encode ( ' u t f- 8 ' ) , 1 ) ) ) p r i n t ( ' UT F - 1 6 : { ! r } ' . fo rma t ( t o_hex ( t e x t . e ncode { ' ut f - 1 6 ' ) , 2 ) ) )

Результатом кодирования строки s t r является об'Ьект Ьуtеs . $ pyt hon3 code cs_encod i ngs . py Raw : 1f1 : 'r' : 'а' : 'n' :

' f r a щ: a i s ' LAT IN SМALL LETTER F LAT I N SМALL LETTER R LAT IN SМALL LETTER А LAT IN SМALL LETTER N ' с;: ' : LAT IN SМALL LETTER С WITH CEDI LLA • а • : LAT IN SМALL LETTER А ' i ' : LAT IN SМALL LETTER I ' s ' : LAT IN SМALL LETTER S UT F - 8 : Ь ' 6 6 72 61 6е с3 а7 6 1 69 7 3 ' UT F- 1 6 : b ' fffe 6 6 0 0 7 2 0 0 6 1 0 0 6 е 0 0 е 7 0 0 6 1 0 0 6 9 0 0 7 3 0 0 '

Если последователыюсть кодированных байтов задана в виде экземпляра byt e s , метод decode ( ) преобразует байты в кодовые точки и возвращает после­ довательность в виде экземпляра s t r. Листинг 6.95. codecs_decode . py

f rom code c s t o hex imp o r t to hex text ' f rancai s ' encoded text . e ncode { ' ut f- 8 ' ) =

=

388 decoded

=

e ncode d . decode ( ' ut f-8 ' )

p r i n t ( ' Or i g i na l . 1 , r epr ( text ) ) p r i n t ( ' Encoded .. ' , t o_hex ( encoded, 1 ) , t ype ( encode d ) ) p r i n t ( ' Decoded . 1 , repr ( decoded ) , t ype ( decoded ) ) •



Выводимый тип нс зависит от выбора исходной кодировки. $ pythonЗ codecs_dec ode . py Or i g i na l Encoded Decoded

' f raщ:a i s ' Ь ' 6 6 7 2 6 1 6е с З а 7 6 1 6 9 7 3 ' < c l a s s ' by t e s ' > ' f ranca i s '

Примечан ие

Кодировка, испол ьзуемая по умолчанию, устанавливается во время запуска интерпрета­ тора при загрузке модуля s i te (раздел 1 7 . 1 ). Более подробное описание настройки коди­ ровки, используемой по умолчанию, содержится в разделе 1 7 .2.1 .4.

6. 1 0.2. Работа с файлами Кодирование и декодирование строк играют особо важную роль в операци­ ях ввода-вывода. Независимо от того, куда осуще1тка загрузки результирующих объектов не увенчается успехом. Лисrинг 7.5. pi ckle_load_from_file_l . py

impo r t pi ckl e impo r t pp r i n t impo r t s y s f i l e name

=

s y s . a rgv ( l ]

with open ( fi l ename , ' rb ' ) a s i n s : wh i l e True : try: о = p i c k l e . l oad ( i n_s ) except EOFE r r o r : break else : p r i n t ( ' READ : ( 1 ( ( 1 ) ' . fo rma t ( о . пате , o . name_b a c kwa rds ) )

Эта версия пе работает ввиду недоступности класса S impleObj ect. $ pythonЗ p i c k l e_l oad_f rom_fi l e_l . py t e s t . dat T ra ceba ck ( mo s t r e c en t ca l l l a s t ) : Fi l e " p i ckl e_load_ f r om_f i l e_l . py " , l i n e 1 5 , i n о = p i c k l e . l oad ( i n_s ) At t r i but e E r ro r : Can ' t get a t t r i b u t e ' S impl eObj e c t ' on

В то же время скорректи рованной версии, которая импортирует объект S impl eOb j ect иа исходного сценария, удается аавершить онерацию. Добавление следующей инструкции в ко11е1 1 сниска импорта ноаволяет сценарию обнаружить класс и сконструировать объект. f r om p i ckl e_dump_to_f i l e_l import S impl e Obj e c t

421

lеперь выполнение видоизмененнш'О сценария обеспечивает получение жела­ емого результата. $ pyt honЗ pi c k l e_load_fr om_f i l e_2 . py t e s t . dat READ : pi c k l e ( e l kcip ) READ : p r e s erve ( evre s e r p ) READ : l a s t ( t s a l )

7 . 1 .4. Объекты, не сериалиэуемые с помощью

модуля pickle Не все объекты могут быть сериализованы с помощью модуля p i c kle. Соксты , дескрипторы файлов, подключения к базам данных и друr·ие объекты с состояни­ ем времени выполнения, которые зависят o·r операционной системы или дру1"01'0 процесса , могут быть непригодными для сохранения каким-либо разумным спо­ собом. Объекты, имеющие подобные непригодные для сериализа1 �ии атрибуrы, мо1уг определить М(.'ТОДЫ _ge ts tate_ ( ) и _se t s tate_ ( ) для 1юзврата подм­ ножества состояния экземпляра, нодлежащеl'о сериализации. Метод _ge t s tate_ ( ) должен вернуть объект, содержащий внутреннее со­ стояние объекта. Одним из удобных способов представления состояния является словарь, 110 значением может быть любой объект, поддающийся ссриализации с помощью модуля pickle. Состояние сохраняется, а затем передается методу _ s e t s t a te_ ( ) при за1·рузке сериализованного объекта. Листинг 7.6. piokle_s tate . ру

impo r t pi c k l e

class State : de f

init ( se l f , name ) : s e l f . name : name

de f _repr ( se l f ) : _ r e t u r n ' S t a t e ( { ! r } ) ' . fo rma t ( s e l f .

dict_}

c l a s s MyC l a s s : de f

init ( s e l f , name ) : p r i n t ( ' MyCl a s s . init s e l f . _se t_name ( name )

( { ) } ' . fo rma t ( name ) )

def _set_name ( s e l f , name ) : s e l f . name = name s e l f . computed = name [ : : - 1 ]

Г118ва 7. ПосЮJlнное храненме и обмен АВННЫММ

422

def �repr� ( s e l f ) : r e t u r n ' MyC l a s s ( { ! r ) ) ( compu t e d= { ! r } ) ' . f o rma t ( s e l f . name , s e l f . c ompu t e d ) def �ge t s t ate� ( s e l f ) : state S t a t e ( s e l f . name ) p r i n t ( ' �g e t s t a t e� - > { ! r } ' . forma t ( s t a te ) ) return state =

de f

set state ( s e l f, state ) : p r i n t ( ' �s e t s t a t e_ ( { ! r } ) ' . f o rma t ( s t a t e ) ) s e l f . _s et_name ( s t a t e . name )

inst MyC l a s s ( ' name h e re ' ) p r i n t ( ' Be fo r e : ' , i ns t ) =

dumped = p i c k l e . dumps ( i n s t ) re l oa de d p i c k l e . l oads ( dumpe d ) p r i n t ( ' Afte r : ' , r e l oade d ) =

В этом примере для хранения внутреннего состояния объекта MyCla s s ис­ пользуется отдельный объект State. При загрузке сериализованного экземпляра MyC l a s s методу _ s e t s tate ( ) передается экземrшяр State, который использу­ ется для инициалиаации объекта. _

$ pythonЗ p i c k l e_s t a t e . py MyCl a s s . init ( name h e r e ) Be fore : MyC l a s s ( ' name h e re ' ) ( comput e d= ' e reh eman ' ) _ge t s t a t e_ - > S t a t e ( { ' name ' : ' п ате here ' } ) setstate ( S t a t e ( { ' name ' : ' name he re ' } ) ) Aft e r : MyC l a s s ( ' name he re ' ) ( comput e d= ' e reh eman ' )

Предупреждение

Если возвращаемое значение равно Fa l s e, то метод десериализации объекта.

s e t s t ate

( ) не вызывается при

7 . 1 .5. Циклические ссылки Протокол модуля p i c kle автоматически управляет циклическими ссылками , поэтому сложные структуры данных не требуют никакой особой организации управления. Взгляните на направленный граф, приведенный на рис. 7 . 1 . Он вклю­ чает несколько циклических ссылок, однако корректную структуру можно сериа­ лизовать, а затем перезагрузить.

423

Рис.

7.1.

Сериализа ция сrруктуры данных с циКАическими ССЬIАками с помощью МОАУАА p i c k l e

Листинг 7. 7. piokle oyole . ру

Lmpo r t p i c k l e

1; l a s s Node : " " " Простой диграф . " .. " de f init ( s e l f , name ) : s e l f . name = name s e l f . conne c t i ons = [ ] de f add_e dge ( s e l f , node ) : " Созда е т гра ницу между данным и другим узлом . " s e l f . connect i on s . append ( node ) de f �i t e r� ( s e l f ) : r e t u r n i t e r ( s e l f . connec t i on s )

DL) , мощью которого создаются таблицы, представлен ы в следующем листинге. Листинг 7 . 1 9. todo schema . sql _

Схема для примеров приложе ния t o-do Проекты - это высокоуровне вые операции , с остоящие из задач c r e a t e t аЫ е p r o j ect ( t e x t p r ima ry key, name d e s c r i pt i on text ,

с

по­

Г"8118 7. Постоянное хрененме м обме н АВН НЫмм

434

)

;

deadl i ne

date

Задачи - это шаги , которые должны быт ь выполнены для з а вершения проекта create t аЫ е t a s k ( i n t e g e r p r ima ry k e y aut o i nc rement n o t nul l , id integer default 1 , priority de t a i l s text , s t at u s text , deadl i n e date , comp l e t e d_on dat e , p ro j ect text not nu l l re ferences pro j ect ( name )

)

;

Для выполнения инструкций по созданию DDI,-cxeмы можно использовать ме­ тод executes cript ( ) объекта Connec t i on. Листинг 7 .20. sql i tеЗ crea te schema . ру _

_

impo r t o s impo r t s ql i t e З db f i l ename = ' t o do . db ' schema f i l ename ' to do_s chema . sql ' =

db i s new

=

not o s . pa t h . e x i s t s ( db_f i l ename )

wi t h s q l i t e З . connect ( db_f i l ename ) as conn : i f db i s new : p r i nt ( ' Crea t i ng schema ' ) with open ( s chema_f i l ename , ' rt ' ) as f : s chema = f . read ( ) conn . execut e s cript ( s chema ) p r i n t ( ' I ns e rt i ng i n i t i a l data ' ) conn . execut e s c r ipt ( " " " i n s e rt i n t o p r o j e ct ( name , de s c r i pt i on , deadl i ne ) values ( ' pymo tw ' , ' Python Modu l e o f the We e k ' , ' 2 0 1 6- 1 1 - 0 1 ' ) ; i n s e r t i n t o t a s k ( de t a i l s , s t at u s , deadl i ne , p r o j ect ) va l u e s ( ' wr i t e about s e l e c t ' , ' done ' , ' 2 0 1 6- 0 4 - 2 5 ' , ' pymotw ' ) ; i n s e r t into t a s k ( de t a i l s , s t at u s , deadl i n e , proj ect ) va lues ( ' wr i t e about random ' , ' wa i t i ng ' , ' 2 0 1 6 - 0 8 - 2 2 ' , ' pymot w ' ) ; i n s e r t i n t o t a s k ( de t a i l s , s t a t u s , deadl i ne , p r o j ect ) va lues ( ' wr i t e abo ut s ql i t e З ' , ' a ct i ve ' , ' 2 0 1 7 - 0 7 - 3 1 ' , ' pymo tw ' ) ; " '' " 1

7.4. sqllte3: 8С1р08НН8R peNU8fOНHllR 6IDa А8ННWХ

435

else : p r i n t ( ' Dat aba s e e x i s t s , a s s ume s ch ema doe s , too . ' )

Вслед за созданием таблиц выполняются инструкции вставки, с помощью ко­ торых создается пробный проект и относящиеся к нему задачи. Содержимое базы данных можно проверить, введя в командной строке команду sqli tеЗ . $ r m - f todo . db $ pythonЗ s q l i t e З_creat e_s chema . py C r e a t ing s ch ema [ n s e r t ing i n i t i a l data

$ s q l i t e З todo . db ' s e l e c t * f rom ta s k ' l ! l ! wr i t e about s e l ect l done l 2 0 1 6 - 0 4 - 2 5 1 l pymot w :2 1 l l wr i t e about random l wai t i ng l 2 0 1 6 - 0 B - 2 2 1 l pymotw З l l l wr i t e about sql i t e 3 1 a c t i ve l 2 0 1 7 - 0 7 - 3 1 1 l pymotw

7 4 2 И звлече ни е данных .

.

.

Чтобы извлечь значения , сохраненные в таблице задач, создайте объект C u r s o r для соединения с базой данных. Курсор обеспечивает сошасованное пред­

t�тавление текущей обрабатываемой записи для различных типои данных и явля­ ется основным средством взаимодействия с трапзакционными базами данных 11а1rюдобие SQI.ite. Листинг 7 . 2 1 . sqli tеЗ select tasks . ру _

_

i.mp o r t s q l i t e З cjb f i l ename

=

' t odo . db '

1rJi t h s ql i t e З . connect ( db_f i l ename ) a s conn : curs o r conn . cur s o r ( ) =

curs o r . execu t e ( " " " s e l e c t i d , p r i o r i t y , d e t a i l s , s t at u s , dead l i ne f r om t a s k whe r e proj e c t ' pymotw ' """) =

for row in c u r s o r . fe t ch a l l ( ) : t a s k_i d , p r i o r i t y , detai l s , s t at u s , deadl i n e = row p r i n t ( ' ( : 2 d } [ ( : d } ] ( : < 2 5 } [ ( : < 8 } ] ( ( } ) ' . fo rmat ( t a s k_i d , p r i o r i t y , d e t a i l s , s t a t u s , deadl ine ) )

Выполнение зап1юса осуществляется в два этапа. Прежде всего, следует иы­ звать метод execute ( ) объекта курсора, чтобы сообщить базы данных, какие дан1ные должны быть выбраны. После этого следует извлечь результирующий набор, �вызвав метод fet chal l ( ) . Возвращаемое значение представляет собой последо1вательность кортежей, которая содержит значения столбцов, включенных в опе­ IРатор s e l e c t запроса.

Гмеа 7. ПосrаRнное хранеиме " обмен АВННЫММ

438 $ python3 s q l i t e 3_s e l ect_t a з ks . py 1 [ 1 ] wri t e about s e l ect 2 [ l ] w r i t e about ra ndom 3 [ 1 ] w r i t e about s q l i t e 3

( 2 0 1 6-04 - 2 5 ) ( 2 0 1 6- 0 8 - 2 2 ) ( 2 0 17-07-31 )

[ done [ wa i t i ng [ a ct ive

Результаты можно извлекать по одному за один раз с помощью метода fetchone ( ) или блоками фиксировашюго размера с помощью метода fetctunany ( ) . Листинr 7 .22. sqli te З select_variations . ру _

impo rt s q l i t e 3 d b f i l ename

=

' t odo . db '

with s q l i t e 3 . connect ( db_f i l ename ) a s conn : cu r s o r conn . cu r s o r ( ) =

cur s o r . execute ( " " " s e l ect name , des c r ipt i o n , deadl i n e f r om pro j ect wh e r e name ' pyrnotw ' """) name , des c r i pt i o n , deadl i n e cursor . f et chone ( ) =

p r i nt ( ' P roj ect deta i l s f o r { } ( { } ) \ n des c r i pt i o n , name , deadl i n e ) )

due { } ' . fo rma t (

c u r s o r . execu t e ( " " " s e l e ct i d , p r i o r i t y , det a i l s , s t a t us , deadl i n e from t a s k whe r e pro j ect ' pyrno tw ' o rder Ьу deadl i ne lt l1 "

)

=

p r i n t ( ' \ nNext 5 t a s ks : ' ) f o r row i n curs o r . f e t chmany ( 5 ) : t a s k_id, p r i o r i t y , d e t a i l s , s t atus , dead l ine row p r i nt ( ' { : 2 d } [ { : d } ] { : < 2 5 } [ { : < 8 } ] ( { } ) ' . fo rmat ( t a s k_id , pr i o r i t y , d e t a i l s , s t atus , deadl i n e ) ) =

Значение, передаваемое методу fet chmany ( ) , определяет максимальное ко­ личество воавращаемых ааписей. Если последовательность доступных записей меньше :iтого значения, то раамер во:шращаемой последовательности будет мень­ ше максима.11 ыюго. $ python3 s ql i t e 3_s e l ect_va r i a t i o n s . py Proj ect det a i l s f o r Python Modu l e o f the Wee k ( pyrnot w ) due 2 0 1 6- 1 1 - 0 1 Next 5 1 [1] 2 [1] 3 [l]

t a s ks : wri te about s e l ect wr i t e about ra ndom w r i t e about s ql i t e 3

[ do n e [ wa i t i n g [ a ct ive

( 2 0 1 6-04-2 5 ) ( 2 0 1 6 - 0 8 -2 2 ) ( 2 0 17-07-31 )

1А. sqllteЗ: вс:tрОеННВR реNIЦМОНН8R б11эа А8ННЫХ

437

7 . 4 .3. Метаданн ые запроса В соответствии со спецификацией DB-API 2.0, после вызова метода execute ( ) объект Cursor должен установить свой атрибут de scrip t ion, используемый для хранения информации о данных, которые будут во:щращаться методами fetch. Спецификация API определяет значение des cr iption как последовательность кортежей , содержа щих имя столб1 1а, тип 11анных, отображаемый размер, вну­ тренний размер, точность, шкалу и флаг, указывающий на то, приемлемы ли зна­ чения nul l. Листинг 7.23. sqli tе З cursor de scription . ру _

_

impo r t s q l i t е З d b f i l ename

=

' t odo . db '

w i t h s q l i t e З . connect ( db_f i l e name ) a s conn : cu r s o r conn . cu r s o r ( ) =

c u r s o r . execute ( " " " s e l e c t * f rom t a s k whe r e pro j e ct " '' " )

' pymotw '

p r i n t ( ' T a s k t аЫ е has t h e s e c o l umn s : ' ) for c o l i n f o in cu r so r . de s c r ipt i on : p r i n t ( co l i n fo )

Поскольку sql i tеЗ не навязывает задание типа или ра:Jмера данных, добавляе­ мых в базу данных, значениями заполняется лишь столбец name. $ pythonЗ s q l i t e З_cu r s o r_de s c r i pt i on . py T a s k t а Ы е h a s these column s : ( ' i d ' , None , None , None , None , None , None ) ( ' pr i o r i t y ' , None , None , None , Non e , None , None ) ( ' de t a i l s ' , None , None , None , None , None , None ) ( ' s t a t u s ' , None , None , None , None , None , None ) ( ' de a d l i ne ' , None , None , None , None , None , None ) ( ' comp l e t ed_on ' , None , None , None , None , None , None ) ( ' pr o j e ct ' , None , None , None , None , None , None )

7 .4.4. Объекты Row По умолчанию значения , возвращаемые методами fetch в качестве строк ре­ зультата запроса, являются кортежами. Отвстственнщ:ть за корректное исполь­ зование порядка следования столбцов в запросе и и:шлечение индивидуальных значений из кортежа возлагается на вы:1ывающий код. В тех Зователе й . $ python 3 s ql i t e 3_i s o l a t i on_l eve l s . py EXCLU S I VE 2 0 16-08-2 0 2 0 1 6- 0 8 - 2 0 2 0 1 6-08-2 0 2 0 1 6- 0 8 - 2 0 2 01 6-08-2 0 2 0 1 6- 0 8 - 2 0 2 0 1 6-08-2 0 2 0 1 6- 0 8 - 2 0 2 016-08-2 0 2 0 1 6-08-2 0 2 01 6-08- 2 0 2 0 1 6- 0 8 - 2 0 2 01 6-08-2 0 2016-08-2 0 2016-08-2 0

17 : 4 6 : 33, 320 17 : 4 6 : 33, 32 0 17 : 4 6 : 33, 324 1 7 : 4 6 : 34 , 32 3 1 7 : 4 6 : 34 , 32 3 17 : 4 6 : 34 , 323 17 : 4 6 : 34 , 32 3 17 : 4 6 : 35, 327 17 : 4 6 : 35, 368 17 : 4 6 : 35, 368 1 7 : 4 6 : 3 5, 3 6 9 1 7 : 4 6 : 35, 3 6 9 1 7 : 4 6 : 35, 385 1 7 : 4 6 : 3 5, 3 8 5 17 : 4 6 : 36, 386

( Reader 1 ( Reader 2 (Writer 2 ( M a i nTh r e a d ) ( Reader 1 ) ( Wri ter 2 ) ( Reader 2 ) (Writer 2 ) ( Reader 2 ) ( Reader 2 ) ( Reader 1 ) ( Reader 1 ) (Writer 1 ) (Writer 1 ) (Wri ter 1 )

wa i t i n g t o s yn chron i z e wa i t i n g to synch roni ze wa i t i ng t o synchroni ze s e t t i ng ready wa i t over PAU S I NG wa i t over CHANGE S СОММIТТЕD S E LECT EXECUTED re s u l t s fe t ched SELECT EXECUTED r e s u l t s fet ched wa i t i n g t o s yn chron i z e PAU S I N G CHANGES СОММIТТЕD

Как только первый объект записи начинает вносить изменения, объекты чте­ ния и второй объект записи блокируются до завершения тра11зак� 1 ии. Вызоn s l eep ( ) вводит искусственную задержку n поток, выполняющий :шпись, чтобы более отчетливо продемонстрировать блокирование других соединений.

7 .4. 1 0.4. Автоматическая фиксация транзакций Установка значения None для параметра i s o lation _ leve l соединения включа­ ет режим автоматической фиксации транзакций. В этом режиме изменения фик­ сируются после каждого успешного вызова execute ( ) 110 завершении инструк­ ции. Режим автоматической фиксации хорошо подходит для коротких транзак­ ций , например для вставки небольшого объема данных в 0;1 11у табл и цу. П ри этом длительность блокирования базы данных сводится к минимуму, что значительно уменьшает вероятность возникновения ситуации гонки.

Г"888 7. Пос:nlАнное хранение М обмен АllННЬI...

454

В сценарии sql i tеЗ _аи tocommi t . ру удален явный вызов метода commi t ( ) и для уровня изоляции установлено значение None. Во всем остальном используе­ мый в нем подход аналогичен тому, который использовался в сценарии sql i tеЗ i s o l a t ion_leve l s . ру. В то же время вывод, создаваемый этим сценарием, от­ личается , поскольку оба потока записи заканчивают работу до того, как объекты чтения обращаются с запросами к базе данных.

_

$

pyt hon3 s q l i t e 3_autocoппni t . py

2 01 6-08-20 2 01 6-08-20 2 0 1 6- 0 8 - 2 0 2 01 6-08-2 0 2 0 1 6 - 0 8 -2 0 2 01 6-08-20 2 01 6-08-20 2 01 6-08-2 0 2 0 1 6- 0 8 -2 0 2 0 1 6- 0 8 -2 0 2 0 1 6- 0 8 - 2 0 2 0 1 6- 0 8 - 2 0 2 0 1 6- 0 8 - 2 0

17 : 46 : 36, 17 : 4 6 : 36, 17 : 4 6 : 36, 17 : 4 6 : 36, 17 : 4 6 : 37 , 17 : 4 6 : 37 , 1 7 : 4 6 : 37 , 1 7 : 4 6 : 37 , 1 7 : 4 6 : 37 , 17 : 4 6 : 37 , 17 : 4 6 : 37 , 17 : 4 6 : 37 , 17 : 4 6 : 37 ,

451 451 455 456 4 52 4 52 4 52 4 52 453 453 454 4 54 454

( Reade r 1 ( Reader 2 ( Wr i t e r 1 ( Wr i t e r 2 ( Ma i nTh r e a d ) ( Reade r 1 ) (Writer 2 ) ) ( Reade r 2 ( Wr i t e r 1 ) ) ( Reade r 1 ( Reade r 2 ) ( Reade r 1 ) ( Reade r 2 )

wa i t i ng t o s ynch r oni z e wa i t ing t o s ynchroni ze wa i t i ng t o s ynch r on i z e wa i t ing t o sync h r on i z e s e t t i ng ready wa i t ove r PAU S I NG wa i t over PAU S I NG S E LECT EXECUTE D S ELECT EXECUT E D r e s u l t s fetched r e s u l t s f e t ched

7 .4. 1 1 . Базы данных в памяти SQI.ite поддерживает управление базой данной, хранящейся в оперативной памяти компьютера, а не на диске. Базы данных, хранимые в памяти , удобно ис­ пользовать для автоматизированного те ' 2 0 1 6- 1 1 - 0 1 ' ( 1 t ime s ) mode ( deadl i ne ) i s : 2 0 1 6 - 1 1 - 0 1

7 4 1 6 Многопоточность и совместное использование .

.

.

соединений По историческим причинам, уходящим корнями в старые версии SQLite, объ­ екты Connect ion не могут совместно испольаоваться несколькими потоками. Каждый поток должен создавать собственное подключение к баае данных. Листинг 7 3 9 sqli tеЗ _threading . ру .

imp o r t import imp o r t imp o r t

.

sql i t e 3 s ys threadi ng t ime

db f i l e name ' t odo . db ' i s o l a t i on l eve l = None # режим автоматиче ской фиксации измене ний =

de f reade r ( conn ) : p r i nt ( ' S t a r t i ng thread ' )

7.4. sqllteЗ: встроенная ремционная ба38 АВННЫХ

481

try : cursor conn . cu r s o r ( ) curs o r . execute ( ' s e l e ct * from t a s k ' ) cursor . fet cha l l ( ) print ( ' re s u l t s fe t ched ' ) except Excep t i on a s e r r : pr i nt ( ' E RROR : ' , e rr ) =

if

name == ' ma i n wi th sql i t e 3 . connect ( db_f i l ename , i s o l a t i on l e ve l = i s o l a t i on_l evel , ) as conn : t t h re a d i ng . Thread ( name = ' Re ader 1 ' , t a rget=reade r , args= ( conn , ) , ) t . s t a rt ( ) t . j oin ( ) '

·

Попытки совмест1ю1·0 использования соединения одновременно несколькими потоками приводят к возбуждению исключения . $ python3 s q l i t e 3_threading . py S t a rt i ng t h re a d ERROR : S QL i t e obj e c t s c r e a t e d i n а thread can onl y Ье used i n that s ame thread . The ob j e ct wa s created i n t h read i d 1 4 0 7 3 5 2 3 4 0 8 8 9 6 0 a n d t h i s i s t hread i d 1 2 3 1 4 5 3 0 7 5 5 7 8 8 8

7 4 1 7 Ограничение доступа к данным .

.

.

l Iесмотря па то что в SQUte пет средств управления н равами доступа 11ол1>Зо­ вателей, предусмотренн ых в дру!'их, более мощных СУl)Д, в ней имеется меха­

низм, позволяющий ограничивать доступ к столбцам. Каждое соединение может установить фушщ,ию авторизации, которая будет управлять возможностью доступа к столбцам на этапе выполнения на основании указанных критериев. Фун кция ав­ торизации вызывается в процессе анализа SQL-инструкций. Ей передаются пять параметров. Первый из них - код действия (асtiоn) - указывает на тип выполня­ емой операции (например, чтение, запись или удаление), а о < ch i l d i d= " a " >Th i s i s ch i l d " а " . < / c h i l d > < ch i l d i d= " b " >Th i s i s ch i l d " b " . < / c h i l d > < /g roup>

< ch i l d i d= " c " >Th i s is ch i l d " c " . < / ch i l d> < / g roup> < / ro o t > 1 1 ' ) p r i n t ( ' pa r s ed = ' , pa rsed ) for e l em i n pa r s e d : show_node ( e l em )

В отличие от метода parse ( ) , в данном случае возвращаемым значением явля­ ется экземпляр Element, а не ElementTree. Класс Elemen t непосредственно поддерживает п ротокол итератора, что из­ бавляет о т необходимости вызывать метод get i terator ( ) .

7.5. xml.etree.ElementТree: АР1 Ntlf манмnумрования ХМL-эммеtnамм

475

$ pythonЗ E l ementTree_XML . py p a r s ed = < E l ement ' root ' at O x l 0 0 7 9e e f 8 > g r oup child text : " Th i s i s chi l d " а " . " id "а" ch i l d text : " T h i s i s chi l d " Ь " . " id "Ь" g roup chi l d t ext : " T h i s i s chi l d " с " . " id "с" =

=

=

В случае сrруктурированных ХМL-документов, в которых для идентификации уникальных узлов ипюльзуются атрибуrы id, удобный способ доступа к результа­ там анализа докуменrа обеспечивает функция XMLI D ( ) . Л истинг 7 . 5 5 . ElementTree_XМLID . ру

f r om xml . e t ree . E l eme n t T r e e import XML I D t re e , id_map XMLI D ( '

< ch i l d id= " a " >Th i s Th i s < / g roup>

Th i s < / group> < / root > =

1

' '

i s ch i l d " a " . < / ch i l d> i s ch i l d " b " . < / chi l d>

is chi l d " c " . < / chi l d >

1 )

1

for ke y , value in s o r t e d ( i d_map . i tems ( ) ) : print ( ' % s % s ' % ( ke y , value ) ) =

Функция XML I D ( ) возвращает дерево в виде объекта E lernent вместе со слова­ рем, сопоставляющим строки атрибутов id с индивидуальными узлами. $ pythonЗ E l ementTre e_XML I D . py а Ь с

=

< E l eme nt ' chi l d ' at Ох 1 0 1 3 З а е а 8 >

< E l eme nt ' ch i l d ' at Ox 1 0 1 3 3a f 9 8 >

7 5 8 С о эдание до кументо в с пом ощью узло в Elemen t .

.

.

В дополнение к возможностям синтаксического анализа модуль xml . et ree .

ElernentTree поддерживает создание корректно сформированных ХМI.-докумен­ тов из объектов E l ernent , конструируемых в приложении. Классу E l ement, ис-

ГА888 7. Посюянное хранение И обмен АВННЫМИ

476

пользуемому при анализе документа, также известно, как генерировать сериали· зованную форму своего содержимого, которое затем может быть записано в файл или другой поток данных. Для создания иерархии узлов E l ement ис1юл1,зуются три вспомогательные функции. Функция Element ( ) создает стандартный узел, функция SubElement ( ) присоединяет новый узел к родитеш.скому узлу, а функция Comment ( ) создает узел , который сериализует данные, используя синтаксис комменrариев XMI,. Листинг 7 . 56. E lementTree_create . py

from xml . e t ree . E l eme ntTree import E l eme n t , S ubE l ement , Comme n t , t o s t r i ng ,

top

=

E l eme n t ( ' t op ' )

comme nt Comme n t ( ' Ge n e r a t e d for PyMOTW ' ) t op . append ( comment ) =

chi l d SubE l eme nt ( t op , ' ch i l d ' ) ch i l d . text ' Th i s chi l d con t a i n s t e xt . ' =

=

ch i l d_w i t h_t a i l S ubEl eme n t ( t op , ' ch i l d_wi th_t a i l ' ) chi l d w i t h t a i l . text ' Th i s c h i l d ha s tex t . ' chi l d w i t h t a i l . ta i l ' And " t a i l " tex t . ' =

=

chi l d_wi t h_ent i t y_re f SubE l eme n t ( t op , ' ch i l d_w i t h_en t i t y_re f ' ) chi l d_wi th_e n t i t y_re f . t e xt ' Th i s & that ' =

=

p r i n t ( t o s t r i ng ( t op ) )

Вывод содержит лишь ХМI.-узлы дерева - он не включает объявление версии и кодировки. $ pythonЗ E l eme n tT ree_create . py b ' < t op>< ! - -Generated for PyMOTW - - >T h i s c h i l d co n t a i n s text . < / chi ld>Thi s c h i l d has text . < / chi l d wi t h- t a i l >And " ta i l " text . < ch i l d ; i t h ent i t y r e f >Th i s & ; tha t < / ch i l d with ent i t y_ref>< / t op> ' -

-

-

-

Символ & , содержащийся в тексте узла chi ld_w i t h_en t i t y_re f , автоматиче­ ски преобразуется в ссылку на сущность & amp ; .

7. 5.9. Красивая пе ч ать XML Класс E l emen tTree не делает никаких попыток форматировать вывод функ­ ции t o s t r ing ( ) для улучшения его удобочитаемости , поскольку добавление до­ нолнительных пробелов изменяет содержимое документа. Чтобы сделать вывод более удобным для чтения, в оставшихся примерах ХМL-документ предваритель­ но преобразуется с помощью модуля xml . dom . minidom перед применением к нему метода t oprettyxml ( ) .

7.6. xrnl.etree.ElementТree: API � мвниnуАИРОВанин ХМL-эмментами

477

Листинг 7. 57. Elemen tтree _pret ty . ру f rom xml . e t r e e import E l ementT r e e f r om xml . dom impo rt mi n i dom

def pre t t i f y ( e l em ) : '""' Вернуть красиво оформленную ХМL-строку для объект а E l ement . rough_s t r i ng El emen tTree . t o s t r i n g ( e l em, ' u t f - 8 ' ) rep a r s ed = mi ni dom . pa r s e S t r i ng ( rough_s t r i n g ) return reparsed . t op r e t t yxml ( i ndent = " ") =

Обновленный пример представлен

н

следующем листипге.

Листинг 7 . 58. ElementTree_create_pretty . py f rom xml . e t r e e . E l ementTree import E l eme n t , SubE l ement , Comme n t f rom El ementTree_p r e t t y import p r e t t i f y t op

=

E l ement ( ' top ' )

comment Comment ( ' Gene rat ed for PyMOTW ' ) t op . append ( comment ) =

chi l d = SubEl ement ( t op, ' ch i ld ' ) chi l d . t ex t = ' Thi s chi l d cont a i n s text . ' S ubEl ement ( top , ' ch i l d_wi t h_t a i l ' ) chi l d_wi t h_t a i l chi ld w i t h ta i l . text ' T hi s chi l d ha s t ext . ' chi l d w i t h ta i l . t a i l = ' And " t a i l " t e x t . ' =

chi l d_wi t h_ent i t y_re f = S ubE l ement ( top , ' ch i l d_wi t h_e nt i t y_re f ' ) chi l d_wi t h_en t i t y_re f . t ext = ' Th i s & that ' p r i n t ( pr e t t i f y ( top ) )

Читать вывод, создаваемый :�тим примером, значительно легче. $ python З El ementTree_c r e a t e_pre t t y . py < ? xml v e r s i o n = " l . 0 " ? > < t op> < ! --Generated f o r PyMOTW- - > < ch i l d>T h i s chi l d cont a i ns t ext . < / c h i l d > Th i s chi l d has text . < / ch i l d_wi t h t a i l > And & quot ; t a i l & quot ; t e x t . < chi l d_wi t h_en t i t y_re f>Thi s & amp ; t h a t < /chi l d_wi th_ent i t y_re f > < / top>

Кроме дополнительных пробелов, добавленных с целью форматирования, мо­

душ. xml . dom . minidom в1U1ючает в вывод объявление XMI"

ГА888 7. Пос:тоRниое хранение и обмен АВННЫМИ

478

7. 5. 1 0 . Установка свойств объектов

E lement

В предыдущем примере создавались узлы с дескрипторами и текстовым содер­ жимым, но их атрибуты не устанавливал ись. В разделе 7.5. 1 многие примеры ра­ ботали с ОРМL-файлом, содержащим список подкастов и их потоков. Для пред­ ставления имеи групп и свойств подкастов в узлах outline иерархического дере­ ва использовались атрибуты. Класс E l ementTree позволяет создать аналогичный ХМI,-файл из входного СSV-файла, установив все атрибу1ъ1 элементов в процессе построения дерева. Листинг 7 . 59. Elemen tTree csv_ to :юnl ру _

_



impo rt csv from xml . et re e . El emen t T r e e import E l ement , S ubE l ernen t , Comme n t , t o s t r i ng , impo r t datet ime from E l ementTree_pre t t y impo rt pre t t i f y generat ed_on

=

s t r ( da t e t ime . da t e t ime . now ( ) )

# Конфигурирование одного а трибута с помощью ме тода s e t ( ) root E l ement ( ' opml ' ) root . s e t ( ' ve r s i on ' , ' 1 . О ' ) =

root . append ( Comment ( ' Gene r a t e d Ьу E l ementT r ee_c sv_t o_xml . p y for P yMOTW ' )

head S ubEl ement ( root , ' he ad ' ) title SubEl ement ( head, ' t i t l e ' ) t i t l e . t ext ' Му Podca s t s ' dc SubEl ement ( he a d , ' da t eC r e a t e d ' ) dc . text generat ed_on dm = S ubEl ement ( head , ' da t eModi f i e d ' ) dm . t ext generated_on =

=

=

=

=

=

body

=

SubEl ement ( ro o t ,

' body ' )

wi th open ( ' podcas t s . c s v ' , ' r t ' ) as f : current_g roup None reader c s v . reader ( f ) f o r row i n reade r : group name , podca s t пате , xml u r l , htrnl url i f ( c� rre nt_g roup i s None o r group_name ! = current_group . tex t ) : # Нач а т ь новую группу current_group = SubEl ement ( body , ' ou t l i ne ' , { ' t ex t ' : group_narne } , =

=

# Добавить подкаст в группу , # одновреме нно установив значения

row

7.5. xml.etree.ElementТree: API � М8НМllуАМр088НМЯ ХМL-мементами

479

# в с е х е го атрибутов podca s t = SubEl eme nt ( current_group , ' out l i ne ' , { ' tex t ' : podcas t_name , ' xml U r l ' : xml_url , ' html U r l ' : html_u r l } ,

p r i n t ( p r e t t i fy ( ro o t ) )

В этом примере для задания значений атрибугов новых узлов используются два приема. Корневой узел конфигурируется с помощью метода s et ( ) с измене­ нием по одному атрибуту за раз. Атрибуты узлов 11одкастов задаются сразу З.1. счет передачи словаря фабрике узлон. $ python3 E l eme ntTre e_c sv_t o_xml . p y < ? xml ve r s i o n= " l . O " ? >

< ! - - G e ne r a t e d Ь у E l eme ntT ree_c sv_t o_xml . py for PyMOTW - - >

< t i t l e >My Podca s t s< / t i t l e > 2 0 1 7 - 0 9 - 0 5 0 0 : 3 8 : 57 . 1 0 9 2 5 7 < /da teCreated> 2 0 1 7 - 0 9 - 0 5 0 0 : 3 8 : 5 7 . 1 0 9 2 5 7 < /da teModi fi ed > < /head>

< / o u t l i ne >



< / out l i ne > < /body> < / opml >

7 5 1 1 Создание деревьев из списков узлов .

.

.

Метод ex tend ( ) позволяет добавить в экземпляр E l ement сразу несколько узлов. Листинг 7 .60. ElementTree_extend . py

f rom xml . e t r e e . E l eme ntT ree import E l eme n t , t o s t ring from E l ementT ree_p r e t t y import pre t t i fy t op

=

E l eme nt ( ' t op ' )

Л.88 7. ПОСIОRнное хранение и обмен данными

480 c h i l dren = [ E l ement ( ' ch i l d ' , num= s t r ( i ) ) for i in range ( З )

top . extend ( ch i l dren ) p r i n t ( p r e t t i f y ( top ) )

Предоставленные в виде списка узлы добавляются непосредственно в новый родительский узел. $ pythonЗ El ementTree_ex tend . py < ?xml ve r s i on= " l . 0 " ? > < top >

< ch i l d num= " l " / >

< / top>

Если предоставляется другой экземпляр E l ement , то в новый родительский узел добавляются дочерние узлы данного узла. Листинг 7.61 . ElementTree_extend_node . py

f rom xml . e t re e . E l emen tTree impo r t ( El emen t , SubE l ement , t o s t r i n g , XML , f r om El ementTree_p r e t t y import p r e t t i f y top = El eme n t ( ' top ' ) pa rent = S ubEl ement ( t op,

' pa re n t ' )

ch i l dren XML ( ' < root > < ch i ld num= " l " / > ' ' < chi l d num= " 2 " / > < / r oo t > ' =

pa rent . ext end ( ch i l dren ) print ( p re t t i f y ( t op ) )

В данном случае узел с дескриптором roo t , созданный на основе ХМl,·строки, имеет три дочерних узла, которые добавляются в дочерний узел. Узел root не включается в результирующее дерево. $ pythonЗ E l ementTree_extend_node . p y < ? xml v e r s i o n= " l . 0 " ? > < t op>



7.5. xml.etree.Element'INe: АР1 �

МllНМnумtроеенин ХМL-мементамм

481

< chi l d num= " 2 " / > < /parent> C / t op>

Важно понимать, что метод extend ( ) не изменяет существующие отношения �ежду родительскими и дочерними узлами. Если значения , переданные методу 3 кt end ( ) , уже существуют в иерархическом дереве, то они остаются и будут вос11роизведены в выходном результате.

n истинг 7 .62. ElementTree_extend_node сору . ру _ from xml . e t r e e . E l ementт ree impo rt ( E l ement , SubE l ement , t o s t r i n g , XML ,

� rom E l ementT ree_p r e t t y imp o r t prett i fy

t i:> p = E l ement ( ' t op ' )

;м re nt_a ::>.з.rent Ь

SubEl ement ( t op , SubEl ement ( t op ,

' pa r e n t ' , i d= ' A ' ) ' pa r e nt ' , i d= ' B ' )

t Создать дочерние узлы

:hi l dren = XML ( ' < r o o t > < ch i l d num= " O " / > < chi l d num= " l " / > ' ' < ch i l d num= " 2 " / > < / root > '

Установка а трибутов id со значениями id об ъ е ктов узло в дл я более о т че тливой демон страции наличия дубликатов �)r с in chi l d r e n : c . se t ( ' i d ' , s t r ( i d ( c ) ) )

Доба вление узла в первый родитель ский узел ;i.з.rent_a . e x t end ( chi l dre n )

; нint ( ' А : ' ) ;i r i n t ( p ret t i fy ( top ) ) ;i r i n t ( )

� Копиро ва ние узлов во второй родител ь ский узел

p.з.rent_b . ext end ( ch i l dre n )

;i r i nt ( ' B : ' ) ;i r i n t ( p r e t t i fy ( t op ) ) ;i r i n t ( )

Установка уникальных идентификаторов объектов Руtlюп в качестве значен и й :tтрибутов этих дочерних узлов поаволяет убедительно продемонстрировать тот факт, что одн и и те же объекты уЗJюн 1 юя вляются в результирующем дереве более 1щною раза.

$ pythonЗ E l ementTree_extend_node_copy . p y

�=

Г118118 7. Посюянное хранение н обмен АВНнwмм

482 < ? xml v e r s i o n= " l . 0 " ? > < t op>

< ch i ld i d= " 4 3 1 6 7 8 9 9 6 0 " num= " l " / >

< /parent>

< / top>

В: < ? xml ve r s i o n= " l . 0 " ? > < t op>

< ch i l d id= " 4 3 1 6 7 8 9 8 8 0 "

< ch i l d id=" 4 3 1 6 7 8 9 8 8 0 " < ch i l d id=" 4 3 1 6 7 8 9 9 6 0 " < ch i l d id= " 4 3 1 6 7 9 0 0 4 0 "

< / top>

num= " O " / > num= " l " / > num= " 2 " / >

num= " O " / > num= " l " / > num= " 2 " / >

7 5 1 2 Сериализация ХМL-разметки в поток .

.

.

Реализация метода tost ring ( ) предусматривает запись данных в оператив­ ную память в виде файлового объекта и возврат строки, представля ющей все дерево элементов. Такой подход позволяет уменьшить потребление памяти при работе с большими объемами данных и повысить эффективность использования библиотек ввода-вывода за счет осуществления записи непосредственно с исполь­ зованием дескриптора файла при помощи метода w r i te ( ) объекта Element T ree. Листинг 7.63. ElementTree_wri te . py

impo r t i o impo r t s y s from xml . e tree . E l emen t T r e e impo rt E l e ment , SubElement , Comme nt , E l ementT ree ,

t op

=

E l ement ( ' t op ' )

comment = Comme nt ( ' Generated for PyMOTW ' ) top . append ( comment ) c h i l d = SubE leme n t ( top, ' ch i l d ' ) c h i l d . t e x t = ' Th i s chi ld cont a i n s t e x t . ' ch i ld wi th ta i l SubEl ement ( t op , ' ch i l d_wi th_t a i l ' ) chi ld wi t h t a i l . t e x t = ' Thi s chi ld h a s regu l a r tex t . ' =

7.5. xml.etree.ElementТree: API /lftJI М8НМnJllИpo88tllUI ХМL-мементамм

483

ch i l d w i t h t ai l . t a i l = ' And " t a i l " t e x t . ' ch i l d wi th ent i t y ref = S ubEl ement ( t op , ' ch i l d-w i t h e nt i t y- ref ' ) ch i l d wi t h=e nt i t y ref . text ' Th i s & that '

=

=

emp t y_chi ld

=

=

SubEl ement ( t op ,

' empt y_chi l d ' )

E l ementтree ( top ) . wr i t e ( s yз . s tdout . bu f fe r )

Для вывода на консоль в этом примере используется буферизованный по­ ток sys . s tdout . bu f fer вместо обычного потока sys . s tdou t , поскольку объ­ ект ElementTree вырабатывает не строки Unico< ! - -Generated for PyMOТW-->Thi s chi l d conta i n s text . < / chi ld>Thi з ch i ld h a s regu l a r text . < / ch i ld wi t h ta " i l >And " t a i l t e xt . Th i s & amp ; tha t < f chi l w i t h_e n t i t y_re f> < ! t op>

d

-

Последний узел в дереве не содержит ни текст-а, ни других узлов и поэтому за­ писывается в виде пустого дескриптора, . Метод w r i t e ( ) полу­ чает аргумент method, позволяющий управлять обработкой пустых узлов. Листинг 7.64. E lementTree_wri te_method . py

impo rt i o impo rt s y s f r om xml . e t r e e . E l ementTree impo rt ( E l ement , S ubE l ement , E l ementTree ,

E l ement ( ' t op ' )

t op

chi l d = S ub E lement ( top , ' ch i l d ' ) ch i l d . text ' Co n t a i ns text . ' =

empty_chi l d

=

SubE l ement ( t op ,

' empt y_chi l d ' )

f o r me thod i n [ ' xml ' , ' html ' , ' t ext ' ] : p r i n t ( me thod ) s y s . s t dout . fl u s h ( ) E l ementTree ( t op ) . wr i t e ( s ys . s tdou t . bu f fe r , me thod=me t hod ) print ( ' \n ' )

Поддерживаются три метода. • •

xml.

Используется по умолчанию, возвращает . html . Создает пару дескрипторов, как это требуется в I IТМL-документах ( < / empty_ch ild> ) . text. Выводит только текст узлов, полностью опуская пустые дескрипторы . _



ГА8811 7. ПосrаRНное хрвненме и обмен АВННЫМИ

484 $ pythonЗ El ementTre e_wr i t e_me thod . py

xml < top> Con t a i n s text . < / ch i l d > < emp t y_ch i l d / > < / t op> h tml < t op>Contains text . < / chi l d>< emp t y_ch i ld> < / empt y_ch i l d >< / t ор> text Cont a i n s text .

Дополнительные ссылки •

Раздел до кументации стандартной библиотеки, посвященный модулю xml . e t ree . E l ementTree 1 4.



csv (раздел 7.6). Чтение и запись файлов со значениями. разделенными запятыми.



defusedxml 1 5. Пакет, полезный при работе с ХМL·данными из ненадежных источни­ ков, который устраняет уязвимости, связанные с возможными атаками типа DoS (отказ в обслуживании) .



Pгetty pгint xml with python - indenting xm/16 (Rеле Dudfield). Советы, касающиеся орга ­ низации красивой печати ХМL-документов в Pythoл .



ElementTгee Overview1 7 (Fredrick Luлdh). Оригинальная документация и ссылки на вер­ сии разработки библиотеки ElemeлtTree.



Pгocess XML in Python with ElementTгee18 (David Mertz) . Статья на сайте IBM Deve­ loperWorks.



Outline Ргосеssог Markup Language (OPML)19 (Dave Wiлer). Спецификация и документа­ ция OPML.



XML Path Lan guage (XPath)20• Синтаксис языка запросов к элементам ХМL-документа.



XPath Support in ElementTгee21 (Fredrick Luлdh). Часть оригинальной документации би­ блиотеки ElemeлtTree .

7 6 csv: файлы с данными, разделенными .

.

за пятыми Модуль c s v иснолюуется для работы с данными, экспортированными и з элек· троппых таблиц и баз данных в текстовые файлы, содержащие поля и записи в формате CSV, в котором данные ра:щеляются эапятыми. 14

ht tps : / / docs . python . o r g / 3 . 5 / l i bra r y / xml . e t r e e . e l ement t r e e . h tml

15

https : / / pypi . python . org /pyp i /defusedxml http : / / renesd . Ь l ogspot . com/ 2 0 0 7 / 0 5 /p r e t t y-print -xml -wi th-python . html

16 1 7 16 19

20 21

h t tp : / / e f fbot . or g / z one / e l eme nt - i ndex . htm www . ibm . com/dev e l operworks / l i brary/x-ma t t e r s 2 8 / www . opml . o rg www . wЗ . org/TR/xpath/ http : / / e f fbo t . o rg / z one / e l ement -xpa t h . htm

7.6. CSV: фаiАы С А8ННWММ, pa3AllМIOllllM M Slll1R1'Ы MM

485

7 6 1 Чтение .

.

.

Для чтения данных из СSV-файла используют функцию reade r ( ) . Создавае­ мый ею объект чтения можно использовап, в качестве итератора мя поочеред­ ной обработки строк файла. Листинг 7 .65. csv reader . ру _

import c s v impo rt s y s w i t h ope n ( s ys . argv [ l ] , ' rt ' ) as f : reader c s v . reader ( f ) f o r row i n reade r : p r i n t ( row) =

Первым аргументом функции reade r ( ) является источник текстовых строк. В данном случае - это файл, но допускается иснол1,зова11ие любоl'о итерируемо­ го об·ьекта ( напри мер, экземпляра S t r i n g I O или списка). Также можно передать необязателы1ые аргументы , 1юзволяющие управлять анализом входных данных. # t e s t d a t a . c sv "Title l " , "Title 2 " , "Title 3" , "Title 4 " 1 , "а" , 08/18/07 , " а " 2 , "Ы " , 0 8 / 1 9 / 07 , " " 3 , "с" , 08/20/07 , " с "

При чтении файла каждая строка анализируемых входных данных преобразу­ ется в список строк. $ python3 c s v_reader . p y t e s tdata . c s v [ [ [ [

' Ti t l e 1 ' , ' Ti t l e 2 ' ' 1 ' , ' а ' , ' 0 8 / 1 8 / 07 ' ' 2 ' , ' Ь ' , ' 08 / 1 9/07 ' ' 3 ' , ' с ' , ' 0 8 / 2 0 / 07 '

, ' Title 3 ' , , 'a' J , J"] , 'c' J

' Title 4 ' ]

Анализатор обрабатывает разрывы zip2. Оба модуля работают с 1 ютоками данных, каков бы ни был их формат, и предоставляют интерфейсы для проарачного чтения и за­ писи сжатых файлов. Ис1юльзуйте эти модули для сжатия одиночного файла или источника данных. Стандартная библиотека также включает модули для работы с архивными фор­ матами, 1 юзволяющими объединять несколько файлов в один, с которым можно обращаться как с отдельной единицей. Модуль t a r f i l e (раздел 8.4) прсдна:шачен для чтения и записи архивов ta·r в Uнix - старого стандарта, который все еще ш и­ роко используется в силу его гибкости. Модуль z ip f i l e (раздел 8.5) работает с архивами, основанными на формате, получившем популярность блаюдаря п ро­ грамме PKZIP для ПК, которая первоначально работала 1 юд управлением MS-DOS и Wi шlows, но в настоящее время ис�юльзуется также на других 11ла1формах из-за простоты ее интерфейса и персносимо & 2 ; e x i t 1 1 , chec k"T rue , s hel l =True , s t dout=subproces s . P I P E ,

e x c e p t s ubproce s s . Ca l l edProc e s s E r r o r a s e r r : p r i n t ( ' ERROR : ' , e r r ) else : p r i n t ( ' r e t u rncode : ' , comp l eted . r e t urncode ) p r i n t ( ' Have { } bytes i n s t dout : { ! r } ' . format ( l en ( comp l e t e d . stdout ) , compl e t ed . s t dout . de code ( ' ut f - 8 ' ) )

552

rмва 10. П&p8М8Abllwe вычмсмнtu1: процессы, nоrоки и соnроrраммы

Сообщения, направляемые в стандартный поток ошибок, в ыводятся на кон­ соль, тогда как сообщен ия , направляемые в стандартный поток вывода, скры­ ваются. $ pythonЗ s ubpro cess_run_output_e rro r . py to s t derr ERROR : Comma nd ' e cho t o s t dout ; e cho t o s t de r r 1 > & 2 ; exi t 1 ' ret urned non- zero exit s t a t u s 1

Чтобы предотвратить вывод на консоль сообщений об ошибках при выполне­ нии команд, запущенных с помощью функции run ( ) , следует установить для ар гу­ мента stderr значени е P I P E . Листинг 1 0.б. subproces s_run_output_error_trap . py impor t s ubproces s

try :

comp l e t ed s ubpro c e s s . run ( ' e cho t o s t dout ; echo to s t derr 1 > & 2 ; e x i t 1 ' , s h e l l -=True , s t dout=subproce s s . P I P E , s t de r r = s ubproce s s . P I P E , =

except subpr oces s . Ca l l edPro c e s s E r ro r a s e r r : p r i n t ( ' ERROR : ' , e r r ) else : p r i nt ( ' re turncode : ' , comp l e t e d . r e turncode ) p r i n t ( ' Ha ve { } byt es i n s t dout : { ! r } ' . format { l e n ( compl e t ed . s t dout ) , comp l e t e d . s t dout . decode ( ' ut f - 8 ' ) ) p r i nt ( ' Have ( 1 bytes i n s t de r r : { ! r l ' . fo rma t ( l e n ( comp l e t ed . s t de r r ) , comp l e t ed . stderr . decode ( ' u t f - 8 ' ) )

В дан н ом примере аргумент check=True не передается, поэтому выходная ин­ формация команды перехватывается и выводится на консоль. $ p y t honЗ s ubproces s_run_output_error_t rap . py returncode : 1 Have 1 0 b y t e s i n s t dout : Have 1 0 b y t e s i n stde r r :

' t o s t dout \ n ' ' t o s t de r r \ n '

Чтобы перехватывать сообщения об ошибках при использовании функции check output ( ) , следует установит�. для аргумента s tder r значение STDOUT. В этом случае сообщения об ошибках будуr выводиться вместе с остальными ре­ зультат:1ми вынолнения команды . _

553 Листинг 1 0.7. suЬproces s_check_output_error_trap_output . py import s ubp roce s s t ry :

output s ubproce s s . che c k_output ( ' echo to s t dou t ; e cho to s t de r r 1 > & 2 ' , shel l=True , s t de r r= s ubproce s s . ST DOUT , =

except subproces s . Cal ledProces sError a s e r r : pr i n t ( ' ERROR : ' , e r r ) else : p r i nt ( ' Have { } byt e s i n output : { ! r } ' . fo rma t ( l e n ( output ) , output . de code ( ' ut f - 8 ' ) )

Порядок вывода может меняться в зависимости от использования буферизастандартного потока вывода и объема выводимых данных.

1щи

$ pytho n З s ubpro c e s s_che c k_ou tput_error_t r ap_output . py Have 2 0 byt es in output :

' t o s t dou t \ n t o s t de r r \ n '

1 0. 1 . 1 .З. Подавление вывода

В тех случаях, когда вывод не должен отображаться или перехватываться , его можно подавить, установив для соответствующего аргумента значение DEVNULL. В следующем примере подавляются стандартные потоки вывода и ошибок. Листинг 1 0.8. suЬproce s s_run_ou tpu t_error_ suppre s s ру .

import s ubproc e s s try:

comp l e t ed s ubproce s s . run ( ' e cho to s t dout ; echo t o s t de r r 1 > & 2 ; ex i t 1 ' , she l l=True , s t dout= subpro c e s s . DEVNULL , s t derr=subpro c e s s . DEVNULL , =

except s ubproce s s . Ca l l edProce s s Error a s e r r : pr i n t ( ' ERROR : ' , e r r ) else : p r i n t ( ' returncode : ' , compl e t ed . r e t u rncode ) p r i n t ( ' s t dout is { ! r } ' . format ( comp l e t e d . s t dout ) ) p r i n t ( ' s t de r r i s { ! r } ' . fo rma t ( comp l e t ed . s t d e r r ) )

Имя константы DEVNULL происходит от наз вания снециалыюго файла устрой­ ства Unix ( нулевого устройства) / dev/nu l l . Это устройство возвращает признак конца файла при его открытии для чтен ия и получает, но игнорирует, любые объ­ емы данных при записи. -

554

l"Aaa

10. парамем.ные вычмсмнин: проqессы, nсmжи и ооnроrраммы

$ pythonЗ subproces s_run_output_e r r o r_suppre s s . py ret urncode : 1 s t dout i s None s t de r r is None

1 0. 1 . 2 . Непосредственная работа с каналами Функции run ( ) , ca l l ( ) , check_cal l ( ) и check_output ( ) являются обертка­ ми вокруг класса Popen. Непосредственное использование класса Popen предо­ ставляет больше воз можностей контроля над выполнением команд и обработкой потоков ввода-вывода. Ilанример , варьируя значения параметров s tdin, s tdou t и s tderr, можно имитировать разли•шые режимы вызова функции o s . popen ( ) . 1 0. 1 .2. 1 . Одностороннее взаимодействие с процессом

Чтобы запустить процесс и прочитать весь его вывод, следует установить зна­ чение P I PE для аргумента s tdout и вызвать метод commun icate ( ) . Листинг 1 0.9. suЬproces s_?open_read . py impo rt s ubproce s s p r i n t ( ' read : ' ) proc = s ubproces s . Popen ( [ ' e cho ' , ' " t o s t dout " ' ] , s t dout=subproces s . P I PE , s t dout_va lue proc . commun i c a t e ( ) [ 0 ] . decode ( ' ut f - 8 ' ) p r i n t ( ' s t dout : ' , repr ( s t dout_va lue ) ) =

Это аналогично тому, как работает функция popen ( ) , за исключением того факта , что управление чтением осуществляется экземпляром Popen. $ pythonЗ subprocess_popen_re ad . py read : s t dout :

' " t o s t dout " \ n '

Чтобы настроить канал для записи в него данных вызывающей программой, следует установить для аргумента s tdin значение P I Р Е . Листинг 1 0. 1 О. suЬproce s syopen wri te . ру _

import subproce s s p r i n t ( ' wr i t e : ' ) proc = s ubproce s s . Popen ( [ ' са t ' ' ' - ' ] ' s t din�subpro ce s s . P I PE , proc . communi ca t e ( ' s t di n : t o s t d i n \ n ' . encode ( ' ut f - 8 ' ) )

Чтобы однократно направить данные в канал стандартного ввода процесса, их следует передать методу c ommun i ca t e ( ) . Этот подход аналогичен использованию функции p o p e n ( ) в режиме ' w ' , $ pythonЗ -u s ubproces s_popen_wri t e . py write : s t di n : to s t d i n

1 О. 1 2 2 Двухстороннее взаимодействие с процессом .

.

.

Чтобы настроить экземпляр Popen как для чтения, так и для записи, следует использовать комбинацию предыдущих подходов. Лисrинг 1 0. 1 1 . suЬproces s_y open2 . ру impor t s ubproces s p r i n t ( ' popen2 : ' ) proc

subp r o c e s s . Pope n ( '-'], s t di n= subproces s . PI P E , s t dout=subproces s . P I PE , =

[ ' ca t ' ,

msg ' t hrough s t d i n t o s t dout ' . en code ( ' ut f - 8 ' ) s t dout_va lue proc . commun i ca t e ( rns g ) [ О ] . de code ( ' ut f -8 ' ) p r i n t ( ' pa s s through : ' , r epr ( s t dout_va l u e ) ) =

=

Настроенный таким образом канал имитирует вызов функции popen2 ( ) . $ pythonЗ -u s ubproces s_popen2 . py popen2 : pas s t h rough :

' t hrough s td i n to s t dout '

1 О. 1 .2.3. Перехват вывода сообщений об ошибках

Также возможно наблюдать одновременно за двумя потоками , как это делается с помощью функции рореnЗ ( )

s tde r r,

Лисrинг 1 0. 1 2. suЬproces s_y open3 . py irnport subproce s s p r i n t ( ' рореnЗ : ' ) proc subp r o ce s s . Popen ( ' cat - ; e cho " to s t de r r " 1 > & 2 ' , s h e l l"True , s t din= s ubproce s s . PI PE, s t dou t • s ubproce s s . P I PE , s t d e r r= subproce s s . PI PE , =

.

s tdout

и

ГА888 10. Jlараммыtые вычмсмим: процессы, nоrокм и соnроrраммы

556

msg ' t hrough s t d i n to s t dout ' . encode ( ' ut f - 8 ' ) s t dout_v a l u e , s tderr_va lue pr o c . communica t e ( rns g ) pr i n t ( ' pa s s through : ' , repr ( s t dout_va l u e . de code ( ' u t f- 8 ' ) ) ) : ' , repr ( s tderr_va l u e . d e code ( ' ut f- 8 ' ) ) ) p r i n t ( ' s t derr =

=

Чтение из потока s tderr рабоrает так же, как и чтение из потока s tdou t. Передача значения P I PE сообщает экземпляру Popen о необходимости подключс· ния к каналу, а метод communi ca te ( ) 'IИтает все даппые из него, прежде чем вы­ полнить возврат. $ pythonЗ -u subproces s_popenЗ . py рореn З : p a s s through : s td e r r

' t hrough s t din to s t dout ' ' to stderr \n '

1 0 . 1 .2 .4. Объединение каналов стандартного вывода и ошибок

Чтобы направить вы вод сообщений об ошибках из процесса в канал его стан­ дартного вывода, следует использовать в качестве зна'lения ар1')'мен·га s tder r константу S T DOUT вместо P I PE. Листинг 1 0. 1 3. suЬproces s_popen4 . ру import subproc e s s p r i n t ( ' popen 4 : ' ) proc = subproces s . Popen ( ' cat - ; echo " to s t derr " 1 > & 2 ' , s h e l l=Tru e , s tdi n=subproces s . P I PE , s tdou t = s ubp roces s . P I PE , s t d e r r=subproce s s . ST DOUT , msg ' t hrough s t d i n to s tdout \ n ' . encode ( ' u t f - 8 ' ) s t dout v a l u e , s t de rr va l u e proc . communi c a t e ( ms g ) print ( 1 comЬ i ned outpu t : ' , repr ( s tdout_val u e . de code ( ' ut f - 8 ' ) ) ) print ( ' s t d e r r va l u e : ' , r epr ( s tde r r_va l u e ) ) =

=

Подобное объединение каналов вывода имитирует работу функции popen4 ( ) . $ pythonЗ -u s ubproces s_popen 4 . py popen 4 : comЬ i ned output : ' t hrough s t din to s t dou t \ n t o s t der r \n ' stderr value None

1 0. 1 .3. Соединение сегментов канала Несколько команд можно соединить, чтобы они образовали коШJейер в стиле оболочки Uнix, создав отдельные экземпляры Popen и объединив в одну цепочку их каналы ввода и вывода. Атрибут s tdou t одного экземпляра Popen используется

557

вместо константы P I PE в качестве ар1-умента s t d i n следующего экземпляра в кон­ вейере. Вывод читается из дескриптора s tdout 11оследней команды конвейера. Листинг 1 0 . 1 4. suЬproces sy ipe s . py import subproces s cat = s ubp r o c es s . Pope n ( [ ' cat ' , ' i ndex . rs t ' ] , s t dout = s ubp roces s . P I P E ,

g r e p = s ubproces s . Popen ( [ ' g r ep ' , ' " l i t e r a l i n c l ude : : ' ] , s t d i n=cat . s t dout , s t dout =subproce s s . P I P E ,

cut

subp r o c e s s . Popen ( [ ' cut ' , ' - f ' , ' 3 ' , ' -d : ' ] , s t di n= g r ep . s t dout , s t dout = s ubproce s s . P I PE ,

=

e nd_o f_pipe

=

cut . s t dout

p r i n t ( ' I nc luded f i l es : ' ) for l i n e i n end_o f_pipe : p r i nt ( l i n e . de code ( ' ut f - 8 ' ) . s t r ip ( ) )

Этот пример воспроизводит выполнение следующей команды: $

c at i ndex . r s t 1 grep " · · l i t e r a l i nc l ude " 1 cut -f 3 -d :

Конвейер 11итает файл в формате r e S s t ructur edText ( irtdex. ·rst) для данного ра:щсла и находит в нем вес строки, включающие другие файлы. После этого 011 выводит имена всех вклю•1аемых файлов. $ pythonЗ -u s ubpr o c e s s_pipes . py I n c l uded f i l e s : s ubpr o c e s s_o s_sys t em . py subprocess_s h e l l_va r i aЫ e s . py s uЬp roces s_run_che ck . py s ubproces s_run_output . py s uЬp r o c e s s _run_out put_e r r o r . py subproces s_run_output_e rror_t rap . py subproces s_che ck_output_e r r o r_t rap_out put . py s ubproces s_run_output_e rro r_supp r e s s . py subproces s_popen_read . py s uЬproces s_popen_wr i t e . py s ubpro c e s s_popen2 . py suЬprocess_popenЗ . py subp r o c e s s_popen4 . py

Гмва 10. ПарМ\МWtые ВЫЧМС/\8НИR: nPDЦ8CClll, nсmмсм и соnроrреммы

558

s ubproces s_pipe s . py repeat e r . py i n t e r a c t i on . py s i gnal _ chi l d . ру s i gnal_parent . py s ubp roc e s s_s i g n a l parent s h e l l . py subproce s s_s i gna l_s etpgrp . py

1 0. 1 .4. Взаимодействие с другой командой Во всех предыдущих примерах предполагалась ограниченная степень взаимо­ действия. Метод c ommu n i c a t e ( ) читает весь вывод и ожидает завершения дочер­ него процесса, прежде чем вернуть управление. Помимо этого существует воз· можность читать и записывать информацию с помощью отдельных дескрипторов каналов, используемых экземпляром Popen для инкрементного обмена данными по мере выполнения программы. В следующем примере в качестве дочернего процесса используется сценарий repea t e r . ру. Этот сценарий читает данные из канала s t d i n и за п исывает в канал s t dout по одной строке за раз до тех пор, пока не перестанет получать входные данные. Он также записывает в канал s t de r r сообщения о начале и завершении выполнения, что позволяет судить о времени жизни дочернего процесса. Лисrинг 1 0. 1 5. repeater . py impo rt sys s ys . s t derr . wr i t e ( ' repeate r . py : s t a r t i n g \ n ' ) s ys . s tderr . f l u s h ( ) wh i l e True : next_l i n e s ys . s t di n . r e adl i n e ( ) s ys . s t d e r r . f lush ( ) i f not next l i ne : break sys . s t dout . wr i t e ( next_l i ne ) s ys . s t dout . f lush ( ) =

sys . s t derr . wr i t e ( ' repeate r . py : exi t i n g \ n ' ) sys . s t derr . f l u s h ( )

В привеl\ешюм ниже сценарии дескрипторы файлов s t d i n и s tdou t , принад­ лежащие экземпляру Popen, используются двумя способами. Сначала последова­ телыюсть, включающая пять чисел, записывается в канал s t di n процесса; после каждой операции записи выводимые данные считываются обратно. Далее зап и­ сываются те же пять чисел, но весь вывод читается за один раз с помощью метода commun i c a t e ( ) .

Лисrинг 1 0. 1 6. interaction . py import i o import s ubp r o ce s s

558 p r i n t ( ' On e l i ne at а t ime : ' ) proc = subproces s . Popen ( ' pythonЗ repeater . py ' , shel l=True , s t d i n= s ubproces s . P I P E , s t dout = s ubp roce s s . P I P E , stdin io . T ex t I OWrappe r ( proc . s t d i n , encodi ng= ' ut f- 8 ' , l i ne_bu f f e r i ng=T rue , # от прав ка да н ных при получении # симво л а перевода строки =

i o . Text I OW rappe r ( s t dout p roc . s t dout , encoding= ' ut f- 8 ' , f o r i i n range ( 5 ) : line ' { } \ n ' . fo rmat ( i ) s t di n . w r i t e ( l i n e ) output = s t dout . readl i n e ( ) p r i n t ( output . rs t r ip ( ) ) rema inder proc . commun i cat e ( ) [ 0 ] . decode ( ' ut f- 8 ' ) p r i n t ( rema i nde r ) =

=

p ri n t ( ) p r i n t ( ' Al l output a t once : ' ) proc subproces s . Popen ( ' pytho n З repea t e r . py ' , s h e l l=T rue , s t din= s ubp ro c e s s . P I P E , s t dout = s ubproce s s . P I P E , =

s t di n i o . тext I OW rappe r ( proc . s td i n , encodi ng= ' ut f - 8 ' , =

f o r i i n range ( 5 ) : l i ne ' { } \n ' . fo rma t ( i ) s t di n . wri t e ( l ine ) s t di n . f l u s h ( ) =

output = proc . commun i ca t e ( ) [ 0 ] . decode ( ' ut f - 8 ' ) p r i n t ( output )

Ст1юки " repeater . р у : exit ing " появляюТуппа процессов создается при помощи функции os . setpgrp ( ) , которая устанавливает для идентификатора группы значение идентификатора текущего процесса. Вес до­ черние процессы наследуют членство в группе от своего родителя. Поскольку эт-а группа должна быть установлена только в оболочке, создаваемой объектом Popen и его наследниками, то функция os . setpgrp ( ) не должна вы:iываться в том же про­ цессе, в котором создается объект Popen. Вместо этого данная функция передается конструктору Popen в качестве аргумента preexec_ f n , т-а к что опа выполняется по­ сле вызова fo r k ( ) в новом процессе до тою, как использует функцию ехес ( ) для выполнения оболочки. (}шравка сигнала всей группе процессов осуществляется при помощи функции os . k i l lpg ( ) со значением pid из экземпляра Popen. Листинг 1 0.20. suЬproce s s_ signal_setpgrp . ру import import import import impo rt impo rt

os s i gnal s ubpro c e s s t emp f i l e t ime s ys

583 d e f s how_s e t t i ng_p rgrp ( ) : p r int ( ' Ca l l ing os . s etpgrp ( ) f rom { } ' . fo rmat ( os . getpid ( ) ) ) os . s e tpgrp ( ) p r i nt ( ' Pro c e s s g roup i s now ( ) ' . fo rmat ( os . getpid ( ) , os . ge tpgrp ( ) ) ) s ys . s t dout . f l u s h ( ) s c r i pt ' ' ' # ! /b i n / s h echo " Sh e l l s c ript in process $ $ " s e t -х p ythonЗ s i gnal _ chi l d . py =

1 1

1

s c r ipt_f i l e t emp f i l e . N amedT empo r a r y F i l e ( ' wt ' ) s c r i pt_f i l e . wr i t e ( s c r ipt ) s c r ipt_fi l e . f l u sh ( ) =

p roc

s ubproces s . Popen ( [ ' s h ' , s c r ipt_f i l e . name ] , preexec_fn=s how_s e t t ing_p r g r p , =

: Paus ing b e f o r e s i gnal i ng ( } . . . ' . fo rmat ( print ( ' PARENT proc . p i d ) ) s ys . s t dout . f lush ( ) t ime . s l e ep ( l ) p r i n t ( ' PARENT S i gnal ing proc e s s g roup ( } ' . fo rmat ( p roc . pi d ) ) s ys . s t dout . f lush ( ) o s . k i l lpg ( proc . p i d , s i gnal . S I GUS R l ) t ime . s l ee p ( 3 )

Соответствующая последовательность событий описана ниже. 1 . Родительская программа создает экземпляр класса Popen.

2. Экземпляр Popen порождает новый процесс. 3. Новый процесс выполняет функцию os . setpgrp ( ) . 4. Новый процесс выполняет функцию е х е с ( ) для запуска командной обо­ лочки. 5. Оболочка выполняет сценарий. б. Сценарий оболочки вновь порождает процесс, и этот процесс выполняет код Руtlюп. 7. Код Руtlюп вьшолняет сценарий s igna l_chi ld . ру. 8. Родительская программа посылает сигнал группе процессов, испольауя идентификатор pid оболочки. 9. Процессы оболочки и Pythoп получают сигнал. 1 О. Оболочка игнорирует сигнал. 1 1 . Процесс Pythoп, выполняющий сценарий s i gnal_ch i ld . py, вызывает об­ работчик сигналов.

ГАавв 10. Пвраммw1ые вwчисмttМR: процессw, nсmжм и соnроrрвммы

584

$

python3 subprocess_s i gnal_s etpg rp . p y

Cal l i n g o s . s e t pg rp ( ) f rom 2 6 9 9 2 P ro ce s s g roup i s now 2 6 9 9 2 PARENT : Pau s i ng before s i gna l i ng 2 6 9 9 2 . . . She l l s c r ipt i n proce s s 2 6 9 9 2 + python3 s i gnal chi l d . py C H I L D 2 6 9 9 3 : S e t t i ng up s i gnal handl e r C H I L D 2 6 9 9 3 : Pau s i n g t o wa i t f o r s i g nal PARENT S i gna l i ng proce s s g roup 2 6 9 9 2 C H I L D 2 6 9 9 3 : Re c e i ved USRl

Дополнительные ссылки • •



• •



Раздел документации стандартной библиотеки, посвященный модулю s ubpro ces s 1 • os (раздел 1 7.3). Несмотря на то что функции модуля s ubp roc e s s заменяют многие функции модуля os, предназначенные для работы с процессами, последние все еще широко применяются в существующем коде. UNIX Signols ond Process Groups2 • Неплохое описание работы с сигналами Unix и груп­ пами процессов. s i gnal (раздел 1 0.2). Более подµобное описание модуля s i gna l . Advonced Progromming in the UNIX Environment, Third Edition3 • Обсуждение таких аспек­ тов работы с несколькими процессами, как обмен сигналами и закрытие дубликатов файловых дескрипторов. pipe s . Шаблоны конвейеров оболочки U nix в стандартной библиотеке.

1 0. 2 . s ignal: асинхронные системные события Си.талы :iто средство операционной системы, обеспечивающее доставку уведомлений о наступлении каких-либо событий и их асинхронную обработку. Сигналы могут генерироваться самой системой или посылаться одним процессом другому. Поскольку сигналы прерывают обычный ход выполнения программы, некоторые операции (особенно ввода-вывода) могут приводить к ошибкам, если сигнал был получен во время их выполнения . Сигналы идентифицируются целыми числами и определяются в заголовоч­ ных файлах С 011ерацио1111ой системы. Руt lюп предоставляет сигналы, соответ­ ствующие конкретной платформе, н виде символов в модуле s igna l . В примерах, приведенных в этом разделе, используются сигналы S I GINT и S I GUSR l , которые определены почти во всех lJ11iх-11одобных системах. -

Примечание

Программирование с использованием обработчиков сигналов U nix задача непростая. Данный раздел служит лишь введением в эту сложную тему и не охватывает все детали, -

1 •

3

https : / /docs . python . o rg / 3 . 5 / l ibra r y / subp r o c e s s . html www . c s . uc s b . edu / � a lme r o th / c l a s s e s /W 9 9 . 2 7 6 / a s s i g nment l / s i gn a l s . html https : / / www . ama z on . com/Adva nced- P rog rammi ng-UNIX-Envi ronme nt - 3 rd / dp / 0 3 2 1 6 3 7 7 3 9 /

10.2. slgnal: асмнхронные смс:темные со6ыn111 знание которых необходимо для успешного использования сигналов на всех платформах. В различных версиях U пix соблюдается определенная степень стандартизации, однако возможны некоторые расхождения. Для получения более подробной информации по этому вопросу следует обратиться к документации операционной системы.

1 0. 2. 1 . Получение сигналов Как и в случае других подходов, основанных на обработке событий, для полу­ чения сигналов используют функции о б ратн о го в ызова - обработ11.ики си2иалов, которые вызываются при 11олу•1ении си1·на.ла. Аргументами обработч ико в служат номера сигналов и кадры стека из той точки программы, в которой сигнал прер­ вал ее выполнение. Лисrинг 1 0.21 . signal signal . ру _

impo r t s i gnal impo r t o s impo r t t ime

d e f r e c e i ve_s ignal ( s i gnum, s t a c k ) : p r i n t ( ' Rece ived : ' , s i g num ) # Реги с тра ция дескрип торов сигналов s i gnal . s i gnal ( s i gnal . S IGUSRl , r e c e i v e_s i g na l ) s i gnal . s i gnal ( s i gnal . S IGUSR2 , r e c e i v e_s i gnal ) # Выв о д I D процесса , кото р ый впоследствии можно буде т # исполь зов а т ь с кома н дой ki l l для о тправки сигналов э той прог р амме p r i n t ( ' Му P I D i s : ' , o s . getpi d ( ) )

wh i l e True : p r i n t ( ' Wa i t i ng . . . ' ) t ime . s l eep ( 3 )

В этом примере с це н а рий выполняет бесконечный цикл, делая паузы длитель­ ностью в несколько секунд на каждой итера� щи. При п олуч е н и и сигнала пызоn s l eep ( ) прерывается и об раб отч ик recei ve s i gnal выводит па кoнcoJJI. п о м е р сигнала. Выполнение цикла продолжается , когда обработчик сигнала возвращает управление. Для отправки сигналов вы1юлю1 ющсйся пр ограмме можно исполь:ювать в ы­ зов o s . kill ( ) или программу k i l l ко м андн ой строки Uнix. _

$

python3 s i gnal_s i gnal . py

Му P I D i s : 7 1 3 8 7 Wa it ing . . . Wa i t i ng . . . Wai t i ng . . . Received : 3 0 Wa i t i ng . . .

566 Wa i t i ng . . . Re cei ved : 3 1 Wa i t i ng Wa i t i ng . . . T ra ceba ck ( mo s t r e cent ca l l l a s t ) : Fi l e " s i gna l_s i g na l . py " , l i ne 2 8 , i n t ime . s l ee p ( 3 ) Keyboardi n t e r rupt .

.



Предыдущий вывод был получен путем запуска сценария s ignal_s ignal . р у в одном окне и выполнения приведенных ниже команд в другом. $ ki l l -USRl 7 1 3 8 7 $ ki l l -USR2 7 1 3 8 7 $ ki l l - I NT 7 1 3 8 7

1 0.2.2. Получение информации о зарегистрированных обработчиках сигналов Чтобы увидеть, какие обработчики зарегистрированы для сигнала, ис1юль­ зуйте функцию get s igna l ( ) . В качестве аргумента ей передается номер сигнала. Возвращаемым значением является зарегистрированный обработчик или одно из специальных значений: S I G_ I GN (если данный сигнал игнорируется) , S IG _ DFL (если испош,зуется поведение, заданное по умолчанию) или None (ее.ли существу­ ющий обработчик сигналов зарегистрирован из С, а не из Руtlюп) . Листинг 1 0.22. signal_getsignal . ру import s i gnal

def a l a rm_r e c e i ved ( n , s t a c k ) : return

s i gnal . s i gna l ( s i gnal . S I GALRМ, a l a rm_r e c e i v e d ) s i gna l s_t o_name s { g e t at t r ( s i gna l , n ) : n for n i n di r ( s i gnal ) i f n . s t a r t s wi th ( ' S I G ' ) and =

1

1

not i n n

for s , name i n s o r t e d ( s i gnal s_t o_name s . i t ems ( ) ) ; handl e r s i gna l . ge t s i g na l ( s ) i f handl e r i s s i gna l . S I G_DFL : handl e r ' S IG DFL ' e l i f handl e r i s s i gnal . S I G_IGN : handl e r ' S IG I GN ' p r i nt ( ' { : < l O ) ( { : 2 d } ) : ' . fo rma t ( name , s ) , handl e r ) =

=

=

10.2. slgnal: есмнхроннwе системные собwтмя

587

Опять-таки , поскольку в разных ОС сигналы могут быть определены но-раз­ ному, вывод может меняться от системы к системе. Следующий вывод получен в ОS Х.

$

python 3 s i gna l_ge t s i gna l . py

S I GHUP S I G I NT S I GQU IT S I G I LL S I GTRAP S I G I OT S I GEMT S I GFPE S I GK I LL S I GBUS S IGS EGV S I GSYS S I G P I PE S I GALRМ S I GTERМ S I GURG S I GSTOP S I GTST P S I GCONT S I GCHLD S I GTT IN S I GTTOU SIGIO S I GXCPU S I GXFS Z S I GVTALRМ S I G PROF S I GW INCH S I GI N FO S I GUSRl S I GUSR2

1) 2) 3) 4) 5) 6) 7) 8) ( 9) (10) (11) ( 12 ) ( 13 ) ( 14 ) ( 15) ( 16) (17) (18) (19) (20) (21) (22) (23) (24 ) (25) (26) (27) (28) ( 29) ( 30) ( 31 )

: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :

S I G DFL

S I G DFL S I G DFL S I G DFL S I G DFL S I G DFL S I G DFL None S I G DFL S I G DFL S I G DFL S I G I GN < funct ion a l a rm r e c e i v e d at O x 1 0 0 7 5 7 f2 8 > S I G DFL S I G DFL None S I G DFL S I G DFL S I G DFL S I G DFL S I G DFL S I G DFL S I G DFL S I G I GN S I G DFL S I G DFL S I G DFL S I G DFL S I G DFL S I G DFL

1 0.2.3. Отправка сигналов В Руt lюн для отправки сигналов предназначена функция os . ki l l ( ) . П ример ее использования приведен в разделе 1 7.3. 1 0.

1 0.2.4. Сигналы таймера Сигналы таймера - особый вид сигналов, которые генерируются , если про­ грамма направляет ОС запрос на передачу ей уведомления по истечении опреде­ ленного промежутка нремени. Сигналы этого тина удобно использовать для пре­ дотвраще11ия бесконечных блокировок при выпол11е11ии операций ввода-вывода и других системных вызовов.

568

Г/\ава 10. Парамем.ные вычмсмНМR: npoцeocw, nсmжм м соnроrреммы

Листинг 1 0.23. signal_alarm . py import s i gnal impo rt t ime de f r e c e i ve_a l a rm ( s i gnum, s t a c k ) : print ( ' Al a rm : ' , t ime . ct ime ( ) ) # Вызвать r e c e i ve_a l a rm через 2 секунды s i gna l . s i gnal ( s i gnal . S I GALRМ , r e c e i ve_al arm) s i gnal . al a rm ( 2 ) p r i n t ( ' Be fo re : ' , t i me . c t i me ( ) ) t ime . s l eep ( 4 ) p r i n t ( ' Af t e r : ' , t ime . ct ime ( ) )

В :�том примере выполнение вызова s l eep ( ) прерывается сигналом, но вос­ сташшливается после его обработки. Как следует из сообщения, которое выво­ дится после возврата из функции s leep ( ) , выполнение программы было прио­ станоn.11е110 на время, 110 крайней мере равное установленной длителыюсти паузы. $ pyt hon3 s i gnal_a l a rm . py B e f o r e : Sun S ep 1 1 1 1 : 3 1 : 1 8 2 0 1 6 Sun S ep 1 1 1 1 : 3 1 : 2 0 2 0 1 6 Al arm Aft e r : Sun S ep 1 1 1 1 : 3 1 : 2 2 2 0 1 6

1 0. 2 . 5 . Игнорирование сигналов Чтобы игнорировать сигнал , следует зарегистрировать S I G IGN в качестве об­ работчика. В следующем сценарии обработчик 110 умолчанию для сигнала SI GINT заменяется на S I G_ I GN и устанавливается обработчик для сигнала S I GUSRl. После этого вызов s i gna l . pause ( ) переводит программу в состояние ожидания до по­ лучения сигнала. _

Листинг 1 0.24. signal_ignore . ру import s i gnal impor t os import t ime def do_exi t ( s i g , s t a c k ) : r a i s e S y s t emEx i t ( ' Ex i t i ng ' ) s i gnal . s i gn a l ( s i gnal . S I G I NT , s i gnal . S I G_IGN ) s i gn a l . s i g n a l ( s i gnal . S I GUSRl , do_ex i t ) p r i nt ( ' My PI D : ' , os . getpid ( ) ) s i gnal . pau s e ( )

10.2. &lgnal: асинхронные смсrемные co6wntR

Обычно сигнал S I G INT (сигнал, посылаемый программе командной оболоч­ кой при нажатии пользователем комбинации клавиш ) ( Thread- 1 ( Thread- 1 ) ( T h r ead- 2 ) О х 1 0 1 с бс2 8 8 > ( T hread-2 ) ( Thread- 2 )

I n i t i a l i z i ng
' , e . i s_s et ( ) )

if

name ' ma i n ': е mul t iproce s s i ng . Event ( ) wl mu l t i p roce s s i ng . P roce s s ( name" • Ы oc k ' , t a rget=wa i t_fo r_even t , a rg s= ( e , ) , ==

-

-

=

=

wl . s t a rt ( ) w2

=

mu l t i p r o c es s i ng . Proc e s s ( name" • nonЬ l o c k ' , t a rget:wai t_fo r_event_t imeout , a rgs: ( е , 2 ) ,

Г/\888 10. Перемем.нwе вwчмСАеt1МR: npoцeccw, nсmжм и соnроrреммы

812 w2 . s t a rt ( )

p r i n t ( ' ma i n : wa i t i ng b e f o re cal l i ng Event . s e t ( J ' ) t ime . s l e ep ( З ) е . set ( ) p r i n t ( ' mai n : event i s s et ' )

По истечении тайм-ау:га метод wa i t ( ) возвращает управление, нс генерируя ошибку. Ответствен ность за проверку состояния объекта события с помощью ме­ тода i s _ s e t ( ) возлагается на вызывающий код.

$

pyt honЗ -u mul t iproc e s s i ng_event . py

ma i n : wa i t i n g be fore ca l l i ng Event . s e t ( ) wai t_for_event : s t a r t i ng wa i t_for_event_t imeout : s t a r t i ng wa i t_fo r_event_timeout : e . i s_s et ( ) - > Fal s e ma i n : event i s s e t wa i t for event : e . i s_s e t ( ) -> T rue

1 0.4. 1 2 . Управление доступом к ресурсам Чтобы избежать конфликтов доступа к ресурсу, разделяемому несколькими процессами , можно использовать экземпля р класса Loc k . Листинг 1 0.66. mul tiproces sing_lock . ру impo rt mu l t i proce s s ing import sys de f wo r ke r_wi t h ( l o c k , s t r e am ) : wi t h l o c k : s t ream . wri t e ( ' Lo c k acqui red v i a w i t h \ n ' ) d e f wo r ke r_no_wi th ( l o c k , s t r eam ) : l o c k . acqu i r e ( ) try : s t re am . wr i t e ( ' Lo c k acqu i r e d d i r e c t l y \ n ' ) f i n al l y : lock . release ( ) lock mu l t i p r oce s s i ng . Lo c k ( J w mul t iproce s s i ng . P r o c e s s ( t a r g e t=wo r k e r_wi t h , args= ( l o c k , sys . s t dout ) , =

=

nw = mu l t iproce s s i ng . P roce s s ( t a r g e t =wo r ke r_no_wi th , a r g s = ( l o c k , s ys . s t dou t ) ,

813 w . start ( ) nw . s t a r t ( ) w . j oi n ( ) nw . j o i n ( )

Если бы в :пом примере оба процесса не синхронизировали свой доступ к 110току вывода с помощью блокировки, то их сообщен ия, выводимые на консоль, могли бы перемежаться. $ pyt hon З mu l t i proce s s i n g_l o c k . py Lock a cqui red v i a wi th Lock acqu i r e d d i r e ct l y

1 0.4. 1 3. Синхронизация операций Класс Condi t ion, представляющий условие , позволяе-г синхронизировать от­ дельные ветви вычислений таким образом , чтобы одни из них выполнялись па­ раллельно, тогда как другие - послсдователыю, даже если они 11ри1 1ад11ежат от­ дельным процессам. Листинr 1 0.67. mul tiprocessing_condi tion . py impo r t mul t i p r oce s s i n g impo r t t ime def s t age_l ( cond ) : " " " В ыполнить первый э тап рабо ты , а затем ув едомить s t a g e_2 для продолжения . " 11 11

name = mu lt iproc e s s i ng . c u r r ent_proces s ( ) . name p r i nt ( ' S t a r t i ng ' , name ) wi t h cond : p r i n t ( ' { } don e and ready for s t a ge 2 ' . forma t ( name ) ) cond . not i f y_a l l ( ) def s t a ge_2 ( cond ) : " " " Дождать с я условия , сообщающего , чт о s t a ge_l заверше н . " " " name = mu l t iproces s i ng . current_p r o c e s s ( ) . name p r i nt ( ' S t arting ' , name ) w i t h cond : cond . wa i t ( ) p r i nt ( ' { } running ' . fo rma t ( name ) ) if

name == ' ma i n · condi t i o n mu l t iproce s s i ng . Condi t i on ( ) sl mu l t iproce s s i ng . Pr o c e s s ( name= ' s l ' , t a r g e t = s t age_l , args= ( c o nd i t i on , ) ) -

=

=

-

'

614

l'!laвa 10. П8pah'l8Alol1we

вwчмсмНIМ: npoцeccw, поnжм и сопроrраммы

s 2 c l i en t s [ mul t i proc e s s ing . P r o c e s s ( name= ' s t age_2 [ { } ] ' . fo rmat ( i ) , t a rget=s t age_2 , args= ( cond i t i o n , ) , =

f o r i i n range ( l ,

3)

for с i n s 2 c l i ent s : с . start ( ) t ime . s l e ep ( l ) s l . start ( ) s l . join ( ) for с i n s 2 c l i e nt s : c . j oin ( )

В этом примере второй этап вычислений выполняется параллельно обо ими процессами, по только после того, как выполнен первый этап. $ pythonЗ mul t iproce s s i ng_condi t ion . py S t a r t i ng s l s l done and ready for s t a g e 2 S t a r t i ng s t a ge_2 [ 2 ] s t a ge_2 [ 2 ] runn i ng S t a r t i ng s t age_2 [ 1 ] s t age_2 [ 1 ] runn i ng

1 0.4. 1 4. Контроль одновременного доступа к ресурсам Ино!'да полезно разреш ить нескольким рабочим процессам одновременный доступ к одному и тому же ресурсу, но при этом ограничить общее количество процессов, которым предоставляется такая возможность. В качестве примера можно привести пул, поддерживающий фиксированное количество соединений, или сетевое приложение, поддерживающее фиксированное количество одновре­ менных загрузок. Одним из способов управления подобными соединениями я вля­ ются семафоры , представляемые классом Semaphore. Листинг 1 0.68. mul tiproce s sing_semaphore . ру impo rt random import mu l t i p roce s s ing import t ime

c l a s s Act i vePoo l : de f

init ( sel f ) : s uper ( Act ive Poo l , s e l f ) . init () s e l f . mg r = mul t iproce s s i ng . Manager ( )

10.4. multlproc1111nc:

мс:nсw.аова н ие

nроцt1ССО8 вместо ncmжoe

sel f . active s e l f . mg r . l i s t ( ) s el f . l o c k = mu l t i p roces s i ng . Lo c k ( ) =

de f ma keAc t i ve ( s e l f , name ) : with s e l f . l o c k : s e l f . a c t i ve . append ( name ) def ma ke i nact i ve ( s e l f , name ) : with s e l f . l o c k : s e l f . a c t i ve . remove ( name ) def

str ( self ) : with s e l f . l oc k : return s t r ( s e l f . act i ve )

def wo rker ( s , pool ) : name mu l t iproces s i n g . curr ent_proces s ( ) . name wi th s : poo l . makeAc t i ve ( name ) p r i n t ( ' Ac t i va t i ng { ) now runni ng { ) ' . fo rmat ( name , po o l ) ) t ime . s l eep ( random . random ( ) ) poo l . ma ke i nact i ve ( name ) =

if

name ma i n • poo l Ac t i vePoo l ( ) s mul t iproce s s i ng . S emapho r e ( З ) j ob s [ mul t i proce s s i ng . P roce s s ( t a rget=wo r ke r , name=s t r ( i ) , args= ( s , poo l ) , ==

'

·

=

=

=

for i i n range ( l O ) for j i n j ob s : j . s tart ( ) whi l e T r u e : a l i ve О for j in j ob s : i f j . i s_a l i ve ( ) : a l i ve += 1 j . j o i n ( t imeout=0 . 1 ) p r i n t ( ' Now runn i ng { ) ' . format ( pool ) ) i f a l i ve О: # в се задачи выполнены Ьreak "'

=•

81 5

618

ГА& ва 10. 11араммыtые 11ЫЧИСМНМ'8: npoцeccw, 11ОТО1О1 и соnроrраммы

В этом примере в качестве удобного средства, позволяющего отслеживать процессы , выполняющиеся в заданны й момент времени , используется класс Act ivePool . Вероятно, при работе с реальным пулом такие ресурсы, как соеди­ нения, распределялись бы между активными процессами и возвращались в пул по завершении выпол нения задачи. В данном с;1у•1ае пул используется всего лишь для хранения имен активных процессов и демонстрации того факта, что только три процесса выполняются одновремен но. $ python3 - u mul t ip r o c e s s i ng_semapho r e . p y Act i v a t i n g О now Ac t i vat i ng 1 now Ac t i va t i n g 2 now Now runni n g [ ' О ' , Now runni n g [ ' О ' , Now runn i n g [ ' О ' , Now runni n g [ ' О ' , Act i v a t i n g 3 now Ac t i va t i ng 4 now Act i vat ing б now Now runn i n g [ ' 1 ' , Now runni ng [ ' 1 ' , Ac t i va t i n g 5 now Now runn i ng [ ' 1 ' , Now runni ng [ ' 1 ' , Now r unni ng [ ' 1 ' , Act i va t i n g 8 now Now runni n g [ ' 4 ' , Now runni ng [ ' 4 ' , Now runni ng [ ' 4 ' , Now runn i n g [ ' 4 ' , Now runni n g [ ' 4 ' , Act i va t i ng 7 now Now runni ng [ ' 5 ' , Act i va t i n g 9 now Now runni ng [ 1 8 1 , Now runni ng [ ' 8 ' , Now runn i n g [ ' 8 ' , Now runn i n g [ ' 9 ' ] Now runn i n g [ ' 9 ' ] Now running [ ' 9 ' ] Now runni ng [ ' 9 ' ] Now runn i n g [ ]

runn i n g [ ' running [ ' runn ing [ ' '1', '2'] '1', '2' ] '1', '2'] '1', '2' ] runni n g [ ' runni ng [ ' runn ing [ ' '4' , ' б' ] '4', 'б' ] runn i ng [ ' '4', '5'] '4', '5'] '4', '5'] running [ ' '5', 18 ] '5', '8 ] '5', '8'] '5', '8' ] '5' , ' 8' ] run n i n g [ ' '8', '7'] runn ing [ ' '7', '9' ] '9' ] ' 9' ]

0', О', О',

'1', '1', '1',

'2'] '2'] '2']

0' , 1', 1',

'1', '3', '4',

'3'] '4'] '6' ]

1' ,

'4',

'5' ]

4',

'5',

'8']

5' ,

'8',

'7']

8',

'7',

'9']

1



1 0.4. 1 5. Управление разделяемым состоянием В предыдущем примере список активных процессов поддерживался цептра­ лизовашю в экземпляре Act i vePool объектом списка специальнш·о ·m па, предо­ ставляемым классом Manager . Этот класс отвечает за координацию и нформации о разделяемом состоянии между всеми своими пользователями.

817 Листинг 1 0.69. mul tiproces sing_manager_di ct . py import mul t iproce s s i ng import ppr i nt

de f wo r ke r ( d , key, va l ue ) : d [ ke y ] = v a l ue

if

name == ' ma i n ' : mg r mu l t iproce s s i ng . Man a g e r ( ) d mgr . di c t ( ) j obs = [ mu l t iproce s s i ng . Proce s s ( t arget=wor ke r , args= ( d , i , i * 2 ) , =

=

for i i n range ( l O ) for j i n j obs : j . start ( ) for j i n j obs : j . j oi n ( ) p r i n t ( ' Re s u l t s : ' , d )

Поскольку этот список создается посредством менеджера, 011 разделяется все­ ми процессами, а его обновления видимы в каждом из них. Поддерживаются так­ же словари. $ pytho n З mul t i proce s s i ng_manager_d i c t . py Re s ul t s : { О : О , 1 : 2 , 2 : 4 , 3 : 6 , 4 : В , 5 : 1 0 , 6 : 1 2 , 7 : 1 4 , В: 16, 9: 18}

1 0.4. 1 6. Разделяемые пространства имен Кроме словарей и списков класс Manage r позволяет со3Давать разделяемые пространства имен. Листинг 1 О.70. mul tipro cessing_namespaces . ру irnport mul t iproces s i ng

de f produce r ( ns , event ) : ns . value = ' Th i s i s the val ue ' event . s e t ( )

d e f consume r ( ns , event ) : try : pr int ( ' Before event :

{ } ' . fo rma t ( ns . va l ue ) )

ГА888 10. ПарвмеАwtые вычмсмнмfl: npoцeccw, псmжм м сопроrраммы

818

except Excep t i on as e r r : p r i n t ( ' Be fore event , e r ror : ' , s t r ( e r r ) ) event . wa i t ( ) p r i n t ( ' Af t e r event : ' , n s . va l ue )

name == ' ma i n ' · mg r mul t iproce s s i ng . Manager ( ) namespace = mg r . Namespace ( ) event mul t iproce s s i ng . Event ( ) mu l t iproc e s s i ng . P roce s s ( р t a rget=pr oduce r , a r g s = ( name space , event ) ,

if

=

=

=

с = mu l t iproce s s i n g . P ro c e s s ( t a rg e t = consume r , a r g s = ( namespace , event ) ,

c . start ( ) p . start ( ) c . j oin ( ) р . j oin ( )

Любое именованное значение, добавляемое в пространство имен , становится видимым для всех клиентов, полу чающих экземпляр Namespace.

$

pythonЗ mu l t iproces s i ng_name space s . py

Before event , e r ro r : ' Namespace ' obj e c t has no a t t r i bute ' va l ue ' Aft e r event : Thi s i s the va l u e

Обновление содержимого изменяемых значений в пр01зова ей передаются любые дополнительные 11озициониые ар1ументы , указанные вслед за функцией обратного вызова при вы­ полнении метода ca l 1 s oon ( ) . Передать фун кции обратного вызова J(}Jючевые аргументы можно с помощью функции pa r t i a l ( ) из модуля functoo l s (см. раз­ дел 3. 1 ) . _

_

Листинг 1 0.80. a synoio_oal l_soon . py import a s yn c i o import func t oo l s

d e f cal lba c k ( a r g , * , kwa rg= ' de fau l t ' ) : p r i n t ( ' cal l back i nvo ked w i t h { } and { } ' . fo rma t ( a r g ,

kwa rg ) )

a s ync d e f ma i n ( l oop ) : p r i n t ( ' r e g i s t e r i n g c a l lbacks ' ) loop . cal l_s oon ( c a l lbac k , 1 ) wr apped funct o o l s . pa r t i a l ( ca l lba c k , kwa rg= ' not d e f au l t ' ) l oop . cal l_soon ( wrapped , 2 ) =

awa i t a s ync i o . s l eep ( 0 . 1 )

event_l oop = a s y n c i o . ge t_event_loop ( ) try: p r i nt ( ' ent e r i ng event loop ' ) event_l oop . run_unt i l_comp l e t e ( ma i n ( event_loop ) ) f i nal l y : p r i n t ( ' c l o s i ng event l o op ' ) eve nt_l oop . c l o s e ( )

Функции обратного вызова за11уска1отся в том порядке, в каком они бьши за­ планированы. $ pythonЗ a syncio_cal l_s o o n . py e n t e r i n g event l oop r e g i s t e r ing cal lbacks c a l lba c k invoked wi t h 1 and defau l t c a l lb a c k i nvoked wi t h 2 a n d not default c l o s i ng event l oop

832 1 0.5.3.2. Планирование обратного в ызова с задержкой во времени

Чтобы отложить выполнение функции обратного вызова на более поздний момент времени, следует использовать метод c a l l _ later ( ) . Его первым аргумен­ том является длительность задержки в секундах, вторым - функция обратного вызова. Листинг 1 0.81 . asyncio_cal l_l a ter . ру import a s yncio

de f ca l lb a c k ( n ) : p r i nt ( ' ca l lback { ) i nvoked ' . forma t ( n ) )

a s ync de f ma i n ( l oop ) : p r i nt ( ' r e g i s t e r i ng cal lba c k s ' ) l o op . c a l l_l a t e r ( 0 . 2 , cal lbac k , 1 ) l oop . c a l l _ l a t e r ( 0 . 1 , cal lbac k , 2 ) l oop . cal l_soon ( ca l l b a c k , 3 ) awa i t a s ync i o . s l eep ( 0 . 4 )

event_loop a s y n c i o . ge t_event_l oop ( ) try : p r i nt ( ' e n t e r i ng event loop ' ) e vent_l oop . run_un t i l _compl e t e ( mai n ( event_loop ) ) f i na l l y : p r i nt ( ' c l o s i ng event loop ' ) event_loop . c l o s e ( ) =

В этом примере выполнение той же функции обратного вызова заrшапирова­ но на несколько различных моментов времени с разными аргументам и. Вызов, за­ планированный с помощью метода c a l l_soon ( ) , которому был передан аргумент 3, выполняется раньше других, тем самым подтверждая тот факт, что планирова­ ние вызова "на ближайшее время" обычно означает минимальную задержку. $ pythonЗ a s yn c i o_ca ll_l a t e r . py

e n t e r i ng event l oop reg i s t e r i n g cal lbacks cal lback З i nvoked callback 2 i nvoked cal lback 1 i nvoked c l o s i ng event loop

1 0.S.3 .3. Планирование обратного вызова на определенное время

Также существует возможность запланировать вызов на определенное время . Исполь..'lуемый для этого цикл основывается на монотонных часах, а не на часах истекшего времени, что исключает отнесение момента времен "сейчас" к про-

шлому моменту времени. Чтобы выбрать время для запланированного обратного вызова, необходимо вести отсчет от внуrреннего состояния этих часов, исполь­ зуя метод t ime ( ) цикла. Листинг 1 0.82. asyncio_call_at . py import a s yn c i o import t i me

de f cal lback ( n , l o op ) : pr i nt ( ' ca l lba c k { } i nvoked at { } ' . fo rmat ( n ,

a s yn c de f mai n ( l oop ) : now loop . t ime ( ) p r i nt ( ' c l o c k t ime : pr i nt ( ' l oop t ime :

loop . t ime ( ) ) )

=

{ } ' . fo rmat ( t ime . t ime ( ) ) ) { } ' . fo rmat ( now ) )

p r i nt ( ' reg i s t e ring cal lbacks ' ) loop . c a l l_at ( now + 0 . 2 , c a l l b a c k , 1 , l oop ) l oop . c a l l_at ( now + 0 . 1 , cal lback, 2 , l oop ) loop . c a l l_soon ( ca l lback, 3 , l o o p ) awa i t asyncio . s leep ( l )

event_l oop a s yn c i o . ge t_eve nt_loop ( ) try : p r i n t ( ' e n t e r i ng event l o op ' ) event l oop . run unt i l comple te ( ma i n { event l o op ) ) f i na l l y : p r i nt ( ' c l o s i n g event loop ' ) event_l oop . cl o s e ( ) =

-

-

Обратите внимание на то, что время , соответствующее часам цикла, не совпа­ дает со значением , возвращаемым функцией t ime . t ime ( ) . $ python3 a s y n c i o_cal l_at . p y

e n t e r i n g event l o op c l o c k t ime : 1 4 7 9 0 5 0 2 4 8 . 6 6 1 9 2 l oop t ime : 1 0 0 8 8 4 6 . 1 3 8 5 6 8 8 5 r e g i s t e r i ng cal lbacks cal lba c k 3 i nvoked at 1 0 0 8 8 4 6 . 1 3 8 6 7 9 5 6 c a l lba c k 2 invoked a t 1 0 0 8 8 4 6 . 2 3 9 9 3 1 5 5 5 c a l l ba c k 1 invoked a t 1 0 0 8 8 4 6 . 3 4 3 4 8 0 9 9 6 cl o s ing event loop

1 0. 5.4. Асинхронное получение результатов Экземпляр Future представляет результат еще не завершенной работы. Цикл событий может отсJJеживать переход экземпляра Future в состояние "выполне-

1"A81111 1D.

118P8h-\Мbltwe 8Ыt1МСМНИЯ: npoцeccw, nоrокм м сопроrраммы

но" (dопе), тем самым предоставляя возможность одной части приложения ожи­ дать, пока другая его часть завершит работу. 1 0. 5 .4. 1 . Ожидание завершения задания экземпляром Fut u r e

Экземпляр Future (фьючерс) действует подобно сопрограмме, поэтому любые приемы, используемые для ожидания завершения сопрограммы, также примени­ мы к фьючерсам. В следующем примере объект фьючерса передается методу run_ unt i l_complet e ( ) цикла. Листинг 1 0.83. asyncio_future_even t_l o op . py irnport a s yn c i o

de f ma r k_done ( f uture , r e s u l t ) : p r i nt ( ' s et t i ng future r e s u l t t o { ! r ) ' . forrnat ( re su l t ) ) future . s et_r e s ul t ( re s u l t )

event_loop a s ync i o . ge t_event_l oop ( ) try : a l l done a s ynci o . Future ( ) =

=

print ( ' s chedu l ing rna r k_done ' ) event_l oop . ca l l_soon ( ma r k_done , a l l_done ,

' the r e s u l t ' )

p r i n t ( ' en t e r i n g event loop ' ) even t_l oop . run_unt i l_cornp l e t e ( a l l_done ) result print ( ' returned r e s u l t : { ! r ) ' . fo rma t ( r e s u l t ) ) final l y : p r i n t ( ' c l o s i ng event l o op ' ) event_loop . c l o s e ( ) =

p r i n t ( ' future r e s u l t :

{ ! r ) ' . f o rrna t ( a l l_done . re s u l t ( ) ) }

Экземпляр Fu ture переходит в состояние "выполнено", когда вызывается ме­ тод set_ result ( ) , и сохраняет переданный методу результат для последующего извлечения. $ pyt hon З a s yn c i o_future_event_l oop . py s chedu l i ng rna r k_done ent e r ing event loop s e t t i ng future r e s u l t t o ' the r e s u l t ' r e t u r ne d r e s ul t : ' the r e s ul t ' c l o s i ng event l o op future r e s u l t : ' t he resu l t '

Экземпляр Fu ture также можно исноль:ювать вместе с ключевым словом awa i t, как показано в следующем примере.

10.6. aaync:lo: ас:мнхроннwе операции 880А8-8Ь"'ОАВ, цим coбwnti и ИНС1РУМ8НТЫ...

835

Листинг 1 0.84. a syncio_future_awai t . py import a s yncio

de f ma r k_done ( future , r e s u l t ) : pr i n t ( ' se t t i ng future r e s u l t t o ( ! r } ' . f o rma t ( re s u l t ) ) future . s e t_re s ul t ( re su l t )

a s ync de f mai n ( l oop ) : a l l_do ne a s ync i o . Future ( ) =

p r i n t ( ' sche du l i n g ma r k_done ' ) l o op . c a l l_soon ( ma r k_done , a l l_done ,

' the r e su l t ' )

re su l t awa i t a l l done p r i n t ( ' retu rne d r e s u l t : { ! r } ' . f orma t ( re su l t ) ) =

event_l oop a s ynci o . ge t_event_l oop ( ) try : event_l oop . run_u n t i l_comp l e te ( ma i n ( e ve nt_l oop ) ) f i na l l y : event_loop . c l o s e ( ) =

Результат, хранящийся в экземпляре Fu ture, возвращается с помощью ключе­ вого слова awa i t , поэтому во многих случаях один и тот же код может работать как с обычной сопрограммой, так и с экземпляром Future. $ p y t h o n З a s y n c i o_future_awa i t . py s c he du l i n g ma r k_done s e t t ing future r e s u l t to ' the r e s u l t ' re turned resu l t : ' t he r e su l t '

1 0. 5 .4.2. Использование объектов Future с функциями обратного

вызова

Экземпляры Future могут не только работать как сопрограммы, но и запускать функции обратного вызова при завершении работы. Функции обратного вызова выполняются в том порядке, в каком они регистрировались. Л истинг 1 0.85. a synci o_future_cal lback . py import a s yncio i mport functoo l s

de f cal lback ( future , n ) : p r i n t ( ' ( } : future done :

{ } ' . forma t ( n , futu re . re su l t ( ) ) )

INllla

838

10. Пllраммыtые вычмсмнм: npoцeccw, ПОТО1СИ м соnроrраммы

a s ync de f regi s t e r_ca l lbacks ( a l l_done ) : pr i n t ( ' reg i s t e r i n g c a l l b a c k s on future ' ) a l l done . add done cal lback ( functoo l s . pa r t i a l ( c a l lba c k , n= l ) ) a l l done . add done c a l lba c k ( functoo l s . pa r t i a l ( c a l lba c k , n=2 ) )

=

=

=

a s ync de f ma i n ( a l l_done ) : awa i t regi s t e r_c a l lba c ks ( a l l_done ) pr i nt ( ' s e t t i ng re s u l t of future ' ) a l l_done . s et_r e s ul t ( ' the r e s u l t ' )

eve nt_loop a s ynci o . g e t_eve nt_loop ( ) try : a l l done a s yn c i o . Futur e ( ) event_l oop . run_unt i l_compl e t e ( ma i n ( a l l _done ) ) f i na l l y : event_loop . c l o s e ( ) =

=

Функция обратного вызова должна получать один аргумент: экземпляр Future. Если требуется передать доrюлнителы1ые аргументы , и спользуйте функцию functoo l s . par tial ( ) для создания обертки. $ pythonЗ a s yn c i o_future_ca l lba c k . py r e g i s t e r i n g cal lbacks on future s e t t i ng result o f fu tur e 1 : future done : the r e s u l t 2 : future done : t h e r e s u l t

1 0. 5 . 5 . Параллельное выполнение задач Зaд(l;ltU - один из основных способов взаимодействия с циклом событий. Зада­ чи обертывают сопрограммы и отслеживают момент их завершения . Посколь­ ку задачи являются подклассами Future, другие сопрограммы могут ожидать их завершения, и каждая задача имеет результат, который может быть изнлечен по­ сле того, как задача завершится.

1 0. S . S . 1 . Запуск задачи

Чтобы запустить задачу, следует создать экземпляр Task, испол ьзуя метод c r eate_ t a s k ( ) . Результирующая задача будет выполняться как часть параллель­ ных операций, управляемых циклом событий, до тех пор пока выполняется цикл и сопрограмма не возвращает управление. Листинг 1 0.86. asyncio_crea te_ta sk . ру i mport a s ync i o

a s ync de f t a s k_func ( } : p r i n t ( ' i n t a s k_func ' ) return ' the r e s u l t '

10.5. 111Ynclo: асинхроннwе опереции 880AIНЫllCWI, 1U1КА coe5w1иii м инстрр18Н1Ы ...

837

a s ync de f ma i n ( l oop ) : print ( ' creating tas k ' ) task l o op . c r e a t e- t a s k ( t a s k func ( ) ) p r i n t ( ' wa i t i ng f o r { ! r ) ' . for iiia t ( t a s k ) ) return_va lue awa i t t a s k p r i n t ( ' t a s k comp l e t e d { ! r ) ' . f o rma t ( t a s k ) ) p r i nt ( ' r e t urn va lue : { ! r ) ' . fo rmat ( re t u rn_value ) ) =

=

event_loop a s yn c i o . ge t_event_l o op ( ) t ry : event_l oop . run_u n t i l_comp l e t e ( ma i n ( event_loop ) ) f i na l l y : e vent_l oop . cl o s e ( ) =

В этом при мере функция main ( ) ожидает возврата результата задачей , после чего возвращает управление. $ python З a s yncio_cr e a t e_t a s k . py crea t i ng t a s k wa i t i ng for < Ta s k pending coro=< t a s k func ( ) runn ing a t a s y n c i o_crea t e _ta s k . py : l 2 >> i n t a s k func t a s k comp l e t e d return value : ' the r e s u l t '

1 0.5.5.2. Отмена выполнения задачи Сохран ив объект Tas k, возвращенный методом create_t a s k ( ) , можно отме­ нить выполнение задачи до ее завершения. Листинг 1 0.87. asyncio_cancel_tas k . ру import a s ynci o

a s ync de f t a s k_fun c ( ) : p r i n t ( ' i n t a s k_func ' ) return ' t he r e s ul t '

a s yn c de f ma i n ( l o op ) : p r i n t ( ' c r e a t i ng t a s k ' ) t a s k = l o op . c r e a t e_t a s k ( ta s k_func ( ) ) p r i n t ( ' ca n ce l i ng t a s k ' ) ta s k . can c e l ( ) p r i n t ( ' ca n ce l e d t a s k { ! r ) ' . fo rma t ( ta s k ) ) try : awa i t t a s k

Гмва 10. Парммм.ные вычмсмиМR: процессы, nсmжи 11 соnроrраммы

838

except a s ynci o . Cance l l e dErro r : p r i n t ( ' caught e r r o r f rom cance l e d t a s k ' ) else : p r i n t ( ' ta s k r e s u l t : { ! r } ' . fo rma t ( t a s k . r e s u l t ( ) ) )

event_l oop a s yn c i o . g e t_event_l oop ( ) try : e ve nt_l oop . run_un t i l _compl e t e ( ma i n ( event_ l o op ) ) f i na l l y : eve n t_l oop . cl o s e ( ) =

В этом примере выполнение предва р ительно созданной задачи отменяется до запуска цикла событий. Вследствие этого метод run unt i l complete ( ) возбужда­ ет исключение Cancel ledE r ror. _

_

$ python З a s yn c i o_can c e l _t a s k . py creat i ng tas k cance l i ng t a s k cance l e d t a s k > caught e r r o r f r om canc e l e d tas k

Если задача отменяется в то время, когда она ожидает завершения другой параллельной операции , она извещается об этом возбуждением исключения Cance l ledError в точке ожидания . Листинг 1 0.88. asyncio_cancel_task2 . ру impo r t a s yn c i o

a s yn c de f t a s k_func ( ) : p r i n t ( ' i n tas k_fun c , s l eeping ' ) try: awa i t a s yn c i o . s l e e p ( l ) except a s ynci o . Cance l l edErr o r : p r i n t ( ' ta s k_func was can c e l e d ' ) ra i s e r e t u r n ' t he r e s u l t '

def t a s k_ca nc e l le r ( t ) : p r i n t ( ' i n t a s k_cance l l e r ' ) t . cancel ( ) p r i n t ( ' ca n c e l e d the ta s k ' )

a s ync de f ma i n ( l oop ) : p r i n t ( ' c r e a t i n g ta s k ' ) ta s k l oop . c reat e_t a s k ( t a s k_func ( ) ) l oop . ca l l_soon ( ta s k_cance l l e r , t a s k ) =

10.5. вsynclo: всмнхромные оnерацим llllCWНWllOAll, � со6ытмА и инструменты...

839

t ry : awa i t t a s k except a s ynci o . Cance l l e dE r r o r : p r i n t ( ' mai n ( ) a l s o s e e s t a s k as can ce l ed ' )

event_loop asynci o . get_event_loop ( ) try : eve n t_loop . run_unti l_comp l e t e ( ma i n ( event_loop ) ) =

Перехват исключения предоставляет возможность выполнить завершающие операции по освобождению ресурсов, если в этом есть необходимость. $ python З a s yn c i o_cancel_t a s k2 . p y cre a t i ng t a s k i n t a s k_fun c , s l e ep i n g i n t a s k cance l l e r cance l e d t he t a s k t a s k func w a s cance l e d ma i n ( ) a l s o s e e s t a s k a s cance l e d

1 0. 5 . 5 . 3 . Соэдание задач и з сопрограмм

Функция ensure_future ( ) возвращает эк:�емпляр Task, связанный с выполне­ нием сонроl'рам мы. Этот экземпляр можно передать другому коду, который будет ожидать его завершения, не зная , каким образом была сконструирована или вы­ звана сопрограмма. Листинг 1 0.89. asyncio_ensure_future . py import a s ync i o

a s ync de f wrapped ( ) : p r i n t ( ' wrapped ' ) re turn ' r e s u l t '

a s ync de f i n n e r ( t a s k ) : p r i nt ( ' i nner : s t a r t i ng ' ) p r i n t ( ' i n n e r : wai t i ng for ( ! r ) ' . fo rma t ( t a s k ) ) re s u l t awa i t t a s k p r i n t ( ' i nne r : t a s k returned ( ! r ) ' . f o rma t ( r e s u l t ) ) =

a s yn c de f print task print awa i t pr i n t

starter ( ) : ( ' s t a r t e r : crea t i ng t a s k ' ) a s ync i o . e n su r e_futu r e ( wrapp e d ( ) ) ( ' s t a r t e r : wa i t i n g for i n n e r ' ) inne r ( ta s k ) ( ' s t a r t e r : inner returne d ' )

=

ГА888 10. ПерамеАыtые вычис:мн14'1: процес:сw. потоки и соnроrреммы

840

event_l oop a s yn ci o . get_event_l oop ( ) t ry : p r i n t ( ' e n t e r i n g eve n t l o op ' ) result event_l oop . run_un t i l_comp l e t e ( s t a r t e r ( ) ) f i na l l y : event_l o op . c l o s e ( ) =

=

Обратите внимание на то , что сопрограмма, переданная функции ensure_ future ( ) , не запустится до тех пор, пока где-нибудь не будет использовано ключе­ вое слово awai t, разрешающее ее вы п олнение. $ pythonЗ a s yn c i o_ensure_fu t u r e . py e n t e r i n g event l o op s t a r t e r : c r e a t i ng t a s k s t ar t e r : wa i t ing f o r inner i nner : s t a r t i ng i nner : wai t i ng for wrappe d i nne r : t a s k returned ' re s u l t ' s t a r t e r : i n n e r returned

1 0. 5.б. Сочетание сопрограмм с управляющими конструкциями С ли нейным потоком управления между серией сопрограмм можно легко спра­ виться с помощью встроенного ключевого слова awa i t . Используя инструменты , предоставляемые модулем a s yncio, можно создавать более сложн ые управляю­ щие конструкции , ко!орые позволяют одной сопрограмме дожидаться заверше­ ния нескольких других сопрограмм, вьшолняющихся паралл ельно. 1 0. 5.б. 1 . Ожидание завершения нескольких сопрограмм

Во многих случаях полезно разбить одну операцию на несколько частей, вы­ полняющихся по отдельности. Например, такой подход эффективен при загрузке данных из нескольких удаленных источников или опросе удаленных программ­ ных интерфейсов. В ситуациях, когда порядок выполнения не имеет значения, а количество операций может быть произвольным, можно использовать функцию w ai t ( ) для приостановки одной программы до тех пор, пока не завершатся дру­ гие операции , выполняющиеся в фоновом режиме. Листинг 1 0. 90. asynoio_wai t . py import a s yn c i o

a s ync de f pha s e ( i ) : p r i n t ( ' i n pha s e { } ' . fo rmat ( i ) ) awa i t a s yncio . s leep ( O . l * i ) p r i n t ( ' done w i t h pha s e { } ' . fo rmat ( i ) ) r e t u rn ' pha s e { } resu l t ' . fo rma t ( i )

10.5. eвynclo: есмнхроннwе операции •llOA8"8WllOA8t циКА событий и иНС11)ументw...

641

a s yn c de f ma i n ( num_pha s e s ) : p r i n t ( ' s t a r t ing ma i n ' ) pha s e s [ pha s e ( i ) for i i n range ( num_phas e s ) =

print ( ' wa i t i ng for pha s e s t o comp l e t e ' ) comp l e t ed, pending awa i t a s yn ci o . wa i t ( phas e s ) result s [ t . r e s u l t ( ) for t i n comp l e t e d ] p r i n t ( ' r e s u l t s : ( ! r } ' . forma t ( r e s u l t s ) ) =

=

e vent_loop a s ynci o . ge t_even t_l oop ( ) t: ry : event_loop . run_unt i l_comp l e t e ( ma i n ( 3 ) ) f i na l l y : event_l oop . cl o s e ( ) =

Функция w a i t ( ) использует множество для хранения э кземпляров Ta s k, кото­ рые она создает, а это означает, что порядок запуска и завершения экаемпляров н е п редсказуем Возвращаемое значение функции wa i t ( ) представляет собой кор1Геж, содержащий два набора: им соответствуют завершенные и вьшшшяющиеся аадачи. .

$ pythonЗ a s y n c i o_wa i t . py s t a r t i ng ma i n wa i t i ng for pha s e s t o comp l e t e i n pha s e О i n pha s e 1 i n pha se 2 done with pha s e О done w i t h pha s e 1 done w i t h pha s e 2 r e s u l t s : [ ' pha s e 1 r e s u l t ' , ' ph a s e О r e s u l t ' ,

' phas e 2 r e s u l t ' ]

Если функция wai t ( ) вызывается с аргументом 1гайм-ауга остаются только незавершенные операции . Листинг 1 0.91 . asyncio_wai t_timeout . py import a s yn c i o

a s ync de f pha s e ( i ) : p r i n t ( ' i n pha s e ( } ' . fo rma t ( i ) ) t ry : awa i t a s yn ci o . s l eep ( 0 . 1 * i ) except a s ync i o . Cance l l edErro r : p r i n t ( ' pha s e ( } cance l e d ' . fo rma t ( i ) ) ra i s e else : p r i n t ( ' done wi t h pha s e ( } ' . fo rmat ( i ) ) r e t u rn ' pha s e ( } res u l t ' . fo rmat ( i )

t imeou t ,

то по истечении

Гм• 10. Параf.мм.ные llЫЧМС:МНМЯ: npoцeccw, nотокм и соnроrрвммы

842

a s ync d e f ma i n ( num_pha s e s ) : p r i n t ( ' s t a r t i ng ma i n ' ) pha s e s [ pha s e ( i ) f o r i i n range ( num_pha s e s ) =

p r i n t ( ' wa i t i n g 0 . 1 for pha s e s t o comp l e t e ' ) comp l e t ed , pending awa i t a s yn c i o . wa i t ( ph a s e s , t imeout= O . l ) p r i n t ( ' ( } comp l e t e d and ( } pending ' . fo rmat ( l e n ( comp l e t ed ) , len ( pe n d i n g ) , ) ) # Отменить о с та вшиеся задачи , ч тобы они не сге н ериров али # о ши б ки , если к момен ту выхода е ще не за в ершились i f pendi n g : print ( ' cance l i ng t a s k s ' ) fo r t in pend i ng : t . cancel ( ) p r i n t ( ' ex i t i n g ma i n ' ) =

event_l oop a s yn c i o . ge t_event_l o o p ( ) try : event_loop . run_u n t i l_compl e t e ( ma i n ( 3 ) ) f i na l l y : event_loop . c l o s e ( ) =

Остальные фоновые операции должны обрабатываться явным образом по нескол ьким причинам. Несмотря на то что выполнение неаавсршенных задач приостанавливается, когда функция wa i t ( ) выполняет во::�врат, оно будет возоб­ новлено, как тол ько управление вернется циклу событий. Без дополнительного вы з ова wa i t ( ) выходные результаты задач никуда пе попадут - задачи будут вы­ полняться и потреблять ресурсы , но от этого не будет никакого результата. Кроме TOl'O , модул1, a s yn c i o вырабатывает предупреждения в тех случаях, когда при вы­ ходе из программы имеются незавершенные задачи. Эти предупреждения моrуг выводиться на экран, чтобы пользователь увидел их. Поэтому лучше всего либо отменить остающиеся незавершенными фоновые операции , либо использовать функцию wai t ( ) , чтобы позволить им завершиться. $ python З a s yn c i o_wa i t_t imeout . py s t a r t i ng ma i n wai t i n g 0 . 1 for pha s e s t o comp l e t e i n pha s e 1 in pha s e О in pha s e 2 done w i t h pha s e О 1 comp l e t ed a nd 2 pending can c e l l i ng t a s k s ex i t i n g ma i n pha s e 1 canc e l l ed pha s e 2 canc e l l ed

1 0. 5 .6.2. Сбор результатов от сопрограмм

Если фоновые фазы определены корректно и имеют значение лишь их резуль­ таты, то для ожидания завершения нескольких о пераций целесообразно исполь­ зовать функцию gather ( ) .

Листинr 1 0.92. a syncio_gather . py imp o r t a s ynci o

a s ync def pha s e l ( ) : print ( ' i n pha s e l ' ) a wa i t a s yn c i o . s l e ep ( 2 ) p r i n t ( ' done wi t h pha s e l ' ) return ' pha s e l r e s ul t '

as ync de f pha s e 2 ( ) : p r i n t ( ' i n pha s e 2 ' ) awa i t a s ync i o . s l e ep ( l ) p r i n t ( ' done w i t h pha s e 2 ' ) return ' pha s e 2 r e s u l t '

a s ync de f ma i n ( ) : p r i n t ( ' s t a r t ing ma i n ' ) p r i n t ( ' wa i t i n g for pha s e s t o comp l e t e ' ) result s awa i t a s ync i o . gather ( pha s e l ( ) , pha s e 2 ( ) , =

print ( ' r e s u l t s :

{ ! r } ' . fo rma t ( resu l t s ) )

event_loop a s yn c i o . get_event_l oop ( ) try : event -l oop . run unt i l comp l e t e ( ma i n ( ) ) f i na l l y : event_l oop . cl o s e ( ) =

-

-

Поскольку доступ к задачам, созданным с помощью функции gather ( ) , не пре­ доставляет(�Я , их нель:�я отмеиить. Возвращаемым значением функции gather ( ) является список результатов, представленных в том порядке, в каком ей переда­ вались аргументы, независимо от того, какова была фактическая очередность за­ вершения операций. $ pythonЗ a s yn c i o_gathe r . py s t a r t i ng ma i n wa i t i ng for pha s e s t o comp l e t e i n p ha s e 2 i n pha s e l

rA888 10 . П8реммwtЫ8 ВЫЧИСМtlМЯ: nроцессы, 11О1'О1СИ И conporp8ММW done wi t h pha s e 2 done wi t h pha s e l r e s u l t s : [ ' pha s e l r e s u l t ' ,

' phas e 2 r e s u l t ' ]

1 0. S . б . 3 . Обработка фоновых операций по мере их завершения

Функция as completed ( ) - это генератор, который управляет выполнением списка переданных ему программ и возвра щает резул ьrаты по одному за раз по мере заверш ения выполняющихся сопрограмм. Как и функция wai t ( ) , функция as completed ( ) нс Г'а рантирует очередность завершения программ, но ждать, пока завсршат 1 ( socket . I P PROТO_ICMP udp - > 1 7 ( socket . I P PROTO_U DP tcp -> 6 ( s ocket . I P PROTO_TCP

1) 17)

6)

Гмве

708

11. Обмен А8ННЫММ no сеrи

1 1 .2. 1 . 3 . Получение информации об адресе сервера

Функция get addr info ( ) преобразует базовый адрес сервера в список корте­ жей , содержащих всю необходимую информацию для установления соединения. Каждый кортеж может содержать различные семейства адресов или протоколы. Листинг 1 1 . 1 5. socket_getaddrinfo . py import s ocket

de f ge t_cons t a n t s ( p r e f i x ) : " " " Создат ь слов арь , сопоставляющий кон станты модуля s oc ke t с их име нами . 11 11 11

return g e t a t t r ( s o c ke t , n ) : п for п in di r ( s o c ke t ) i f n . s ta r t s wi t h ( p re f i x )

fami l i e s ge t_cons tants ( ' AF_ ' ) t ypes ge t_cons t a n t s ( ' SOCK_ ' ) protocols ge t_cons tants ( ' I P PROTO_ ' ) =

=

=

for r e s p o n s e i n s oc ke t . getadd r i n fo ( ' www . python . o rg ' , # Ра спаковка кортежа о тв ета

fami l y , s o c k t ype , p r o t o , canonname , s oc kaddr

print p r int print print print p r int

: ( ' Fami l y ( ' Туре : ( ' P rotocol : ( ' Canonical name : ( ' Socket addres s : ()

' ' ' ' '

, , , , ,

' ht tp ' ) :

response

fami l i e s [ fami l y ] ) t yp e s [ s o cktype ] ) p r o t o co l s [ pr o t o ] ) canonname ) s o c kaddr )

Этот пример демонстрирует, как получить информацию о возможных соеди­ нениях с сервером www . python . org. $ python3 s o c ke t_g e t addr i n f o . py A F I NET Fami l y Туре S OCK DGRAМ P rotocol I P PROTO UDP Canon i ca l name : Socket a ddre s s : ( ' 1 5 1 . 1 0 1 . 3 2 . 2 2 3 ' , 8 0 ) Fami l y A F I NET Туре SOCK- STREAМ I P PROTO ТСР Protocol Canon i ca l name : Socke t addre s s : ( ' 1 5 1 . 1 0 1 . 3 2 . 2 2 3 ' ,

80)

708

AF_INET б Fami l y Туре SOCK DGRAМ Protocol I P PROTO U D P C a n o n i c a l name : Socket a ddre s s : ( ' 2 а 0 4 : 4 е 4 2 : 8 : : 2 2 3 ' , 8 0 , О , О ) Fami l y A F I NET б SOCK STREAМ Туре I PPROTO Т С Р Protocol C a no n i c a l name : Socket a ddre s s : ( ' 2 а 0 4 : 4 е 4 2 : 8 : : 2 2 3 ' , 8 0 , О , 0 )

Функция ge taddrinfo ( ) получает нескол�.ко ар1уме11тов, обес11ечи8ающих фильтрацию результирующе1·0 списка. Эначения 11арамutpu t s [1 =

Соединения добавляются в списки и удаляются из них в основном цикле сервс· ра. Поскольку данная версия сервера вместо немедленной отправки ответа будет дожидаться, пока сокет не станет доступным для записи , прежде чем отправлять данные, каждое выходное соединение нуждается 11 очереди , которая будет слу· жить буфером для отправляемых посредством нее /\а1111ых. # Очереди исходящих сообщени й ( s ocke t : Queue ) me s s a ge_queues {} =

Основная часть программы выполняется в цикле, вызывая метод select ( ) для блокирования и ожидания активности сети. wh i l e i npu t s : # Ждать ,

по ка по крайней мере один из со кетов

# не будет готов к обработке

p r i n t { ' wa i t ing for the next e ve n t ' , f i l e= s ys . s t derr ) re adaЫ e , wri t aЫ e , e xcep t i onal s e l e c t . s e l ect ( i npu t s , output s , i npu t s ) =

Функция select ( ) возвращает три новых списка, содержащих подмножества содержимого переданных ей списков. В список читаемых сокетов входят сокеты,

Г/1888

738

u.

Обмен АВННЫМИ no C8nl

которые имеют буферизованпые данные, доступные для чтения. В список запи­ сываемых сокетов входят сокеты, имеющие свободное буферное пространство, в которое моrут быть записаны данные. А в список сокетов, в которых возникли исключительные ситуации, входят сокеты, в которых возникли ошибки (точный смыс.л определения "исключительная ситуация" зависит от платформы ) . Читаемые сокеты представляют три возможных случая. Если сокет является основным серверным сокt.-том (т.е. тем , который используется для п рослушива­ ния соединений ) , то условие "читаемости" означает, что он готов принять еще одно входящее соединение. Кроме добавления нового соединения в список отсле­ живаемых входов этот раздел кода устанавливает для клиентского сокета небло­ кирующий режим. # Обработать вх одные данные for s in r eadaЬ l e : i f s i s s e r ve r : # Ч и таемый сокет готов к при н я тию сое д инения connect i o n , c l i en t_add r e s s = s . accept ( ) print ( ' conne c t i o n from ' , c l i e nt_addr e s s , f i l e= s y s . s t derr ) conne ct i o n . s e t Ы o c k i n g ( O ) i nput s . appe nd ( connec t i on ) # Предос тавить соединению очередь для

# буфери за ции отправляемых данных

me s s a ge_queue s [ connect i o n ) = qu eue . Qu eue ( )

Следующий случай представляет соединение с клиентом, который отправил данные. Данные читаются с помощью метода recv ( ) , а затем помещаются в оче­ редь для отправки через сокет обратно кл иенту. else : dat a = s . re cv ( l 0 2 4 ) i f dat a : # Читаемый клиентский сокет име е т данные для # ч тения p r i nt ( ' received { ! r } f rom { } ' . fo rmat ( dat a , s . getpee rname ( ) ) , f i l e= s y s . s t de r r , me s s a ge_queues [ s ) . put ( da t a ) # Добав ить выходн ой канал для о тправ ки о т в е та i f s not in output s : output s . appe nd ( s )

Ч итаемый сокет, который не возвращает данные с помощью метода recv ( ) , соответствует клиенту, который разорвал соединение, и теперь поток готов к за­ крытию. else : # Интерпретировать пустой резуль т а т как закрытие # соединения c l o s i ng ' , c l i e nt_addr e s s , p r i nt ( '

11.4. eelect эффекlмвное ОJСМА&Ние 388е рwения ВВОАIНIЫВОАВ

739

f i l e= s y s . s t de r r ) # Пре кра тить про слуши в а ние вх одного канала дл я # данн ого соеди н ени я i f s in output s : output s . r emove ( s ) i nput s . remove ( s ) s . cl o s e ( ) # Удалить очередь сообщений de l me s s age_queue s [ s ]

Для записываемых сокетов количество различных возможных случаев мень­ ше. Если очередь содержит данные, предназначенные для отправки через соеди­ нение, то отправляется следующее сообщение. В противном случае соединение удаляется из списка выходных соединений, так что п ри прохождении цикла в сле­ дующий раз функция se lect ( ) не укажет, что данный сокет готов к отп равке данных. # Обработать выходные данные for s in wr i t aЫ e : try : nex t_ms g me s sage_queue s [ s ] . get_nowa i t ( ) e xcept queue . Empt y : # Ввиду о тсутс твия с о обще ний , ожидающих обрабо т ки , # прекрати ть пров ерку в о зможности выполнения за писи print ( ' ' , s . getpe e r name ( ) , ' queue empt y ' , f i l e= s y s . s t de r r ) output s . remove ( s ) else : print ( ' s endi ng { ! r } t o { } ' . fo rmat ( nex t_ms g , s . getpee rname ( ) ) , f i l e= s y s . s t de r r ) s . s e nd ( nex t_ms g ) =

Наконец, сокеты, входящие в исключительный список, закрываются. # Обработать " и сключи тель ные усло ви я " for s i n except i o na l : p r i nt ( ' except i o n condi t i on оп ' , s . g e t peername ( ) , f i l e= s y s . s t derr ) # Прекратить прослушив ание входного канала для # данно г о соединения i nput s . r emove ( s ) i f s in output s : output s . r emove ( s ) s . clos e { ) # Удали ть очередь сообщений d e l me s s age_queues [ s ]

В данном примере клиентская программа использует два сокета для демон­ страции того, каким образом сервер управляет одновременно несколькими сое-

гм" u. Обмен А&ННЫММ no С81М

740

динениями с помощью функции select ( ) . Клиентский код начинается с подклю· чен ия каждого сокета ТСР /IP к с е р ве ру. Листинг 1 1 .37. select_echo_mul ticl ient . ру import s o cket impor t s y s me s s a g e s = ' T h i s i s the me s s age . ' I t wi l l Ье s ent ' ' i n part s . ' , s e rve r addre s s

=

'

( ' localhost ' , 1 0 0 0 0 )

# Создать со кет TCP / I P socks = [ s o c ket . s o c ke t ( s o c ke t . AF_INET , s o c ke t . SOCK_STREAМ ) , s o c k e t . s o c k e t ( s oc ket . AF_INET , s o cket . SOCK_STREAМ ) ,

# Подключи ть сокет к порту, который про слушивается серв ером print ( ' conne c t i n g t o { } port { } ' . format ( * s erver_addre s s ) , f i l e= s y s . s t derr ) for s in s o c ks : s . connect ( s e rve r_addre s s ) Дал ее клиент отп ра вляет по од но й порции сообщения за р а з че р е з и читает доступ н ые ответы после записи новых данных.

каждый со·

кет

for me s s a g e in me s sage s : outgoi ng_dat a = me s s age . encode ( ) # Послать соо бщения в оба сокета for s i n s o c ks : p r i nt ( ' { } : s e nding { ! r } ' . fo rma t ( s . g e t s o c kname ( ) , out going_dat a ) , f i l e=sys . s t derr ) s . s end ( outgoi ng_da t a ) # П рочитать сообщения в обоих со кетах for s in socks : da t a = s . re cv ( l 0 2 4 ) p r i nt ( ' { } : received { ! r } ' . fo rma t ( s . get s o c kname ( ) , dat a ) , f i l e= s y s . s tderr ) i f not data : p r i n t ( ' c l o s i n g s o cket ' , s . g e t s o ckname ( ) , f i l e= s y s . s t derr ) s . close ( )

Запустите программы сервера и IUIИента в разных окнах. Вывод будет выгля· деть примерно так, как показано н иже, хотя номера портов могут отличаться.

741

$ python3 s e l e ct_e cho_s e r ve r . py s t a r t ing up on l oc a l h o s t po r t 1 0 0 0 0 wa i t i ng for the next event connect ion from ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) wa i t ing for t he next event connect ion from ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) wa i t ing for the next event rece ived b ' Thi s is the me s s age . ' from ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) r e c e ived b ' Thi s i s t he me s s age . ' f rom ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) wa it ing for the next event s e nd i n g b ' Th i s i s t he me s sage . ' t o ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) s e nd i n g b ' T h i s i s the me s s age . ' t o ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) wa i t i ng for the next event ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) queue empt y ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) queue empty wa i t i ng for t h e next event rece ived b ' I t wi l l Ье s e nt ' f rom ( ' 12 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) r e c e i ved b ' I t wi l l Ье s ent ' f rom ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) wa it i ng for the next event s e nd i n g b ' I t wi l l Ье s e nt ' t o ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) sending b ' I t wi l l Ье s e nt ' to ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) wa i t i ng for the next eve nt ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) queue emp t y ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) queue emp t y wa i t i ng for the next e v e n t r e c e i ve d b ' i n pa r t s . ' f r om ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) wa i t i ng for the next event r e c e i ve d b ' i n pa r t s . ' f rom ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) s e nd i n g b ' i n p a r t s . ' t o ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) wa i t i ng for the next eve nt ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) queue empt y s e nd i n g b ' i n pa r t s . ' t o ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) wa i t i ng for the next event ( ' 1 2 7 . 0 . О . 1 ' , 6 1 0 0 4 ) queue empty wait i ng for t he next event c l o s i ng ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) c l o s ing ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) wa i t ing for the next event

В окне клиента отображаются данные, отправляемые и получаемые обоими сокетами. $ python3 s e l e ct_e cho_mu l t i c l i e nt . py connect i ng t o l o c a l h o s t p o r t 1 0 0 0 0 ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) : s e n d i ng b ' Th i s i s t h e me s sage . ' ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) : s e nding b ' Th i s i s the me s s age . ' ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) : rece ived b ' Th i s is the me s s age . ' ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) : r e c e ived b ' Th i s is the me s s age . ' ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) : s e nd i n g b ' I t w i l l Ье s e nt ' ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) : s e nd i ng b ' I t wi l l Ье s e nt ' ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) : r e c e i ve d b ' I t wi l l Ье s e nt ' ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) : rece i ved b ' I t wi l l Ье s e nt ' ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) : s e nd i ng b ' i n pa r t s . '

ГА888 1J.. О6мен АllННЫММ 11О С81М

742 ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) : s e ndi ng b ' i n p a r t s . ' ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 3 ) : rece i ved b ' i n part s . ' ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 0 0 4 ) : r e c e i ved b ' i n pa r t s . '

1 1 .4.2. Неблокирующий ввод-вывод с тайм-аутами Функция select ( ) также имеет необязательный четвертый параметр t imeout (тайм-аут) , т.е. предельное время ожидания в секундах, в течение ко­ торого необходимо проверять, не появился ли доступный активный канал. Использование этого параметра позволяет основной программе вызывать функ­ цию select ( ) в качестве части более широкого цикла обработки, предпринимая другие действия в промежутках между проверками активности сети. По истечении предельного интервала ожидания функция select ( ) возвраща­ ет три пустых списка. Чтобы обновить пример серверной программы для испол1r зования тайм-аута, необходимо добавить дополнительный аргумент в вызов функ­ ции select ( ) и обработать возвращаемые ею пустые списки. -

Листинг 1 1 . 38. select_e cho server_timeou t . ру _

readaЫ e , wr i t aЫ e , excep t i onal = s e l e c t . s e l e ct ( i nput s , output s , i nput s , t imeou t ) i f not ( readaЬ l e or wr i t aЫ e or excep t i o na l ) : print ( ' t imed out , do s ome other wo rk he re ' , f i l e= s ys . s tderr ) cont i nue

Приведенная ниже "медленная" версия клиентской программы приостанавли­ вает выполнение после отправки каждого сообщения с целью имитации задерж­ ки при передаче сообщений. Листинг 1 1 .39. select echo_slow_client . ру _

impo r t socke t import sys import t ime # Со здать сокет TC P / I P sock s o c ke t . s ocket ( s o c ke t . AF_I NET , s ocket . SOCK STREAМ ) =

# Подключи ть сокет к порту , прослуши ваемому сервером s e rver_addres s ( ' l oca l ho s t ' , 1 0 0 0 0 ) print ( ' conne c t i ng to { } port { } ' . format ( * s e rve r_addres s ) , f i l e=sys . s t derr ) sock . connect ( server_addre s s ) =

t ime . s l e ep ( l ) me s s a g e s [ ' Part one o f the me s sage . ' , ' Part two o f the me s s a g e . ' , =

11.4. Иlect: аффе КТМ llНО8 CJalМAllН M8 3881PWIHIOI 8llOA81lll llOAll

amount_expect ed

743

l e n ( ' ' . j o i n ( rne s s a g e s ) )

t ry : # Отпра в и ть данные

for rnes s a g e in rne s sa ge s : da t a me s s a g e . encode ( ) pri nt ( ' s endi ng { ! r } ' . fo rma t ( da t a ) , f i l e= s y s . s tderr ) sock . s enda l l ( data ) t irne . s l eep ( l . 5 ) =

# Ожидание ответа arnount r e c e i ve d О =

whi l e arnount received < arnount-expect ed : data = s oc k . recv ( 1 6 ) amount recei ved += l e n ( da t a ) print ( ' received { ! r } ' . fo rrnat ( dat a ) , fi l e= s y s . s t derr ) f i na l l y : print ( ' c l o s i ng s o c ket ' , f i l e=sys . s td e r r ) s o c k . cl o s e ( )

Выполнение новой ве1кии сервера с медленным клиентом приводит к следую­ щим результатам. $ pytho n З s e l e ct_echo_s e rve r_t imeout . py s t a r t i n g up on l oc a l h o s t port 1 0 0 0 0 wa i t ing f or t h e next event t irned out , do s ome other work here wa i t ing for the next event conne ct ion f rom ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 1 4 4 ) wa i t ing for the next event t irned out , do s orne other wo rk here wa i t i ng for the next event received b ' P a r t one o f the rne s s a ge . ' f rom ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 1 4 4 ) wa i t i ng fo r t h e next event b ' Part one of the rne s sag e . ' t o ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 1 4 4 ) wa it ing for the next event ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 1 4 4 ) queue ernpty wa i t ing for t he next event t imed out , do s ome other wo rk here wa i t ing for the next event received b ' P a r t two o f the rne s s a ge . ' f rorn ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 1 4 4 ) wa i t ing for the next event s e nding b ' P a r t two of the rnes s age . ' t o ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 1 4 4 ) wa i t i ng for the next event ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 1 4 4 ) queue ernp t y wa i t i ng for the next eve nt t imed out , do s ome other wo rk here wa i t i ng for the next event c l o s ing ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 1 4 4 )

Г/\8118 11. Обмен А11Ннымм no сети

744 wa i t i ng f o r the next e v e n t t imed out , d o s ome o t h e r wo r k h e r e

Ниже приведен вывод на стороне клиента. $ pythonЗ s e l ect_echo_s l o w_c l i e nt . py conne c t i n g t o l o ca l hos t port 1 0 0 0 0 s e nd i n g b ' Part one o f t h e me s s ag e . ' s e nding b ' Pa r t two o f the me s s age . ' rece i ved b ' Part one o f t h e ' rece i ved b ' me s s a g e . Pa r t two ' rece i ved Ь ' of t he mes s a g e . ' c l o s i ng s o c k e t

1 1 .4.3. Использование функции poll ( ) Функция po l l ( ) предоставляет возможности, аналогичные тем , которые предлагает функция select ( ) , но с более эффективной базовой реализацией. Не­ достатком функции poll ( ) является то, что она не поддерживается на платфор­ мах Wiнdows, и поэтому переносимость использующих ее программ ограничена. Как и в предыдущих примерах, код эхо-сервера на основе функции po l l ( ) на­ чинается с конфигурирования сокета. Листинг 1 1 .40. seleot_Poll_eoho_server . ру import impo r t import impo r t

s e l ec t s o c ke t sys queue

# Создать со кет TCP/ I P socke t . socket ( s o c ket . AF INET , socket . SOCK_STREAМ ) s e rve r s e r ve r . s e t Ы o c k i n g ( O ) =

# Прив язат ь сокет к порту ( ' l oca l h o s t ' , 1 0 0 0 0 ) se r v e r_addr e s s p r i n t ( ' s t a r t ing up on { } port { } ' . fo rmat ( * s e rv e r_add r e s s ) , f i l e = s y s . s t derr ) s e rver . bind ( s e rver_addre s s ) =

# Слушать входящие соединения server . listen ( 5 ) # Очереди исходящих со общений me s s ag e_queues {} =

Длительность тайм-аута, передаваемая в качестве аргумента функции pol l ( ) задается не в секундах, а в миллисекундах. Таким образом, паузе длительностью одна секунда соответствует значение 1 000. ,

# Предотвратить бесконечное блокиров ание ( миллисекунды ) T I MEOUT 1000 =

11.4. select: эффективное ОЖМД8Ние эаверwенмя BllOA----.WllOA8

745

Pytl1011 реализует функцию po l l ( ) с классом, который управляет наблюдаемы­ ми зарегистрированными каналами данных. Каналы добавляются посредством вызова функции reg i s ter ( ) с флагами, указывающими, какие события представ­ ляют интерес для этого канала. Полный набор флагов приведен в табл. 1 1 . 1 . Таблица

1 1 .1 .

Флаги событий для функции

pol l ( )

Событиеt

Оп иса ни е

POLLIN

Имеются данные, доступные для чтения

POLLPRI

Имеются приоритетные данные, доступные для чтения

POLLOUT

Готовность к получению исходящих данных

POLLERR

Ошибка

POLLHUP

Канал закрыт

POLLNVAL

Канал не открыт

Эхо-сервер будет настраивать одни сокеты только для чтения, другие - только для записи. Подходящая комбинация флагов сохраняется в локальных перемен­ ных READ ONLY и READ WRI TE соответственно. # О быч но исполь зуе мые установки фла гов READ_ONLY ( s e l ec t . POLL IN 1 s e l ec t . POLLPRI 1 s e l e c t . POLLHU P 1 s e l ec t . POLLERR =

READ WRITE

=

READ ONLY 1 s e l ect . POLLOUT

Сокет сервера регистрируется таким образом, чтобы любые входящие соеди­ нения или данные запускали событие. # З аре гис триров ать об ъект , выполняющий опр о с pol l e r s e l ect . po l l ( ) po l l e r . r e g i s t e r ( se rve r , READ_ONLY ) =

Поскольку функция pol l ( ) возвращает список кортежей, каждый из которых содержит дескриптор файла для сокета и флаг события, необходимо сопоставить числовые дескрипторы с объектами, чтобы извлечь сокет для чтения или записи. # Сопоставить д ескрипторы ф айлов с о б ъ ектами сокетов

fd_t o_s ocke t { serve r . f i l eno ( ) : server, =

Цикл сервера вызывает функцию poll ( ) , а затем обрабатывает возвращенные события, выполняя поиск сокета и предпринимая необходимые действия в соот­ ветствии с флагом события. whi l e T ru e : # Жд а ть ,

п о ка по крайней мере о дин из со кетов не перей д ет

Гмва 11.. Обмен данными

748

no

с:еtм

# в состояние готовности к выполнению о б ра бо т ки print ( ' wa i t i ng for the next event ' , f i l e=sys . s t derr ) event s po l l e r . pol l ( T IMEOUT ) =

f o r fd,

f l a g i n event s :

# П олучи ть фактически й с оке т п о его деск р и п тору фа й ла s = fd_t o_s ocket [ fd ]

Как и в случае функции s e l e c t ( ) , если основной сокет сервера - читаемый, то в действительности это означает, что имеется входящий запрос на подключе­ ние от клиента. Новое соединение регистрируется с флагом READ ONLY для отсле­ живания поступающих через него новых данных. _

# Об р аботать в ходные дан ные i f f l a g & ( s e l e c t . POLL I N 1 s e l ect . POLLPRI ) : i f s i s s e rver : # Ч итае мый сокет го т о в принят ь со единение connect i o n , c l i e nt_a dd r e s s = s . a ccept ( ) conn e c t ion ' , cl i ent_add r e s s , print ( ' f i l e= s y s . s t d e r r ) connect ion . s e t Ы o c k i n g ( O ) f d_t o_s ocke t [ connect i on . f i l e no ( ) ] conne c t i o n pol l e r . regi s t e r ( connect i o n , READ_ONLY ) =

# П р ед остави ть соеди нению очередь для буфери за ции # о тпра вляемых дан ных

me s s age_queue s [ connect ion ]

=

queue . Queue ( )

Сокеты, отличные от серверного, - это существующие клиенты, и для доступа к данным, ожидающим чтения, используется метод recv ( ) . else : data = s . recv ( l 0 2 4 )

Если метод recv ( ) возвращает данные, то они помещаются в очередь исходя­ щих сообщений для сокета. Затем флаги для этого сокета изменяются с помощью метода modi fy ( ) таким образом, чтобы функция poll ( ) отслеживала готовность сокста к получению данных. i f data : # Ч итае мый кли е н т с ки й со кет имеет данные для чт ения print ( ' r e c e i ved { ! r } from { } ' . fo rmat ( dat a , s . getpee rname ( ) ) , f i l e= s y s . s t de r r , me s s a ge_queue s [ s ] . put ( da t a ) # Доба в и т ь выходной канал для отп р ав ки о т в е та po l l e r . modi fy ( s , READ_WRITE )

Возвращение методом recv ( ) пустой строки означает разрыв соединения с клиентом, и в этом случае объект, выполняющий опрос, информируется о том,

747

что данный сокет следует игнорировать, посредством отмены регистрации соке­ та с помощью метода unregi ster ( ) . e l se :

# Ин терпре тиро в а ть пустой резул ь т а т как # за крыти е соединени я

print ( ' c l o s i ng ' , c l i e nt_addre s s , f i l e= s y s . s tde r r ) # Прекра т и т ь прослуши в а ние вхо дных д анных # для этог о соединения po l l e r . unreg i s t e r ( s ) s . close ( ) # Удали т ь очередь соо бщений del me s s age_queue s [ s ]

Флагом POLLHUP отмечается клиент, который "отключился", не закрыв соеди­ нение корректным образом. Сервер прекращает опрос таких "исчезнувших" кли­ ентов. e l i f f l a g & s e l ect . POLLHU P : # Кли ент отключился print ( ' c l o s i ng ' , cl i ent_add r e s s , ' ( HU P ) ' , f i l e= s y s . s tde r r ) # Пр е кратить прослуши в ание в ходных данных # для это го соединения po l l e r . unreg i s t e r ( s ) s . close ( )

Д.11я записываемых сокетов код обработки аналогичен коду, который использо­ ва.11ся в примере с функцией select ( ) , за исключением того, что вместо удаления сокета из списка выходных сокетов изменяются его флаги событий в объекте, вы­ полняющем опрос, что делается с помощью метода modi fy ( ) . e l i f f l a g & s e l e c t . POLLOUT : # Со ке т гото в к отпра в ке дан ных , # если тако вые имеются try : next_ms g = me s s age_queue s [ s ] . ge t_nowa i t ( ) except queue . Empt y : # В виду отсутс тви я сообщений , ожидающих # о брабо тки , прекрати т ь про в ерку гото в ности # со кета к выполн ению записи p r i n t ( s . getpeername ( ) , ' queue emp t y ' , f i l e = s y s . s t de r r ) poll e r . modi f y ( s , READ_ONLY ) else : print ( ' s endi n g { ! r } to { } ' . forma t ( next_ms g , s . g e t p e e rname ( ) ) , f i le=s ys . s t de r r , s . send ( nex t_ms g )

748

Гмве

11. 06•н АВННЫММ по С8'1М

Наконец, любые события с флагом POLLERR приводят к закрытию сокета сер­ вером. e l i f f l a g & s e l ect . POLLERR : print ( ' exc ept i o n o n ' , s . getpe e r name ( ) , f i l e= s ys . s t de r r ) # Пре кратить прослушив ание в ходных данных дл я # этого соединения pol l e r . unre g i s t e r ( s ) s . close ( )

# Удалить очередь сообщени й del me s s age_queu e s [ s ]

В результате совместного выполнения сервера, основанного на использова­ нии опроса, и клиента se lect_e cho_mu l t i c l ient . py (клиентская программа, в которой используется несколько сокетов) выводится следующая информация. $ python3 s e le c t_pol l_echo_s e rver . py s t a r t ing up on l o c a l ho s t port 1 0 0 0 0 wa i t i ng f o r the next event wa i t i ng f o r the next event wa i t i ng f o r the next event wa i t i ng for the next event conne c t i o n ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 3 ) wa i t i ng f o r the nex t event connect i o n ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 4 ) wa i t i ng for the next event received b ' Th i s i s the me s s age . ' f r om ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 3 ) received b ' Th i s i s t h e me s s age . ' f rom ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 4 ) wa i t i ng for the next eve nt s endi ng b ' Th i s is the me s s a g e . ' to ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 3 ) s endi ng b ' Th i s i s the me s s a g e . ' t o ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 4 ) wa i t i ng f o r the next event ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 3 ) queue emp t y ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 4 ) queue emp t y wa i t i ng f o r the n e x t event r e c e i ved b ' I t wi l l Ье s e n t ' f rom ( ' 1 2 7 . О . О . 1 ' , 6 1 2 5 3 ) received b ' I t wi l l Ье s ent ' f r om ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 4 ) wa i t ing f o r the next event s e nding b ' I t wi l l Ье sent ' t o ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 3 ) s endi ng b ' I t wi l l Ье s ent ' t o ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 4 ) wa i t i ng for the next eve nt ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 3 ) queue emp t y ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 4 ) qu eue emp t y wa i t i ng f o r t h e next event received b ' i n pa r t s . ' f rom ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 3 ) received b ' i n pa r t s . ' f rom ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 4 ) wa i t i ng f o r the next event s endi ng b ' i n pa r t s . ' t o ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 3 ) s e nding b ' in pa r t s . ' t o ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 4 ) wai t i ng for the next event ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 3 ) qu eue emp t y

748 ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 4 ) queue emp t y wa i t i ng for t h e n e x t event c l o s ing ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 4 ) wa i t i ng fo r the next event c l o s i ng ( ' 1 2 7 . 0 . 0 . 1 ' , 6 1 2 5 4 ) wa i t i ng for the next event

1 1 .4.4. Опции, специфические для платформы Модуль select также предоставляет специфические для конкретных систем возможности, такие как epo l l (от англ. Edge and Triggf:r РоШпg) механизм отсле­ живания событий по перепаду и по значению, поддерживаемый в I..i11ux, а также kqueue (от англ. kernel queue) возможности доступа к очереди ядра и kevent (от англ. kernel event) возможности доступа к событиям ядра, поддерживаемые в си· стемах BSI>. Для получения более подробной информации по этому вопросу обра· щайтесь к документации операционной системы. -

-

-

Дополн и тельные ссыпки • •

Раздел документации стандартной библиотеки, посвященный модулю s e l ec t 1 3. s e l e cto rs (раздел 1 1 .3). Высокоуровневая абстракция, построенная п оверх модуля s e l ect.



• •

• •



Socket Programming НОWТО 1 4 (Gordon McMillan). Учебное руководство, включенное в документацию стандартной библиотеки. s ocket (раздел 1 1 .2). Низкоуровневое сетевое взаимодействие. S ocketS e rver. Фреймворк, предназначенный для создания сетевых серверных прило­ жений. as ynci o (раздел 1 0.5). Библиотека средств асинхронного ввода-вывода. Unix Network Programming, Volume 1: The Sockets Networking АР/, Third Edition, Ьу W. Richard Stevens, 8111 Fenner, and Andrew М. Rudoff. Addison-Wesley, 2004. ISBN-1 0: 01 3 1 41 1 551 . Foundations of Python Network Programming, Third Edition, Ьу Brandon Rhodes and John Goerzen. Apress, 201 4. ISBN-1 0: 1 430258543.

1 1 . 5 . s ocketserver: соэдание сетевых серверов Модуль socketserver это фреймворк, предназначенный для создания сете­ вых серверов. Он определяет классы, упрощающие обработку синхронн ых сете· вых запросов (обработчик запросов блокируется до полного завершения обра· ботки запроса) с использованием сокетов ТСР, UDP, а также потоков и датаграмм Unix. Кроме того, он предоставляет примесные классы, позволяющие легко пере­ страивать серверы для работы с использованием отдельного потока или процесса для каждого запроса. Ответстнеююсть за обработку запроса распределяется между классом серве­ ра и классом обработчика запросов. Сервер отвечает за осуществление взаимо· -

1 3 https : / /docs . python . org / 3 . 5 / l ibrary/ s e l e ct . html 14 htt ps : / / doc s . pyt ho n . o r g / howt o / s o c ke t s . html

l'Nlвa 1.1. Обмен АВННЫММ no C811t

750

действия (прослушивание сокета, принятие соединений и т.д.), а обработчик за­ просов - за протокол ( интерпретация входящих данных, их обработка, а также отправка данных клиенту) . Подобное разделение ответственности означает, что многие приложения могут использовать один из существующих серверных клас­ сов без каких-либо изменений и предоставлять класс обработчика запросов для работы с пользовательским протоколом.

1 1 .5. 1 . Типы серверов В модуле s o c ke t s e rve r определены пять классов серверов. Класс Ba seServer определяет API и не предназначен для инстанциализации и непосредственного использования. Класс TCPServer использует для взаимодействия сокеты ТСР /IP. Класс UDPServer использует датаграммные соксты. Классы UnixSt reamSe rve r и Uni x DatagramServer используют сокеты доменов Unix и доступны лишь на плат­ формах U нiх.

1 1 .5.2. Объекты сервера Чтобы создать сервер, следует передать конструктору адрес, по которому будут прослушиваться запросы, и класс (а не экземпляр) обработчика. Формат адреса зависит от типа сервера и используемого семейства сокетов. Для получения более подробных сведений по этому вопросу обратитесь к разделу 1 1 .2, посвященному описанию модуля socket. Для обработки запросов с помощью вновь созданного объекта сервера следует использовать один из методов handle request ( ) или s erve foreve r ( ) . Метод s e rve_fo rever ( ) вызывает метод h andle_reque s t ( ) в б ёС конечном ци кле. Однако, если приложению необходимо интегрировать сервер с другим циклом событий или использовать функцию s elect ( ) для мониторинга нескольких соке­ тов для различных серверов, оно может вызывать метод handle _reque s t ( ) непо­ средственно.

1 1 .5.З. Реализация сервера Обычно, когда создается сервер, достаточно использовать один из существую­ щих классов и предоставить пользовательский класс обработчика запросов. Для всех остальных случаев класс BaseServer включает несколько методов, которые могут быть переопределены в подклассе. •





ver i fy_reque s t ( reque s t , cl ient _addre s s ) . Возвращает значение T rue, если запрос необходимо обработать, или Fal se, если его необходимо иг­ норировать. Например, перегруженный сервер может отказаться обслужи­ вать запросы, поступающие с определенного диапазона адресов. proce s s_reque s t ( reque s t , c l i ent_addres s ) . Вызывает метод fini sh_ reque s t ( ) для фактического выполнения работы 110 обработке запроса. Данный метод также может создавать отдельные потоки или процессы, как это дела ют примесные классы. finish_ reque s t ( re ques t , cl ient _addre s s ) . Создает экземпляр обработ­ чика запросов, используя класс, переданный конструктору объекта сервера. Вызывает метод handle ( ) обработчика для обработки запросов.

11.5. soc:ketlerver: СОIА8НМ8 сетевых серверов

751

1 1 .5.4. Обработчики запросов Обработчики запросов выполняют бальшую часть работы , связанной с полу· чением входящих запросов и принятия решений относительно того, какие дей· ствия должны предприниматься. Обработчик отвечает за реализацию протокола поверх слоя сокетов (т.е. НТТР, XML-RPC или АМQР). Обработчик читает запрос из входного канала данных, обрабатывает его и записывает отправляемый ответ. Для переопределения доступны три метода. •

setup ( ) . Подготавливает обработчик к обработке запросов. В случае клас· са StreamReques tHandler создает файловые объекты для чтения и записи данных через сокет. • handle ( ) . Выполняет фактическую работу 1ю обработке запроса. Анализи­ рует входящий запрос, обрабатывает данные и отправляет ответ. • fini sh ( ) . Выполняет завершающие операции по освобождению всех ре­ сурсов, созданных в процессе выполнения метода setup ( ) . Многие обработчики могут быть реализованы только с методом handle ( ) .

1 1 . 5 . 5. Пример с эхо-сервером и эхо-клиентами Этот пример реализует простую пару "сервер - обработчик запросов", кото· рая принимает ТСР-соединения и отправляет обратно клиенту посланные им дан­ ные. Ниже приведен код обработчика запросов. Листинг 1 1 .41 . soc:ketserver_ec:ho . ру import l ogging import s ys import socke t s e rver l og g i ng . ba s i cCon f i g ( l ev e l = l ogging . DEBU G , format • ' % ( name ) s : % ( me s s a ge ) s ' , )

c l a s s EchoReque s t Handl er ( s o c ket s erve r . Bas eRequ e s t Handl e r ) : def

init ( s e l f , reque s t , c l i ent addr e s s , s e rve r ) : se l f . l ogge r = l o g g i n g . g e t Logge r ( ' EchoReque s tHandl e r ' ) s e l f . l ogge r . debug ( ' init ') s o c ke t s erve r . Bas eReque s t H andl e r . �i n i t� ( s e l f , reques t , c l i ent_addre s s , s e rv e r ) return

de f s e t up ( s e l f ) : s e l f . l ogger . debug ( ' setup ' ) ret urn s o c ke t s e rve r . Bas eReque s t Handl e r . s e t up ( s e l f ) d e f hand l e ( s e l f ) : s e l f . l ogge r . debug ( ' hand l e ' )

1"Аа8а 11. О6мен АВННЫММ no сети

752 # Э хо -сообщен и е кл и енту dat a s el f . reque s t . recv ( 1 0 2 4 ) s e l f . l ogger . debug ( ' recv ( ) - > " % s " ' , data ) s e l f . requ e s t . s e nd ( da t a ) ret u rn =

de f f i n i sh ( s e l f ) : s e l f . l ogge r . debug ( ' f i n i sh ' ) ret urn socket s e rve r . Ba s e Reque s t Handl e r . f i n i sh ( s el f )

F.динственным методом, который в действительности необходимо реализо­ вать, является EchoRequestHandl er . handle ( ) , но, чтобы проиллюстрировать последовательность вызовов, в приведенный ниже код включены версии всех ра· нее описанных методов. Класс EchoServer не делает ничего сверх того, что де­ лает класс TCPServer, за исключением протоколирования вызываемых методов. c l a s s Echo S e rver ( s o c ke t s e rve r . TCPSe rve r ) : de f

init

( s e l f , s e rver_addr es s , handl e r_c l a s s= EchoRequ e s t Ha nd l e r , ) : s e l f . l ogger = logg i n g . getLogger ( ' EchoServe r ' ) s e l f . l ogge r . debug ( ' _i n i t_ ' ) soc k e t s erve r . TCP S e rver . init ( s e l f , s e rve r_addres s , hand l e r_cl a s s ) return

d e f server_act i vat e ( s e l f ) : s e l f . logge r . debug ( ' s e rver_a c t i va t e ' ) s ocke t s e rve r . TCPServer . s e rver_a c t i va t e ( s e l f ) return def s e rve_foreve r ( s e l f , po l l_i n t e rva l = 0 . 5 ) : s e l f . logge r . debug ( ' wa i t i ng for reque s t ' ) se l f . l ogger . i n f o ( ' Handl i ng reque s t s , p r e s s < C t r l -C> to qu i t ' s o c ke t s erver . T C P S e rver . s erve_foreve r ( s e l f , pol l_i nt e rva l ) return d e f handl e_reque s t ( s e l f ) : s e l f . logge r . debug ( ' handl e_requ e s t ' ) return socke t s erve r . TCPS erver . handl e_reque s t ( s e l f ) d e f ve r i fy_requ e s t ( s e l f , reque s t , cl i e nt addre s s ) : s e l f . logge r . debu g ( ' ve ri f y_reque s t ( % s , % s ) ' , re que s t , c l i e nt_addr e s s ) return s o c ke t s e rver . TCPS e rver . ve r i fy- reque s t ( s e l f , requ e s t , c l i e n t_addre s s ,

d e f pro c e s s_reque s t ( s e l f , reque s t , c l i ent_addr e s s ) : s e l f . logge r . debug ( ' p roce s s_reque s t ( % s , % s ) ' , reque s t , c l i e n t_addre s s )

11.&. 80Ck8tletwr: COIAIUIИ8 Cln88WX серверов

753

r e t u r n s o c k e t s e rve r . TCPSe rve r . p r oc e s s - reques t ( s e l f , reque s t , c l i e n t_a ddr e s s ,

d e f s e rver_c l o s e ( s e l f ) : s e l f . l o gge r . debug ( ' s e rver_c l o s e ' ) r e t u r n s o c k e t s e r ve r . T C P S e rve r . s e rve r_c l os e ( s e l f ) d e f f i n i s h_r eque s t ( s e l f , r e qu e s t , c l i ent_add r e s s ) : s e l f . l ogg e r . debug ( ' f i n i s h_requ e s t ( % s , % s ) ' , re que s t , c l i e nt addres s ) r e t u r n s o c ke t s e rve r . T C P S e rve r . f i nls h_reque s t ( s e l f , reque s t , c l i e nt_add r e s s ,

d e f c l o s e r e qu e s t ( s e l f , re qu e s t addres s ) : s e l f . l ogge r . debu g ( ' c l o s e_re qu e s t ( % s ) ' , reques t_addr e s s ) r e t urn s o c ke t s e rve r . TC PS e rve r . c l o s e_re que s t ( s e l f , r eques t_add r es s ,

d e f s hu t down ( s e l f ) : s e l f . l ogger . debug ( ' shutdown ( ) ' ) r e turn s o c k e t s e rve r . T C P S e rve r . s hu tdown ( s e l f )

Все, что теперь остается сделать, - это добавить основную программу, которая нас1·раивает сервер для работы в потоке и отправляет ему данные, чтобы проил­ люстрировать, какие методы вызываются при обратной отправке данных клиенту. if

name ' ma i n impo r t s ocket impo r t t h reading ==

'

·

addr e s s ( ' localho s t ' , 0 ) # по з в олит ь ядру назначить порт s e rver Echo S e rve r ( addre s s , EchoReques tHand l e r ) ip , p o r t s e rve r . s e rve r_addres s # Ка кой порт был назначен ? =

=

=

# Запустить сервер в nотоке t = thr eading . Th r ead ( t a r g e t = s e rve r . s e rve_fo reve r ) t . s e t Daemo n ( T rue ) # рабо т а ть в фоновом режиме t . start ( )

l og g e r = l o g g i ng . getLogger ( ' c l i ent ' ) l o gge r . i n fo ( ' S e rve r o n % s : % s ' , ip , po r t ) # Подключи ть ся к серв еру l o g ge r . debug ( ' c r e a t i n g s ocket ' ) s s o c k e t . s ocket ( s o c ke t . AF_I NET , s o c k e t . SOCK_ST REAМ ) l o gge r . debug ( ' conne c t i ng t o s e rv e r ' ) s . conne c t ( ( ip , por t ) ) =

# Отправить да нные me s s a ge = ' He l l o , wo r l d ' . encode ( )

754

Гма u. Обмен АВННЫММ no С81И l o g ge r . debug ( ' s endi ng d a t a : % r ' , me s s age ) len s e nt s . s e nd ( me s s a ge ) =

# Получить ответ

l ogge r . debug ( ' wa i t i ng f o r re spons e ' ) response = s . recv ( l en_s e n t ) l o g ge r . debug ( ' re spons e f r om s e r ve r : % r ' , r e spons e ) # Очис тит ь ре сурсы s e rve r . shutdown ( ) l ogg e r . debug ( ' c l o s i ng s o c ket ' ) s . close ( ) l ogg e r . debug ( ' done ' ) s e rver . s o c k e t . c l o s e ( )

Выполнение п рограммы приводит к сл едующим результатам. $ pythonЗ s o c k e t s e rve r_e cho . py i ni t EchoS e r ve r : EchoS erve r : s e rve r a c t i va t e EchoS erve r : wa i t i ng f o r re qu e s t EchoS e rve r : Hand l i ng reque s t s , pr e s s to qu i t c l i ent : S e rver o n 1 2 7 . 0 . 0 . 1 : 5 5 4 8 4 c l i ent : c r e a t i ng s o c ke t c l i e n t : conne c t i ng to s e r v e r c l i en t : s e ndi ng data : b ' He l l o , wor l d ' EchoS erve r : v e r i fy_reque s t ( < s o c ke t . s o c k e t fd= 7 , fami l y=Add re s s Fami l y . AF_I NET , t ype=S o c ke t Ki nd . S OCK_STREAМ, p r o t o=O , l a ddr= ( ' l 2 7 . 0 . 0 . l ' , 5 5 4 8 4 ) , raddr= ( ' l 2 7 . 0 . 0 . l ' , 5 5 4 8 5 ) > , ( ' 1 2 7 . 0 . 0 . 1 ' , 5 5 4 8 5 ) ) E choS e rve r : p r o c e s s_re que s t ( < s o c k e t . s o c ket fd=7 , fami l y=Addre s s Fami l y . AF_I NET , t ype=SocketKind . SOCK_STREAМ , p r o t o= O , l addr= ( ' l 2 7 . 0 . 0 . l ' , 5 5 4 8 4 ) , raddr= ( ' l 2 7 . 0 . 0 . l ' , 5 5 4 8 5 ) > , ( ' 1 2 7 . 0 . 0 . 1 ' , 5 5 4 8 5 ) ) EchoSe rve r : f i ni s h_reque s t ( < s o c ke t . s o c k e t fd=7 , fami l y=Add re s s Fami l y . AF_INET , t ype= S o c ke t Ki nd . S OCK_S TREAМ , p r o t o= O , l a ddr= ( ' l 2 7 . 0 . 0 . l ' , 5 5 4 8 4 ) , raddr= ( ' l 2 7 . 0 . 0 . l ' , 5 5 4 8 5 ) > , ( ' 1 2 7 . 0 . 0 . 1 ' , 5 5 4 8 5 ) ) init EchoRequ e s tHand l e r : E choRe que s tHandl e r : s e t up EchoReques tHand l e r : handl e c l i ent : wa i t i ng for r e spon s e EchoRequ e s t Hand l e r : r e cv ( ) - > " b ' He l l o , wo r l d ' " EchoReque s tHand l e r : f i ni s h c l i ent : re sponse f rom s e rve r : b ' He l l o , wor l d ' EchoServe r : shutdown ( ) EchoServe r : c l o s e_re qu e s t ( < s o c ke t . s o c ke t fd= 7 , fami l y=Addr e s s Fami l y . AF_I NET , t ype= S o c ke tKi nd . S OCK_STREAМ , p r o t o= O , l addr= ( ' l 2 7 . 0 . 0 . l ' , 5 5 4 8 4 ) , raddr= ( ' l 2 7 . 0 . 0 . l ' , 5 5 4 8 5 ) > )

755 c l i e n t : c l o s i ng s o c ke t c l i ent : done

П ри мечание

И спользуемый номер порта будет меняться от одно го запус ка программы к другому, п о­ скол ьку ядро автоматичес к и выделяет ДОС'fVГIНЫ й порт. Ч тобы сервер слушал каждый раз один и тот же порт, укажите его номер в кортеже адреса вместо о .

Ниже п редставлена компактная верс и я того же сервера без отладочных вы­ зовов. В класс е обработчи ка за просов необходимо предоставить лишь метод handle ( ) . Листинг 1 1 .42. sooketserver_eoho_siшple . ру imp o r t s o c k e t s e rver

class EchoReque s t Ha nd l e r ( s o c ke t s e rve r . BaseReques tHandl e r ) : de f handle ( s e l f ) : # Эхо- сообщение клиенту da t a s e l f . r e qu e s t . recv ( l 0 2 4 ) з e l f . reque s t . s end ( da t a ) return =

i f _name_ ma i n impo r t s o c ke t impo r t threadi ng ==

'

' :

addr e s s = ( ' l o c a l h o s t ' , 0 ) # позволить ядру на значит ь порт s e rv e r s o c ke t se rve r . TCPServe r ( addre s s , EchoRequ e s tHa ndl e r ) i p , po r t s e r ve r . s e rve r_add r e s s # Ка кой порт на значен ? =

=

t t h r eading . Th r e ad ( t a r g e t = s e rver . s e rve_foreve r ) t . s e t Da emo n ( T rue ) # рабо т а т ь в фон овом режиме t . start ( ) =

# Подключить с я к серв еру s s o c ke t . s o c k e t ( s o cket . AF_INET , s oc k e t . SOCK_STREAМ ) s . connect ( ( i p , po r t ) ) =

# Отправить данные me s s a ge = ' He l l o , wo r l d ' . e ncode ( ) p r i n t ( ' S ending : { ! r } ' . fo rma t ( me s s age ) ) l e n s ent s . s e nd ( me s sage ) =

# Получи ть о т в е т response s . recv ( l en_se nt ) pr int ( ' Rec e i ved : { ! r } ' . fo rma t ( r espons e ) ) =

# Очи стить ре сурсы

758

Гмва � Обмен АВНнwми no сети s e rve r . shutdown ( ) s . close ( ) s e rve r . s o c ke t . c l o s e ( )

В данном случае никакой специальный класс сервера не требуется, поскольку TCPServer удовлетворяет всем требованиям, предъявляемым к серверу. $ pythonЗ s o c k e t s e rver_echo_s imp l e . py Sending : b ' He l l o , wo r l d ' Re c e i ved : b ' He l l o , wo r l d '

1 1 . 5.б. Создание потоков и порождение процессов Чтобы добавить в сервер поддержку потоков и процессов, следует вю1ючить в иерархию классов для сервера примесный ю�асс. Примесные классы переопреде­ ляют метод proce s s request ( ) для запуска нового потока или процесса и выпол­ нения в нем всей работы, когда запрос готов к обработке. Лля потоков следует использовать ю1асс ThreadingMi xin. _

Листинг 1 1 .43. sooke tserver_threaded . ру import t h r eadi ng impo r t s o c k e t s e rver c l a s s ThreadedEchoRequ e s t Handl e r ( s o c ke t s e rve r . BaseReque s tHandl e r , ) : d e f handle ( s e l f ) : # Эх о-сообщение клиен ту data s e l f . reque s t . recv ( l 0 2 4 ) cu r_thread threading . c u r r e n tThread ( ) r e sponse b ' % s : % s ' % ( cu r_thread . g e tName ( ) . enc ode ( ) , data ) s e l f . r eque s t . send ( re s p on s e ) return =

=

=

c l a s s Thr eadedEchoS e r ve r ( s o c k e t s e r ve r . Threadi ngMi x ! n , soc k e t s e rve r . TCPSe rve r , ) : pa s s if

name ' ma i n impo r t s o c ke t =-

addr e s s s e rve r

'

·

( ' l o c a l ho s t ' , 0 ) # позволит ь ядру назначи т ь порт Thre adedEcho S e rve r ( addr e s s , ThreadedE choRequ e s tHandl e r ) i p , port s e rve r . s e rve r add r e s s # Ка кой п о р т назна чен ? =

=

11.5. IOCketlener: саwнме С8'1811ЫХ серверов

757

t threading . Thread ( t arg e t = s e rve r . s e rve_f orever ) t . s e t Da emon ( T rue ) # работать в фон о в ом режиме t . start ( ) p r i n t ( ' S e rve r l oop running i n thread : ' , t . ge tName ( ) ) =

# ПодIarse_urlparseattrs . ру f rom u r l l i b . pa r s e impo r t urlpa r s e url ' ht tp : / / u s e r : pwd@NetLoc : B O /path ; pa r am? que ry=a rg # f rag ' pa r s e d = u r l p a r s e ( url ) p r i n t ( ' s cheme : ' , pars e d . s cheme ) p r i n t ( ' ne t l o c : ' , pa r s e d . ne t l o c ) : ' , parsed . pa t h ) p r i n t ( ' pa t h pri nt ( ' pa rams : ' , pa r s e d . pa rams ) print ( ' que r y : ' , pars e d . que ry ) print ( ' fragment : ' , pa r s e d . f ra gme nt ) p r i nt ( ' u s e rname : ' , pa r s e d . u s e rname ) p r i n t ( ' p a s sword : ' , parsed . pa s sword ) p r i n t ( ' h o s t name : ' , pa r s e d . ho s t name ) p r i n t ( ' po r t : ' , pa r s e d . port ) =

12.1. urtllЬ.p8118 : раабиение URL-e№ICO• Н8 O'Qt,8/\WtW 8 М8М8Н1111

783

Значения username и password доступны тогда, когда они есть во входном URL-aдpece, и устанав.ливаются в значение None в противном случае. Значение hostname совпадает со значением специ ф икатора местоположения в сети netloc с переводом всех букв в нижний регистр и без номера порта. Номер порта port п реобразуется в целое число, если он указа11, или принимает значение None в про­ тив1юм случае. $ pythonЗ u r l l i b_pa r s e_u rlpa r s e a t t r s . py s cheme net l oc path pa rams que r y f r a gmen t : u s e r name : p a s s wo r d : h o s t name : po r t

http u s e r : pwd@NetLoc : B O /path param que ry=a rg f rag user pwd net l o c во

Функция u r l sp l i t ( ) аналогична функции urlparse ( ) , но ведет себя несколь­ ко иначе, поскольку не выбирает параметры из U RL. Это полезно в случае URL­ aдpecoв, соответствующих требования м документа RFC 2396 1 , который поддер­ живает параметры для каждого сегмента пути. Листинг 1 2.3. url lib_Parse_url spl i t . py f rom u r l l i b . pa rs e impo r t u r l spl i t url ' ht tp : / /u s e r : pwd@NetLoc : B O / p l ; pa ra /p 2 ; para ? que ry=a rg # f ra g ' pa r s e d urlspl i t (url ) p r i n t ( pa r s e d ) p r i n t ( ' s cheme : ' , pa r s e d . s cheme ) print ( ' netloc : ' , p a rs e d . n e t l o c ) p r i n t ( ' pa t h : ' , p a rs ed . pa t h ) : ' , pa r s e d . que ry ) p r i n t ( ' qu e r y p r i nt ( ' f r a gme n t : ' , pa r s e d . f ragment ) p r i n t ( ' u s e rname : ' , pa r s e d . us e r name ) p r i nt ( ' pa s s wo rd : ' , pa r s ed . pa s s wor d ) p r i n t ( ' h o s t name : ' , pa r s e d . hos t name ) p r i n t ( ' po r t : ' , p a r s e d . po r t ) �

=

Поскольку параметры не выбираются, то атрибут params в этом случае отсут­ ствует, и API кортежа отображает пять элементов вместо шести. $ pythonЗ u r l l i b_pa r s e_u r l spl i t . py S p l i tResul t ( s cheme= ' ht tp ' , net l oc= ' u s e r : pwd@Ne t Loc : B O ' , path= ' /p l ; pa r a /p2 ; para ' , que ry= ' qu e ry=a rg ' , fragment= ' f rag ' ) http s cheme n e t l oc : u s e r : pwd@Ne t Loc : B O 1

h t t ps : / / t o o l s . i e t f . o r g / h tml / r f c 2 3 9 6 . html

ГА&ва 12. Интернет

764 path que ry f r a gment : u s e r name : pas s word : h o s t name : port

/pl ; pa ra /p2 ; para que ry=arg frag user pwd netloc во

Чтобы упростить вычленение идентификатора фрагмента из URL-aдpeca, на­ пример для получения базового имени страницы, можно использовать функцию ur ldefrag ( ) . Листинг 1 2.4. urllib_yarse_urldefrag . py f rom u r l l i b . pars e import u r l de frag o r i g i na l = ' ht tp : / / net l o c /pa t h ; param? que ry=a r g # f rag ' p r i n t ( ' o r i g i na l : ' , o ri g i nal ) d = u r l de f rag ( o r i g i na l ) : ' , d . ur l ) pr i nt ( ' ur l p r i n t ( ' fragment : ' , d . fragment )

Возвращаемым значением является объект DefragResult на основе именован­ ного кортежа, содержащий базовый URL-aдpec и фрагмент. $ pythonЗ u r l l i b_pa r s e_u rlde f r a g . py o r i g i na l : h t tp : / / n e t l o c /pat h ; param?query=a r g # f rag h t tp : / / n e t l o c /pat h ; param? que ry=a rg url f r a gment : f rag

1 2. 1 .2. Конструирование строки URL-aдpeca из элементов Сборку отдельных компоенентов UR-aдpeca в единую строку можно осуще­ ствить несколькими способами. Объект Pa rseResul t имеет метод getur 1 ( ) . Листинг 1 2. 5 . urllib_parse_geturl . ру f r om u r l l ib . pa r s e imp o r t u r l pa r s e o r i g i na l ' ht tp : / / n e t l o c / pa t h ; pa ram? query=arg # f rag ' p r i n t ( ' OR I G : ' , o r i g i na l ) parsed u r l pa r s e ( or i g i na l ) p r i nt ( ' PARS E D : ' , p a r s e d . geturl ( ) ) =

=

Метод getur 1 ( ) работает лишь для объекта, возвращаемого функцией urlparse ( ) или u r l sp lit ( ) . $ pythonЗ u r l l i b_parse_g e t u r l . py OR I G h t t p : / / n e t l o c /pa t h ; param?qu e r y=a r g # f rag PARS E D : h t t p : / /net l oc / p a t h ; param?que ry=a r g # frag

12.1. url11Ь.P8f18: рва6иенме URL4Al)8COll на ОIМАЫIЫ8 81\8М8Н1W

Для конструирования URI.-aдpeca на основе обычного кортежа, содержащего строки, следует использовать функцию ur lunpar s e ( ) . Лисrинr 1 2 .6. urll ibyarse_urlunparse . py from u r l l i b . pa r s e import urlparse , u r l u nparse o r i g i na l = ' ht tp : / / n e t l o c /pa t h ; param?query=arg # f rag ' p r i n t ( ' ORIG : ' , o r i g i na l ) p a r s e d = u r l pa r s e ( o r i g i na l ) p r i nt ( ' PARSE D : ' , t ype ( pa r s ed ) , pa r s e d ) t p a r s ed [ : ] pr i n t ( ' TUPLE : ' , t ype ( t ) , t ) p r i n t ( ' NEW : ' , u r l u npars e ( t ) ) =

В то время как в качестве кортежа, представляющего элементы URL-aдpeca, может быть использован объект Pa r s eRe s u l t , возвращаемый фун кцией ur lparse ( ) , в данном примере, для того чтобы продемонстрировать, что функ­ ция urlunpa r se ( ) работает также с обычными кортежами, новый кортеж созда­ ется явным образом. $ pyt honЗ u r l l ib_pars e_ur l unpa r s e . py ORI G : h t tp : / / ne t l o c /pa t h ; param? que ry=arg# frag PARS E D : < c l a s s ' u r l l i b . pa rs e . P a r s e Re s u l t ' > Pars eResu l t ( s cheme= ' ht tp ' , n e t l oc= ' ne t l oc ' , path= ' /p a th ' , pa rams= ' pa r am ' , que r y= ' que ry=arg ' , fragment= ' fra g ' ) TU PLE : < c l a s s ' t up l e ' > ( ' http ' , ' ne t l oc ' , ' /path ' , ' param ' , ' qu e r y= a r g ' , ' frag ' ) NEW : h t tp : / /net l o c /pa t h ; pa ram? query= a r g # frag

Если входной URL-aдpec включает избыточные компоненты , то они могут быть исключены из реконструированной строки URL. Лиcrинr 1 2 .7. urll ibyarse_urlunparseextra . py from u rl l i b . pa r s e import urlpars e , u r l u np a r s e ori g i na l ' ht tp : / / ne t l oc / pa th ; ? # ' p r i nt ( ' ORIG : ' , o r i g i na l ) pa r s e d = u r lparse ( o r i g i na l ) p r i n t ( ' PARSE D : ' , t ype ( p a r s ed ) , parsed ) t = parsed [ : ] p r i n t ( ' TUPLE : ' , type ( t ) , t ) pr i nt ( ' NEW : ' , u r l u npa r s e ( t ) ) =

В данном случае параметры , строка запроса и фрагмент отсутствуют во вход­ ном URL-aдpece. Новая строка URI. выглядит иначе, чем исходная, однако экви­ валентна ей согласно стандарту. $ pythonЗ u r l l ib_pa r s e_u r l u npars eextra . py ORI G : h t tp : / / net l oc /path ; ? # PARS E D : < c l a s s ' u r l l i b . pa r s e . ParseResul t ' > P a r s e Re s u l t ( s c heme= ' http ' , ne t l oc= ' ne t l oc ' , pa t h= ' /path ' ,

Г/\8811 12. Инrернеr

786 pa rams= ' ' , que ry= ' ' , fragment= ' ' ) TUPLE < c l a s s ' tup l e ' > ( ' ht tp ' , ' ne t l oc ' , NEW : h t tp : / / ne t l o c /path

' /p a th ' ,

' '

1 1

1 1

)

1 2. 1 .3. Объединение элементов В дополнение к разбору URL-aдpecoв на элементы модуль u r l ib . parse позво­ ляет конструировать абсолютные URI,-aдpeca путем объединения базового и от­ носительного адресов с помощью функции u r l j o in ( ) . Листинг 1 2.8. urll ib_yarse_url j oin . py f rom u r l l ib . pa r s e impo r t u rl j o i n p r i n t ( u r l j o i n ( ' ht tp : / /www . examp l e . com/path / f i l e . html ' , ' a notherf i l e . html ' ) ) p r i n t ( u r l j o i n ( ' ht tp : / /www . ex ampl e . com/pa th / f i l e . html ' , ' . . / a nother f i l e . html ' ) )

В этом примере при вычислении второго URL-aдpeca учитывается относи­ тельная часть пути ( " . . / " ) . $ pyt honЗ u r l l ib_p a r s e_u r l j o i n . py h t tp : / / www . exampl e . com/pa th/anothe r f i l e . html h t tp : / /www . ex ampl e . com/ anothe r f i l e . html

Части пути, не являющиеся относительными, обрабатываются так же, как и с помощью функции o s . ра th . j oin ( ) . Листинг 1 2.9. urllib_J>arse_url j oin_wi th_J>ath . py from u r l l ib . parse imp o r t u r l j o i n p r i nt ( u r l j o i n ( ' ht tp : / /www . examp l e . com/pa t h / ' , ' / subpa th / f i l e . html ' ) ) p r i nt ( u r l j o i n ( ' ht tp : / /www . examp l e . com/path / ' , ' subpa th / f i l e . html ' ) )

Если присоединяемый к URL-aдpecy путь является каталогом (начинае1·ся с символа косой черты " / " ) , то функция u r l j oin ( ) удаляет часть пути URL до верх­ него уровня. В противном случае новая часть пути присоединяется к концу суще­ ствующей.

$

pythonЗ u r l l ib_pa r s e_ur l j o i n_wi t h_pa th . py

h t tp : / /www . exampl e . com/ s ubpat h / f i l e . html h t tp : / /www . exampl e . com/pa t h / s ubpat h / f i l e . html

1 2. 1 .4. Кодирование параметров запроса Прежде чем параметры запроса можно будет добавить в строку URL, их необ­ ходимо преобразовать соответствующим образом.

12.2.. urlHb.parse: рабиенме URL-eдpecoв на Оl'АеАЫtые эмменn.t

787

Листинг 1 2. 1 0. urll ib_parse_urlenc:ode . py f rom u r l l i b . p a r s e import u r l encode que r y_a rgs = { ' q ' : ' que r y s t r i ng ' , ' foo ' : ' ba r ' , encoded_a rgs = u r l encode ( qu ery_args ) p r i n t ( ' Encoded : ' , encoded_args )

Процесс преобразования включает замену таких специальных символов, как пробелы, что гарантирует их корректную передачу на сервер с исполыованием формата, удовлетворяющего требованиям стандарта. $ pythonЗ ur l l ib_p a r s e_u r l e ncode . py Encode d : q=que ry+ s t r i n g & foo=bar

Если какое-либо значение в счюке запроса является последовательностью, то функции urlencode ( ) необходимо дополнительно передать флаг doseq, имею­ щий значение True. Листинг 1 2. 1 1 . urll ib_parse_urlenc:ode_doseq . py f rom u r l l i b . p a r s e impor t u r l encode que ry_a rgs = { ' fo o ' : [ ' foo l ' ,

' foo 2 ' ] ,

print ( ' S ingle : ' , u r l e ncode ( query_a rg s ) ) p r i n t ( ' S eque n ce : ' , u r l e ncode ( qu e r y_a rg s , d o s e q=T rue ) )

Результат представляет собой строку запроса, в которой с одним именем ассо­ циировано несколько значений. $ pythonЗ u r l l ib_pa r s e_u r l e ncode_do s e q . py S i ng l e foo• % 5B % 2 7 f o o l % 2 7 % 2C+% 2 7 foo2 % 2 7 % 5 D S e quence : foo•foo l & foo=foo2

Для декодирования строки запроса следует использовать функцию parse _ qs ( ) или parse_qs l ( ) . Листинг 1 2. 1 2. urll ib_parse_parse_qs . py f r om u r l l i b . parse import p a r se_qs , pa r s e_qs l encoded = ' f oo=foo l & foo= f o o 2 ' p r i nt ( ' par s e_qs : ' , parse_qs ( encoded ) ) p r i n t ( ' pa r s e_qs l : ' , p a r s e_qs l ( encoded ) )

ГА8ва 12. Интернет

788

Функция par se_qs ( ) возвращает словарь, сопоставляющий имена со значени­ ями, тогда как функция parse_q s l ( ) возвращает список кортежей, каждый из ко­ торых содержит имя и значение.

$

pythonЗ u r l l ib_p a r s e_pa r s e_qs . py

pars e_qs : pa r s e_qs l :

{ ' foo ' : [ ' f o o l ' , ' fo o2 ' ] ) [ ( ' foo ' , ' f oo l ' ) , ( ' f oo ' ,

' foo2 ' ) ]

Входящие в строку запроса специальные символы, при анализе которых на стороне сервера могуг возникнуть проблемы, должны экранироваться при пере­ даче их функции url encode ( ) . Безопасные локальные версии строк можно полу­ чить непосредственно, используя функции quote ( ) или quote_plus ( ) . Листинг 1 2 . 1 3 . url lib_parse_quo te . py f rom u r l lib . p a r s e impo r t quote , quo t e_plus , u r l e ncode url = p r i nt print p r i nt

' ht tp : / / lo c a l h o s t : B O B O / -hel lma n n / ' ( ' u r l e n code ( ) : ' , u r l e ncode ( { ' ur l ' : u r l ) ) ) ( ' quo t e ( ) : ' , quote ( u r l ) ) ( ' quo t e_plus ( ) : ' , quo t e_p l u s ( u r l ) )

Реализация экранирования в функции quote_plus ( ) отличается большей строгостью в отношении замены символов.

$

pythonЗ u r l l ib_p a r s e_quo t e . py

u r l encode ( ) : url=http% 3A% 2 F % 2 F l o ca l ho s t % 3AB 0 8 0 % 2 F% 7 Eh e l lma nn % 2 F h t t p % 3A/ / l ocalhos t % 3AB O B 0 / % 7 Eh e l lmann/ quot e ( ) quote_plus ( ) : http% 3A% 2 F% 2 Floca l ho s t % 3AB O B 0 % 2 F % 7 Eh e l lma nn % 2 F

Чтобы обратить операцию экранирования символов, следует использовать функцию unquote ( ) или unquo te_p l u s ( ) соответственно. Листинг 1 2. 1 4. url lib_Parse_unquote . py f r om u r l l i b . p a r s e impo r t unquot e , unqu o t e_plus p r i nt ( unquo t e ( ' h t t p % 3A/ / l ocalho s t % 3AB O B 0 / % 7 Ehe l lmann/ ' ) ) p r i n t ( u nquote_p l u s ( ' ht t p % 3A% 2 F% 2 Fl o calhos t % 3AB 0 8 0 % 2 F% 7 Eh e l lma nn % 2 F ' ) )

Кодированное значение преобразуется обратно в обычную строку URI

$

pythonЗ u r l l ib_p a r s e_unquote . py

h t tp : / / l o c a l h o s t : B O B O / - h e l lmann/ h t tp : / / l o c a l ho s t : B O B O / - h e l lmann/

•.

12.2. urlllb.request: АОСТУП к С81НWМ ресурс11М

789

Дополнительные ссылки •

Раздел документации стандартной библиотеки, посвященный модулю u r l l ib . pa r s e2 • u r l l ib . requ e s t (раздел 1 2.2). Получение содержимого ресурса, идентифицированного с помощью адреса URL.



RFC 1 7383 • Uniform Resource Locotor (URL) syntox.



RFC 1 8084• Relotive URLs.



RFC 23965 • Uniform Resource ldentifier (URI) generic syntox.



RFC 39866 • Uniform Resource ldentifier (URI) syntox.



1 2 . 2 . url l ib . reque s t: доступ к сетевым ресурсам Модуль urllib . request предостамяет API для использования интернет-ре­ сурсов, идентифицируемых своими URL-адресами . Он спроектирован таким об­ разом , чтобы отдельные приложения могли расширять его для поддержки новых протоколов или внесения изменений в существующие протоколы (например, для поддержки базовой IПТР-ауте11тификаци и ) .

1 2.2. 1 . Метод НПР GET П римечание

Код тестового сервера дл я последующих примеров содержится в файле http s e rver GET . py, находящемся в папке примеров для модуля h t tp . s e rve r (раздел 1 2. Sf Серве р следует запустить в одном окне, а выполнять примеры - в другом. Использование модуля ur l l ib . reque s t проще всего продемонстрировать на примере операции Н1ТР GET. Чтобы получить файловый дескриптор удаленных данных, следует передать URl,-aдpec методу ur lopen ( ) . Л истинг 1 2. 1 5. url l ib_reques t_urlopen . py f r om u r l l ib imp o r t reque s t r e sponse = reque s t . urlop e n ( ' ht t p : / / l o c a l ho s t : 8 0 8 0 / ' ) p r i nt ( ' RES PONSE : ' , respon s e ) : ' , response . ge t u r l ( ) ) p r i nt ( ' URL heade r s response . i n fo ( ) p r i nt ( ' DATE : ' , heade rs [ ' date ' ] ) p r i nt ( ' HEADERS : ' ) p r i nt ( ' ' ) p r i nt ( he a de r s ) =

---------

da t a

=

r e sponse . read ( ) . de code ( ' u t f- 8 ' )

2

h t t p s : / / do cs . python . o rg / 3 . 5 / l ibrary /u r l l i b . pa r s e . html

3

5

h t t p s : / / t oo l s . i e t f . or g /html / r f c l 7 3 8 . html h t t p s : / / t o o l s . i e t f . org /html / r f c l 8 0 8 . html h t t p s : / / too l s . i e t f . org/html / r f c2 3 9 6 . html

6

h t t p s : / / t oo l s . i e t f . or g / html / r f c 3 9 8 6 . html

4

Гмвв 12. Интернет

770 p r i nt p r i nt print p r i nt

( ' LENGTH : ' , l e n ( da t a ) ) ( ' DATA : ' ) ( ' --------- ' ) ( da t a )

Сервер получает входящие значения и форматирует ответ в виде просто­ го текста, отправляемого обратно клиенту. Значение, возвращаемое функцией urlopen ( ) , предоставляет доступ к :iаголовкам ответа НТТР-сервера через метод i n fo ( ) , а доступ к данным для удаленного ресурса обеспечивают, например, та­ кие методы , как read ( ) и readl ines ( ) .

$

pyt h o n 3 u r l l i b_requ e s t_u r l open . py

RES PONSE : ht tp : / / l o c a l h o s t : 8 0 8 0 / URL DATE Sat , 0 8 Oct 2 0 1 6 1 8 : 0 8 : 5 4 GМТ HEADERS S e rver : BaseHTT P / 0 . 6 Pytho n / 3 . 5 . 2 Da t e : S a t , 0 8 Oct 2 0 1 6 1 8 : 0 8 : 5 4 GМТ Content-Type : t ex t /p l a i n ; cha r s e t =u t f - 8 LENGTH DATA

34 9

CLI ENT VALUE S : c l i e nt_add r e s s = ( ' 1 2 7 . О . 0 . 1 ' , command=GET pa t h= / r e a l path= / que ry= reque s t_v e r s i on=HTT P / 1 . 1

58 4 2 0 )

( 127 . О . 0 . 1 )

SERVER VALUE S : s e rv e r_ve r s i o n=Ba seHTT P / 0 . 6 s y s_v e r s i on=Pytho n / 3 . 5 . 2 protoco l_ve rs i o n=HTT P / 1 . 0 HEADERS RECE IVED : Accept -E ncodi ng=i dent i t y Connect i on=c l o s e Ho s t = l o c a l h o s t : 8 0 8 0 U s e r-Agent= Python-u r l l ib / 3 . 5

Функция urlopen ( ) возвращает итерируемый объект, подобный файлу. Листинг 1 2. 1 6. urllib_reque s t_urlopen_i terator . py from u r l l ib irnpo rt requ e s t r e sponse reque s t . u r l ope n ( ' ht tp : / / l o ca l hos t : 8 0 8 0 / ' ) f o r l i ne i n r e spons e : p r i n t ( l ine . decode ( ' ut f- 8 ' ) . r s t r i p ( ) ) =

12.2. Urlllb.1'8qU818: AOCIJll К C8188WM ресурсам

771

В этом примере перед выводом информации на печать из 11ее удаляются сим­ волы новой строки и перевода каретки. $ python3 url l ib_reque s t_ur l open_i t e r a t or . p y C L I ENT VALUES : c l i ent_addres s= ( ' l 2 7 . 0 . 0 . l ' , 5 8 4 4 4 ) command=GET path=/ r e a l path=/ que ry= reques t_ve r s i on=HTT P / 1 . 1

( 12 7 . 0 . 0 . 1 )

SERVER VALUES : s e rv e r_ve r s i on=Ba s e HTT P/ 0 . 6 s y s_vers i on= Python/ 3 . 5 . 2 protocol_ve r s i o n=HTT P / 1 . 0 HEADERS RECE IVE D : Accept - Encodi ng=ident i t y Conne ct i o n=cl o s e Hos t = l ocalhos t : 8 0 8 0 U s e r -Agent= Python-ur l l iЬ / 3 . 5

1 2.2.2. Кодирование параметров запроса Параметры запроса могут передаваться серверу посредством кодирования с 1юмощ1.ю функции ur l l ib . pars e . ur lencode ( ) и присоединения к URL-aдpecy. Листинг 1 2. 1 7. urllib_reques t_http_ge t_args . ру from url l ib import pa r s e from url l ib import reque s t que r y_args ( ' q ' : ' query s t r i ng ' , ' foo ' : ' ba r ' } encoded_args = pars e . ur l e ncode ( query_a rgs ) pr i n t ( ' E ncoded : ' , e n coded_args ) =

url ' ht tp : / / l ocalhost : 8 0 8 0 / ? ' + e ncoded_a rgs pri nt ( reque s t . u r l open ( url ) . read ( ) . de code ( ' ut f - 8 ' ) ) =

Список значе11ий, отображаемых в выводе этого примера, содержит закодиро­ ванные параметры запроса. $ python url l i b_reques t_http_ge t_args . py Encode d : q=query+ s t ri n g & foo=bar CLI ENT VALUE S : c l i e nt_a ddre s s = ( ' 1 2 7 . 0 . 0 . l ' , 5 8 4 5 5 ) comma nd=GET pat h= / ? q=que ry+s t r i ng & foo=ba r real path=/ que ry=q=query+ s t ring& foo=bar reques t_ve r s i on=HTT P / 1 . 1

( 12 7 . О . О . 1 )

772

Гм88 12. Интернет

SERVER VALUES : s e rve r v e r s i o n=Ba seHTT P / 0 . 6 sys ve � s i on=Python / 3 . 5 . 2 prot ocol ve r s i on=HTT P / 1 . 0 HEADE RS RECE IVED : Accept-Encod i ng=i dent i t y Conne ct i on=c l o s e Host=localhost : 8 0 8 0 U s e r -Agent=Python-ur l l i Ь/ 3 . 5

1 2.2.3. Метод НПР POST Примечание Код тестового с ервера для последующих примеров с одержится в файле h t t p s e rver 1 2.5� Сервер следует запустить в одн ом окн е, а выпол н ят ь примеры - в другом.

GET . ру, н аходящем ся в папке примеров для модуля http . s e rver (раздел

Чтобы отправить удаленному серверу данные, закодированные в виде формы, используя метод POST вместо метода GКГ, следует передать закодированные па· раметры аапроса в виде данных функции ur lopen ( ) . Листинг 1 2. 1 8. url lib_reques t_urlopenyos t . py from u r l l i b import p a r s e from url l ib import reque s t que ry_a r g s = { ' q ' : ' query s t r i ng ' , ' foo ' : ' ba r ' } e ncoded a r g s pars e . u r l e ncode ( query-a rg s ) . e ncode ( ' ut f- 8 ' ) url = ' h t t p : / / l o c a lho s t : 8 0 8 0 / ' print ( reques t . u r l open ( ur l , encoded_a rgs ) . read ( ) . de code ( ' ut f - 8 ' ) ) =

Сервер может декодировать данные формы и получить доступ к индиоидуаль· ным значениям по имени. $ python 3 ur l l i b_reque s t_ur l open_po s t . py Cl ient : ( ' 1 2 7 . 0 . 0 . 1 ' , 5 8 5 6 8 ) Use r-agent : Python-url l iЬ / 3 . 5 Path : / Form da t a : q=que r y s t r i ng foo=ba r

1 2.2.4. Добавление исходящих заголовков Функция ur lopen ( ) скрывает некоторые детали создания и обработки запро· са. Возможнщ:ти более точпоl'о контроля обеспечивает непосредственное исrюль­ зование экземпляра Reque st. Например, в исходящий запрос могуг быть добав· лены нользовательские за!'оловки, позволяющие управлять форматом возвраща· емых данных, указывать локальную кешировапную версию документа и сообщать удаленному серверу имя взаимодействующей с ним клиентской программы.

12.2. urtllb.request: AOC'fYll к с:еrевwм ресурсвм

773

Как следует из результатов предыдущего примера, значение 110 умолчанию заголовка User-agent состоит из константы Python-ur l l ib и номера версии интерпретатора Python. При создании приложения, которое будет получать до­ ступ к веб-ресурсам, принадлежащим другим владельцам, считается правилом хо­ рошего тона включать в запрос информацию о реальном владельце агента, что упрощает ведение аналитики посещаемости сайтов. Кроме того, использование пользовательского заголовка агента позволяет владельцам сайтов контролиро­ вать действия веб-роботов с помощью файла 1vbots. txt (см. описание модуля http . robo tpar ser). Листинг 1 2. 1 9. ur llib_request request header . ру _

_

f r om u r l l ib import reque s t r = r e que s t . Reque s t ( ' ht t p : / / l oc a l h o s t : 8 0 8 0 / ' ) r . add_he ader ( ' U s e r - a g e nt ' , ' PyMOTW ( ht t p s : / /pymotw . com/ ) ' ,

response = r e que s t . u r l open ( r ) re spon s e . read ( ) . decode ( ' ut f - 8 ' ) dat a print ( dat a ) =

После создания объекта Reque s t , н о до открытия запроса следует устано­ вить значение пользовательского заголовка с rюмоЩI>Ю метода add_header ( ) . Пользовательское значение отображается в последней строке вывода. $ pyt honЗ ur l l i b_r eques t_reque st_heade r . py

CLI ENT VALUES : c l i e nt_addr e s s= ( ' 1 2 7 . 0 . 0 . 1 ' , 5 8 5 8 5 ) CO!\Ul\a nd=GET path= / r e a l path=/ quer y= reque s t_ve r s i on=HTT P / 1 . 1

( 127 . 0 . 0 . 1 )

SERVER VALUE S : s e r v e r v e r s i on=Ba s eHTT P / 0 . 6 s y s ve r s i on= Pyt h o n / 3 . 5 . 2 prot ocol_ve r s i on=HTT P / 1 . 0 HEADERS RECE IVE D : Accept - E ncodi ng= i dent i t y Conne c t i o n=cl o s e Host=localhost : 8 0 8 0 U s e r -Agent=PyMOTW ( h t t p s : / /pymot w . com/ )

1 2.2.5. Отправка формы данных на сервер Создавая объект Reque s t , можно ука.зать в аргументе data исходящие данные, подлежащие выгрузке на сервер.

ГА888 12. Интернет

714 Листинг 1 2.20. urllib request reques t_Post . ру _

_

frorn urll ib irnport par s e frorn u r l l i b import reque s t que r y_a rgs

=

{ 'q' :

' query s t r i n g ' ,

' foo ' :

' ba r ' }

r = reque s t . Reque s t ( u r l = ' h t t p : / / l ocalhost : 8 0 8 0 / ' , dat a=pa r s e . u r l encode ( query_a r g s ) . e ncode ( ' u t f- 8 ' ) , p r i n t ( ' Re que s t rnethod . 1 , r . ge t_rnet hod ( ) ) r . add_heade r ( ' Us e r - a g ent ' , ' PyMOTW ( ht t p s : / /pymot w . corn/ ) ' , •

print ( ) print ( ' OUTGOING DATA : ' ) p r i n t ( r . da t a ) print ( ) p r i n t ( ' SERVER RES PONSE : ' ) p r i n t ( reque s t . u r l open ( r ) . r ead ( ) . decode ( ' ut f - 8 ' ) )

При наличии этого аргумента НТТР-метод GJt:T, используемый объектом Reques t , автоматически заменяется методом POST. $ pythonЗ u r l l ib_reque s t_re que s t_po s t . py Requ e s t rne thod : POST OUTGOING DATA : b ' q=que ry+ s t r i n g & foo=ba r ' SERVER RE S PONS E : Cl ient : ( ' 12 7 . 0 . 0 . 1 ' , 5 8 61 3 ) U s e r - a gent : PyMOTW ( ht t p s : / /pymo t w . corn/ ) Pa th : / Forrn da t a : foo=ba r q=query s t r i n g

1 2.2.б. Выгрузка файлов Кодирование файлов для выгрузки на сервер требует выполнения чуть боль­ шего объема работы, чем при исполь.1овании простых форм. Для этого в теле за­ проса необходимо сконструировать полное МIМЕ-сообщение, чтобы сервер мог отличать поля входящей формы от выгружаемых файлов. Листинг 1 2. 2 1 . url lib_request_upload_fi les . py irnport io irnport rnirne t ypes

12.2. urlllb.request: АОСJУП к С818ВЫМ ресурсам f r orn url l ib irnport re que s t import uuid c l a s s Mu l t i Pa r t Forrn : " " " На капливае т да нные , исполь зуемые при публикации формы . " " " de f

init ( se l f ) : se l f . f o rm f i e l d s = [ ] sel f . files = [ ] # Исполь зова т ь случайную байтовую строку большой длины # для разде л е ния отдель ных частей МIМЕ-да нных s e l f . bounda ry uui d . uuid4 ( ) . hex . e ncode ( ' ut f - 8 ' ) ret urn =

de f get cont ent t ype ( s e l f ) : return ' rnu l t ipart / f o rm-da t a ; bounda ry= ( } ' . fo rrna t ( s e l f . bounda r y . decode ( ' ut f- 8 ' ) ) de f a dd f i e l d ( s e l f , narne , v a l ue ) : " " "Добавить простое поле в форму да нных . " " " s e l f . form_f i e l d s . append ( ( narne , v a l ue ) ) de f a dd_f i l e ( s e l f , f i e l dname , f i l e narne , f i l eHandl e , mime t ype=None ) : " " " До бавит ь выгружа емый файл . " " " body fi leHandl e . read ( ) i f rnime t ype i s None : rnirne t ype ( mirne t ype s . gue s s_type ( f i l ename ) [ 0 ] o r ' appl i ca t i on / o c t e t - s t r e am ' =

=

s e l f . f i l e s . append ( ( f i e l dnarne , f i l e name , mime t ype , body ) ) return @ s t a t i cme thod de f fo rm_da t a ( narne ) : return ( ' Cont e n t - D i spos i t i on : form-da t a ; ' ' narne=" ( } " \ r \ n ' ) forma t ( name ) . encode ( ' ut f- 8 ' ) •

@ s t a t i crne thod de f _ a t t a ched_f i l e ( narne , f i l enarne ) : return ( ' Cont e n t - Di spos i t i on : f i l e ; ' ' name = " ( } " ; f i l enarne = " ( } " \ r \n ' ) . forrna t ( narne , f i l ename ) . en code ( ' ut f - 8 ' ) @ s t a t i cme thod de f _conte nt_t ype ( ct ) : return ' Co n t e n t -Type :

{ } \ r \ n ' . fo rrnat ( c t ) . e ncode ( ' ut f- 8 ' )

def �byt e s� ( s e l f ) : " " " Вернуть байтовую строку , предс т авляющую данные формы , включа я присо едине нные файлы . """

775

l'Аава 12. ИНУернет

776 buffe r = i o . Byte s I O ( ) boundary Ь ' -- ' + s e l f . bounda ry + b ' \ r \ n ' # Д о бав и т ь п оля ф ормы f o r name , value i n se l f . form_ f i e l d s : b u f fe r . w r i t e ( bounda r y ) buf fe r . wr i t e ( se l f . _f orm_dat a ( name ) ) buffe r . wri te ( b ' \ r \ n ' ) buff e r . w r i t e ( va l ue . e ncode ( ' u t f - 8 ' ) ) buffe r . wr i t e ( b ' \ r \ n ' )

# До бавить выгружа емые файлы f o r f_name , f i l ename , f_content_t ype , body in se l f . f i l e s : buffe r . w r i t e ( bounda r y ) b u f fe r . wr i t e ( s e l f . _a t t ached_fi l e ( f_name , f i l e name ) ) buffe r . wri te ( s e l f . content -t ype ( f content t ype ) ) b u f f e r . wr i t e ( b ' \ r \ � ' ) bu f f e r . wr i t e ( body ) buff e r . write ( Ь ' \ r \ n ' ) -

-

b u f fe r . wr i t e ( b ' - - ' + se l f . boundary + b ' - - \ r \ n ' ) return bu f fe r . g e t v a l ue ( )

if

name ' ma i n ' # С о зда т ь ф орму с пр о с тыми п о лями f o rm Mu l t i Pa r t Fo rm ( ) form . add_ f i e l d ( ' f i r s t name ' , ' Doug ' ) form . add_fi e l d ( ' l a s t name ' , ' He l lmann ' ) ==

·

=

# Д о ба вить ф и кт и в ный файл f o rm . a dd_f i l e ( ' b i og raphy ' , ' bi o . t x t ' , f i leHand l e = i o . Byt e s I O ( b ' Python deve l ope r and Ыogge r . ' ) ) # С о здать запр ос , в клю ч ающий байт о вую с т р о ку # для выгружа емых да нных data byte s ( form) r = r e que s t . Reque s t ( ' ht tp : / / l oc a l h o s t : 8 0 8 0 / ' , data=da t a ) r . add_heade r ( ' U se r-agent ' , ' PyMOTW ( ht t p s : / /pymo t w . com/ ) ' , =

r . a dd_he ader ( ' Content - t ype ' , form . ge t content t ype ( ) ) r . add_he ade r ( ' Content - l ength ' , l e n ( da t a ) ) pr i nt ( ) p r i nt ( ' OUTGOING DATA : ' ) f o r name , va l ue i n r . heade r i tems ( ) : p r i nt ( ' { } : ( } ' . forma t ( name , va l ue ) ) print ( ) print ( r . da t a . de c ode ( ' ut f - 8 ' } )

m

12.2. ut111b.request АОС1У11 к сетевым ресурсам

print ( ) print ( ' SERVER RES PONSE : ' ) print ( requ e s t . url open ( r ) . read ( ) . de code ( ' ut f- 8 ' ) )

$

python3 u r l l ib_re qu e s t_up l oad_f i l e s . py

OUTGOING DATA : User-agent : PyMOTW ( ht tps : / /pymot w . com/ ) Content - t ype : rnu l t ipart / forrn-data ; bounda ry=d 9 9b5dc6 0 8 7 1 4 9 l b 9d 6 3 3 5 2 eb 2 4 9 7 2 b4 Content - l ength : 3 8 9 - -d 9 9b5dc 6 0 8 7 1 4 9 l b 9d 6 3 3 5 2 eb2 4 9 7 2b 4 Conten t - Di spo s i t i on : f o rrn-da t a ; n arne= " f i r s t name " Doug --d 9 9b5dc 6 0 8 7 1 4 9 1 b 9d 6 3 3 5 2 eb2 4 9 7 2b 4 Cont e n t - D i spo s i t i on : f o rm- da t a ; narne= " l a s t narne " H e l lma nn - - d 9 9b5dc 6 0 8 7 1 4 9 l b 9 d6 3 3 52eb2 4 9 7 2 b 4 Content -Di spo s i t i on : f i l e ; narne = " Ь i og raph y " ; f i l enarne= " b i o . t xt " Content -Type : t ext /pl a i n Python deve l ope r and Ьlogger . - -d 9 9b5dc 6 0 8 7 1 4 9 l b 9d 6 3 3 52eb2 4 9 7 2 b 4 - -

SERVER RES PONSE : C l i ent : ( ' 1 2 7 . 0 . 0 . 1 ' , 5 9 3 1 0 ) U s e r - a g e n t : PyMOTW ( h t tps : / /pymotw . com/ ) Path : / Forrn data : Uploaded Ьi og raphy as ' Ьi o . txt ' ( 2 9 byt e s ) f i r s t n arne=Doug l a s t narne=He l lrnann

1 2.2. 7. Создание пользовательских обработчиков протоколов Модуль url l ib . reques t имеет встроенную поддержку протоколов НТГР(S) , FTP и локального доступа к файлам. Для добавления поддержки URL-aдpecoв дру­

гих типов необходимо зарегистрировать обработчики соответствующих прото­ колов. Например, чтобы организовать поддержку URL-aдpecoв, представляющих произвольные файлы на удаленных серверах NFS, и при этом не требовать от пользователя предварительного монтирования пути к файлу для получен ия досту­ па к нему, следует создать подкласс BaseHandler с методом nfs open ( ) . Специфический для протокола метод open ( ) получает единственный аргу­ мент - экземпляр Request и возвращает объект, который имеет метод r ead ( ) _

-

Гмва 12. Интернеr

778

для чтения данных, метод i n f o ( ) , возвращающий заголовок ответов, и метод getur l ( ) , возвращающий фактический URl--aдpec файла, который необходимо прочитать. Самый п ростой способ удовлетворить этим требованиям - создать экземпляр urllib . response . addinfour l , передав конструктору заголовки, URL­ aдpec и дескриптор файла. Листинr 1 2 .22. url l ib_reques t_nfs_handler . py irnport i o irnpo r t rnirne t ypes irnp o r t os irnp o r t t ernp f i l e frorn ur l l i b irnport re que s t f rorn ur l l i b irnport response

c l a s s NFSFi l e : def

init ( s e l f , t ernpd i r , f i l enarne ) : s e l f . t ernpdi r t ernpdi r s e l f . f i l enarne f i l e narne with open ( o s . pa t h . j o i n ( t ernpdi r , f i l enarne ) , se l f . bu f f e r i o . Byt e s I O ( f . read ( ) ) =

=

' rb ' ) a s f :

=

def read ( s e l f , * a r g s ) : re turn s e l f . bu f fe r . read ( * a rg s ) def readl ine ( s e l f , * a rg s ) : return s e l f . bu f f e r . readl ine ( * a rg s ) def c l o s e ( s e l f ) : p r i n t ( ' \ nNFS Fi l e : ' ) print ( ' unrnount i ng { } ' . fo rrna t ( o s . pa t h . b a s e narne ( s e l f . t ernpdi r ) ) ) print ( ' whe n { } i s c l o sed ' . f o rrna t ( o s . pa t h . b a s enarne ( s e l f . f i l e narne ) ) )

c l a s s FauxNFSHandl e r ( r e que s t . Ba s eHand l e r ) : def � i n i t� ( s e l f , t ernpdi r ) : s e l f . ternpd i r t ernpdi r super ( ) . init () =

def n f s_ope n ( s e l f , r e q ) : url r e q . ful l_url di rect o r y_narne , f i l e_narne o s . pa t h . spl i t ( u r l ) s e rver narne r e q . ho s t p r i n t ( 1 FauxNFSHandl e r s irnu l a t i ng rnount : ' ) print ( ' Rernot e path : { } ' . forrnat ( d i re c t o r y narne ) ) print ( ' Server { } ' . fo rrnat ( s e rve r_nam e ) ) print ( ' Local path : { } ' . fo rrnat ( os . pa t h . ba s enarne ( t ernpdi r ) ) ) =

=

=

12.2. Urlllb.requeat: АОсtУП К С81'8ВЫМ ресурсам

779

print ( ' Fi l e narne : ( } ' . f orrna t ( f i l e_narne ) ) l ocal_f i l e o s . pa t h . j oi n ( ternpdi r , f i l e_narne ) fp N FS Fi le ( t ernpd i r , f i l e_narne ) conte nt_t ype ( rnirne t ype s . gues s_t ype ( f i l e_narne ) [ 0 ] o r ' appl i ca t i o n / o c t e t - s t rearn ' =

=

=

s t a t s = o s . s t a t ( l ocal_ f i l e ) s i ze = s t a t s . s t s i z e heade r s ( ' Cont ent - t ype ' : conten t_t ype , ' Cont ent - l ength ' : s i z e , =

r e t urn re spon s e . addi n four l ( fp , heade r s , r e q . g e t_ful l_u r l ( ) )

if

narne == ' rna i n ' · w i t h t ernp f i l e . TernporaryDi r e c t o r y ( ) as t ernpd i r : # Соз д а т ь со д ержимое временного файла f i l enarne o s . pa th . j o i n ( t ernpdi r , ' f i l e . t xt ' ) w i t h open ( fi l e n arne , ' w ' , encodi ng= ' ut f - 8 ' ) as f : f . wr i t e ( ' Content s o f f i l e . tx t ' ) =

# Соз д а т ь объе кт достуnа к ресурсам с nомощью нашего # NFS- обра ботчика и зарегистрир о в а т ь его дл я # исnоль з ования по умолча нию opene r reque s t . bui l d_opene r ( FauxNFSHand l e r ( t ernpd i r ) ) reque s t . i n s t a l l_opene r ( opener ) =

# Открыть файл , исполь зуя URL- aдpe c resp r e que s t . urlope n ( ' n f s : / / rernote_s e rv e r / p a th / t o / the / fi l e . txt ' =

print ( ) p r i n t ( ' READ CONTENT S : ' , r e s p . re ad ( ) ) : ' , resp . geturl ( ) ) p r i n t ( ' URL p r i n t ( ' HEADERS : ' ) for narne , va lue in s o r t ed ( re sp . i n fo ( ) . i t erns ( ) ) : print ( ' ( : б } : { } ' . f o rma t ( pa r s e r . can_fe t ch ( AGENT_NAМE , pat h ) , pa t h ) ) u r l = pa r s e . ur l j o i n ( URL_BAS E , pat h ) p r i nt ( , { ! r : > б } : { } ' . fo rma t ( pa rs e r . can_f e t ch ( AGENT_NAМE , ur l ) , u r l ) ) print ( )

В качестве аргумента URL метода can_ fetch ( ) может выступать путь, задан ­ ный относительно корневой папки сайта, или полный URl.·aдpec.

10 www . robo t s t x t . or g / o r i g . html

782

ГА888 12. И�nернет

$ pythonЗ u r l l i b_robo tpa r s e r_s imp l e . py

True True

/ https : / /pymotw . com/

True True

/ PyMOTW/ h t t ps : / /pymo t w . com/PyMOTW/

Fa l s e Fa l s e

/admi n / https : / /pymotw . com/ admi n /

Fa l s e Fa l s e

/ down l o a ds / PyMOTW- 1 . 9 2 . t a r . g z https : / /pymotw . com/ down l oads / PyMOTW- 1 . 92 . t a r . g z

1 2.3.3. Длительно выполняющиеся веб-роботы Приложения, которые тратят много времени на обработку загружаемых ре­ сурсов или простаивают в промежутках между загрузками, должны периодически проверять наличие новых файлов rohots. txt на основании срока жизни уже загру­ женного содержимого. Сроком жизни нельзя управлять автоматически , но име­ ются вспомогательные методы, позволяющие отслеживать значение этого пара­ метра. Листинг 1 2.25. urllib_robotpar ser longl i ved . ру _

f r om u r l l ib import robotpa r s e r impor t t ime AGENT NАМЕ ' PyMOTW ' pa r s e r robotpa rse r . Robot F i l e Pa r s e r ( ) # Ис пол ь з о в а ние локал ь ной копии pars e r . s e t_u r l ( ' f i l e : robots . t xt ' ) pa rs e r . r e a d ( ) pa r s e r . modi f i e d ( ) =

=

PATHS [ '/', ' / PyMOTW/ ' , ' /admi n / ' , ' / down l o a ds / P yMOTW- 1 . 9 2 . t a r . g z ' , =

for path i n PATHS : age i n t ( time . t ime ( ) - pa r s e r . mt ime ( ) ) print ( ' a ge : ' , age , end= ' ' ) i f a ge > 1 : p r i n t ( ' rereading robot s . t xt ' ) pars e r . read ( ) pa r s e r . modi f i e d ( ) else : print ( ) { ) ' . fo rma t ( p r i nt ( ' { ! r : > б ) =

12.4. Ь8sе64: КОАИров&нме АВОмчнwх АВННЫХ с помощыо ASCll

783

pa r s e r . can_ f e t ch ( AGENT_NAМE, pat h ) , pat h ) ) # Имта ци я задержки в проце ссе обра ботки t ime . s l e ep ( l ) print ( )

В этом экстремальном примере новый файл robots.txt загружается в том случае, если время жизни существующего файла превышает 1 секунду. $ pyt h o n З u r l l ib_robotp a r s e r_long l i ved . py age : О True

/

age : 1 T rue : / PyMOTW / age : 2 rereading robot s . txt Fa l s e : /admin/ age : 1 Fa l s e : /down l oads / PyMOTW- 1 . 92 . t a r . g z

Хорошо организован ная версия длительно вьшолпяющеrося приложения мо­ жет запрашивать время последнего изменения файла, прежде чем загружать сам файл. Однако обычно файлы roЬots. txt имеют доволыю небольшие размеры, и по­ э·гому повторная загрузка всего документа не является слишком дорогостоящей операцией. Дополнительные ссыпки •



Раздел документации стандартной библиотеки, посвященный модулю ur l l i b . robotpa r s e r 1 1 • Web Robots Pages 12 • Описание формата файла robots.txt.

1 2 .4. base 64 : кодирование двоичных данных с помощью ASCl l Модуль bas e 6 4 содержит функции, позволяющие преобразовывать двоичпые данные в текстовое представление с использованием подмножества символов ASCII , пригодного для передачи по сети посредством простых текстовых прото· колов. Кодировки Base64, Base32, Base l б и Base85 преобразуют 8-битовые байты в значения из диапазона печатаемых символов ASCII, добавляя в них дополни· тельные биты для достижения совместимости с системами, которые поддержива­ ют только АSСП-данные, такими как SMTP. Значения Base соответствуют длине алфавита, используемого в каждой кодировке. Вариации исходных кодировок, безопасные в отношении включения в URl,-aдpeca, используют несколько и ные алфавиты. 1 1 https : / /docs . python . or g / 3 . 5 / l i b r a r y / u r l l ib . robotpa r s e r . html 12 www . robot s t x t . o r g / o r i g . html

784

1""888 12. Иtnернет

1 2.4. 1 . Кодировка Base64 В следующем листинге представлен простой пример кодирования текста. Листинг 1 2.26. base64_Ь64encode . py import ba s e 6 4 impo r t t extwrap # За грузить этот исходный файл и отделить з а г оловок with open ( �f i l e�, ' r ' , encoding= ' ut f - 8 ' ) as i nput : raw i nput . read ( ) i n i t i a l_da t a = raw . spl i t ( ' # e nd_pymot w_heade r ' ) [ 1 ] =

byte_s t r i ng = i n i t i a l_da t a . e ncode ( ' ut f - 8 ' ) encoded_da t a = ba s e 6 4 . b 6 4 e ncode ( byte_s t r i n g ) num_i n i t i a l

=

l e n ( byte_s t r i n g )

# Никог д а не буде т больше 2 дополнител ь ных байтов padd i n g = З - ( num_i n i t i a l % 3 ) print print print print

( ' ( ) byt e s b e f o r e encoding ' . forma t ( num_i n i t i a l ) ) ( ' Expect { ) padding byt e s ' . forma t ( paddi ng ) ) ( ' ( ) byt e s a f t e r encod i ng \ n ' . forma t ( l e n ( encoded_da ta ) ) ) ( e ncoded_da t a )

Входные данные должны быть байтовой строкой, поэтому строка UнicoF­ файлах. $ python3 ba s e 6 4_bas e 8 5 . py O r i g i na l Ь б 4 Encoded yLg== ' Ь 8 5 Encoded а 8 5 Encoded

31 b yt e s b ' Thi s is the d a t a , in the cl e a r . ' 4 4 byt e s b ' VGhpcyBpcyBOaGUgZGFOYSwgaW 4 gdGhl IGNsZWF 39 byt e s b ' RAA - ) AZc?TbZBKDWMOn+EFfuaAa r P DAY* KOVR 9 } ' 3 9 byt e s b ' FD, 5 . A7 9Rg / O JYE+EV : . +C f 5 ! @< * t '

Дополнительные ссылки • •

Раздел документации стандартн ой библиотеки, пос вя щенн ый модулю Ьаs е б 4

RFC 35481 4• The Воsе 1 б, Bose32, ond Воsеб4 Doto Encodings.

1 3 h t t p s : / /docs . python . org / 3 . 5 / l ibrary/base 6 4 . html 1 4 https : / / t o o l s . i e t f . o rg / html / r f c 3 5 4 8 . html

13



788 •

• •

Гмве 12. Интернет

1 9241 5. А Compact Representation of /Рvб Addresses (предложение кодировки Base85 для сетевых адресов 1Pv6). Википедия : Ascii85 1 6 •

RFC

Замечания относительно портирования программ из Pythoп модуля bas e 6 4 (раздел А.6.6).

2

в Pythoп

3, касающиеся

1 2 . 5 . ht tp . server: базовые классы

для реализации неб-серверов Используя кла t o s t op

Аргументы команды cur 1 могут включать форму данных, отправ.ляемую на сервер с помощью опции F Последни й аргумент, -F data f i le=@ http_se rve r_ -

.

792

ГА888 12. ИН1'8рнет

GET . ру, задаст отправку содержимого файла монстрировать чтение данных из формы.

h t tp_ s e r v e r_GET . ру,

чтобы проде­

$ curl -v ht tp : / / 1 2 7 . 0 . 0 . 1 : 8 0 8 0 / -F narne=dh e l lrnann -F foo=b a r \ -F d a t a f i l e= @ ht tp_ s e rve r_GET . py * T r y i ng 1 2 7 . 0 . 0 . 1 . . . * Connected t o 1 2 7 . 0 . 0 . 1 ( 1 2 7 . 0 . 0 . 1 ) port 8 0 8 0 ( # 0 ) > POST / НТТ Р / 1 . 1 > Ho s t : 1 2 7 . 0 . 0 . 1 : 8 0 8 0 > U s e r -Agent : cur l / 7 . 4 3 . 0 > Accept : * / * > Content - Length : 1 9 7 4 > Expect : 1 0 0 -cont i nue > Content -Type : rnu l t ip a r t / forrn-data ; bounda ry= - - - - - - - - - - - - -- - - - - -- - - - - a 2 b 3 c 7 4 8 5 c f 8 de f2 > * Done wa i t i ng f o r 1 0 0 -conti nue НТТ Р / 1 . 0 2 0 0 ОК Content -Type : t e x t / pl a i n ; cha r s e t =ut f - 8 S e r ve r : B a s e HTT P / 0 . 6 Python / 3 . 5 . 2 Da t e : Thu , 0 6 Oct 2 0 1 6 2 0 : 5 3 : 4 8 GМТ Cl ient : ( ' 1 2 7 . 0 . 0 . 1 ' , 5 3 1 2 1 ) U s e r - agent : cur l / 7 . 4 3 . 0 Path : / Forrn data : narne=dhel lrnann Uploaded da t a f i l e a s ' ht t p_se rve r_GET . py ' ( 1 6 1 2 byt e s ) f o o=ba r * Conne c t i on # О t o h o s t 1 2 7 . 0 . 0 . 1 l e f t i n t a c t

1 2. 5. 3 .

Порождение потоков и процессов

H T T P S e rver - это простой подкласс класса s o c ke t s e r ve r . T C P S e r v e r , кото­ рый не использует много1юточность или ветвление процессов для обработки за­ просов. Чтобы добавить в него эти возможности, создайте новый класс, исполь­ зуя подходящий примесный класс из модуля s o c k e t s erve r (раэдел 1 1 .5 ) .

Листинг 1 2. 34. http_server_threads . ру f r orn http . s e rver irnport HTT P S e r ve r , B a s e HTT PReque s t H a ndl e r f r orn s oc ke t s e rver irnport Thre adi ngMi x i n i rnport threading

c l a s s Handle r ( BaseHTTPReque stHandle r ) : de f do_GET ( s e l f ) : s e l f . s end_r e spon s e ( 2 0 0 ) s e l f . send-header ( ' Cont e n t -Type ' , ' t ex t / p l a i n ; cha r s et=ut f - 8 ' )

783 s e l f . end_heade r s ( ) me s s age = thre ading . currentTh r e ad ( ) . getName ( ) s e l f . wf i l e . wr i t e ( me s s age . e ncode ( ' ut f- 8 ' ) ) s e l f . wf i l e . wr i t e ( b ' \ n ' )

c l a s s Threade dHTT P S e r ve r ( ThreadingMi x i n , HTT P S e rve r ) : " " " Обра батыв а т ь запросы в отдель ном nотоке . " " "

if

name == ' ma i n ' : s e rver ThreadedHTT PServer ( ( ' l oc a l h o s t ' , 8 0 8 0 ) , Handl e r ) p r i n t ( ' S t a r t i ng s erve r , u s e t o s t op ' ) se rve r . s e rve_foreve r ( ) =

Запустите сервер тем же способом, что и в других примерах. $ pythonЗ http_s e rver_threads . py

S t a rt i ng s e rve r , u s e < C t r l -C > t o s t op

Каждый раз, когда сервер получает запрос, он запускает новый поток или про­ цесс для его обработки . $ curl http : / / 1 2 7 . 0 . 0 . 1 : 8 0 8 0 / Thread- 1 $ curl ht tp : / / 1 2 7 . 0 . 0 . 1 : 8 0 8 0 / Thread-2 $ curl h t t p : / / 1 2 7 . 0 . 0 . 1 : 8 0 8 0 / Thread - 3

При замене аргумента ThreadingMix l n аргументом For kingMixln будут полу­ чены аналогичные результаты, но вместо потоков будуr использоваться процессы.

1 2.5.4. Обработка ошибок Для обработки ошибок предназначена функция send e r ror ( ) , которая полу­ чает код ошибки и необязательное сообщение. Полный ответ (включая заголов· ки , код состояния и тело сообщения) генерируется автоматически. _

Листинг 1 2.35. http_server_errors . py f r om ht tp . s e rver import Ba s eHTTPRequ e s tHand l e r

c l a s s E r r o rHand l e r ( Ba s eHTTPRequest Hand l e r ) : de f do_GET ( s e l f ) : s e l f . s e nd_e r r o r ( 4 0 4 )

if

name ' -ma i n- ' : f r om h t tp . s e rver import HTT P S e rver s e rve r HTT P S e r ve r ( ( ' l oc a l ho s t ' , 8 0 8 0 ) , E r r o rHandl e r ) ==

=

794

Гмве 12. Интернет p r i n t ( ' Start ing s e rver , use t o s t op ' ) s e r ve r . s erve_foreve r ( )

В данном случае всегда возвращается код ошибки 4 0 4 . $

pythonЗ http_s e rve r_e r r o r s . py

S t a r t ing s e rve r , u s e < Ct r l -C> t o s t op

Клиенту отправляется заголовок с кодом ошибки и сообщение об ошибке в виде НТМI.-документа. $ curl - i ht tp : / / 1 2 7 . 0 . 0 . 1 : 8 0 8 0 / НТТ Р / 1 . 0 4 0 4 Not Found S e r ve r : B a s e HTT P / 0 . 6 Python / 3 . 5 . 2 Da t e : Thu , 0 6 Oct 2 0 1 6 2 0 : 5 8 : 0 8 GМТ Connect i on : c l o s e Content - Type : text /html ; ch a r s e t=ut f - 8 Content -Length : 4 4 7 < ! DOCTYPE HTML PUB L I C 11 - / /W ЗC / / DT D HTML 4 . 0 1 / /EN " " ht tp : / /www . w3 . o r g /TR/html 4 / s t r i ct . dt d " >

< t i t l e >E r r o r re spons e < / t i t l e > < /head>

< h l >Error respons e < / h l >

Error code : 4 0 4 < /р>

Me s s age : Not Found . < /p>

Error code explanat i on : 4 0 4 - Nothing ma t che s t he g i ven URI . < /p > < /body> < /html>

1 2.5.5. Настройка заголовков Метод send heade r ( ) позволяет добавить данные заголовка в НТТР-ответ. Он получает два аргумента: имя и значение заголовка. _

Листинг 1 2.36. http_server_send_header . py f r om http . s e rve r import B a s e HTT PReque s t Hand l e r import t ime

c l a s s GetHandle r ( B a s e HTT PReque s tHandl e r ) : def do_GET ( s e l f ) : s e l f . s e n d_re sponse ( 2 0 0 )

12.5. http.server: ба:юеые КАВССЫ NtЯ реuмэацм м веб-серверое

795

s e l f . s end_he ader ( ' Cont ent -Type ' , ' t ext /p l a i n ; ch a r s e t=u t f - 8 ' , s e l f . s end heade r ( ' La s t =мodi f i e d ' , s e l f . dat e_t ime_s t r i ng ( t ime . t ime ( ) ) s e l f . end_heade r s ( ) s e l f . wf i l e . wr i t e ( ' Re spons e body\n ' . e ncode ( ' ut f - 8 ' ) ) if

name ' rna i n f r om h t tp . s e rve r import HTT P S e r ve r s e rve r HTT PSe rve r ( ( ' l oc a l h o s t ' , 8 0 8 0 ) , GetHandl e r ) p r i n t ( ' S t a r t i ng s e r ve r , u s e t o s t op ' ) s e r ve r . s e r ve_foreve r ( ) ==

'

·

=

В этом примере для отметки текущего времени, форматированной в соответ­ ствии с документом RFC 723 1 , устанавливается заголовок La s t -Modified. $ curl - i h t tp : / / 1 2 7 . 0 . 0 . 1 : 8 0 8 0 / НТТР / 1 . 0 2 0 0 ОК S erve r : Bas eHTT P / 0 . 6 Python / 3 . 5 . 2 Da te : Thu , 0 6 Oct 2 0 1 6 2 1 : 0 0 : 5 4 GMT Cont en t -Type : text /p l a i n ; cha r s et=ut f - 8 L a s t -Modi f i ed : Thu , 0 6 Oct 2 0 1 6 2 1 : 0 0 : 54 GMT Re spon s e body

Как и в других примерах, сервер выводит запрос на терминал. $ pythonЗ ht tp_se rve r_se nd_he ade r . py S t a rt i ng s e rve r , us e < C t r l - C > t o s t op 1 2 7 . 0 . 0 . 1 - - [ 0 6 / 0ct / 2 0 1 6 1 7 : 0 0 : 5 4 ) " GET / НТТ Р / 1 . 1 " 2 0 0 -

1 2.5.6. Использование командной строки Модуль h t t p . server включает встроенный сервер для обслуживания файлов локальной файловой системы. Чтобы запустить его из командной строки , следует использовать опцию -m для интерпретатора Pytlюn. $ pythonЗ -m http . s e r ve r 8 0 8 0 S e rving НТТ Р o n О . О . О . О port 8 0 8 0 1 2 7 . 0 . 0 . 1 - - [ 0 6 /0c t / 2 0 1 6 1 7 : 1 2 : 4 8 )

" HEAD / index . rs t НТТ Р / 1 . 1 " 2 0 0 -

Корневым каталоl'ом сервера служит рабочий каталог, в котором он запускается. $ curl - I http : / / 1 2 7 . 0 . 0 . 1 : 8 0 8 0 / i ndex . rs t НТТ Р / 1 . 0 2 0 0 ОК

ГАава 12. Интернет Serve r : S i mp l eHTT P / 0 . 6 Python / 3 . 5 . 2 Da t e : Thu , 0 6 Oct 2 0 1 6 2 1 : 1 2 : 4 8 GМТ Content - t ype : app l i c a t i o n / o c t e t - s t r eam Content-Length : 8 2 8 5 La s t -Modi f i e d : Thu , 0 6 Oct 2 0 1 6 2 1 : 1 2 : 1 0 GMT Дополнительные ссылки • •



Раздел документации стандартной библиотеки, посвященный модулю h t tp . s e rver 17 • s oc ke t s e rver (раздел 742). Модуль s oc ke t s erver предоставляет базовый класс для управления серверным сокетом. RFC 7231 1 8• Hypertext Transfer Protocol (НТТР/ 1 . 1): Semantics and Content. Данный RFС­ документ включает спецификацию формата заголовков и дат для протокола НТГР.

1 2 . 6 . http . cookies : сооkiе-файлы НПР Модуль h t tp . cookies реализует синтаксический анализатор для рабо­ ты с сооkiе-файлами , который в основном отвечает требованиям документа R1''C 2 1 09 1 !1. Эта реализация не такая строгаа, как того требует стандарт, посколь­ ку приложение MSIE 3.Ох не обеспечи вает полную поддержку этого стандарта.

1 2.6. 1 . Создание и настройка сооkiе-файлов Файлы cookie (или просто кукu.) используются в качестве средства управления состоянием в приложениях на основе браузера. Они представляют собой неболь­ шие фрагменты данных, создаваемые сервером и сохраняемые на стороне клиен­ та. Простейшим примером сооkiе-файла может служить пара "имя-значение". Листинг 1 2. 37. http_cookies_setheaders . ру from http import coo k i e s с = cooki e s . Simp l eCooki e ( ) c [ ' mycoo ki e ' ] = ' cookie -value ' print ( c )

Вывод представляет собой действительный заголовок Set-Cookie, который может передаваться клиен ту вместе с ответом НТТР. $

python3 ht tp_coo ki e s_s e theade r s . py

S e t - Cooki e : mycookie=co o k i e_va lue

1 2 .6.2. Атрибуты сооkiе-файлов Сооkiе-файлы имеют ряд свойств, таких как срок жизни, путь и домен , кото­ рыми можно управлять. В действительности модуль http . cco kies предоставляет 17 18 19

https : / / docs . python . o r g / 3 . 5 / l ibrary/http . s e rve r . html https : / / t oo l s . i e t f . o r g /html / r f c 7 2 3 1 . html https : / / t oo l s . i e t f . o rg /html / r fc2 1 0 9 . html

797

объект Mor s e l , позволяющий сохранять куки и управлять всеми его атрибутами , определенными в RFС-документе. Листинг 1 2 .38. http_cookies Иorsel . py _ f r om h t t p import c o o k i e s import da te t ime

def show_cookie ( c ) : print ( c ) f o r key , mo r s e l i n c . i t ems ( ) : pr i n t ( ) p r i nt ( ' key = ' , mo r s e l . ke y ) p r i nt ( ' value = ' , mor s e l . va l ue ) print ( ' coded_va l ue = ' , mor s e l . coded_va lue ) for name in mo r s e l . keys ( ) : i f mo r s e l [ name ] : { } = { } ' . fo rmat ( name , mor se l [ name ] ) ) p r i nt ( '

с = coo k i e s . S imp l e C o o ki e ( ) # Куки , значение которого должно быт ь за кодировано для # того , чтобы поме стить е г о в заголов о к c [ ' encoded_va l ue_co o ki e ' ] = ' " co o ki e , v a lue ; " ' c [ ' encoded_va l ue_cookie ' ] [ ' commen t ' ] ' На з e s caped punc t ua t i on ' =

# Куки , который приме ним т ол ь ко к части сайта c [ ' re s t r icted_coo ki e ' ] = ' co o k i e_v a l ue ' c [ ' re s t r i cted_co o k i e ' ] [ ' pa th ' ] = ' / suЬ/path ' ' PyMOTW ' c [ ' re s t r i c t ed_coo kie ' ] [ ' doma i n ' ] c [ ' re s t r i ct ed_co o k i e ' ] [ ' s e cure ' ] = T rue # Куки , срок действия которого истекае т чере з 5 минут c [ ' w i th_max_a ge ' ] = ' exp i r e s in 5 minut e s ' c [ ' wi t h_max_age ' ] [ ' ma x-age ' ] = 3 0 0 # s e conds # Куки , срок действия которого истекае т к ука занному време ни c [ ' e xp i re s at t ime ' ] = ' co o k i e value ' t ime_t o_l i v e � da t e t ime . t imede lt a ( hour s= l ) exp i r e s ( datet ime . da t e t ime ( 2 0 0 9 , 2 , 1 4 , 1 8 , 3 0 , 1 4 ) + t ime_t o_l ive ) =

# Ф ормат даты : Wdy , DD-Mon-YY НН : ММ : S S GМТ exp i r e s_at_t ime = exp i re s . s t r f t ime ( ' % a , % d %Ь %У % H : % M : % S ' ) c [ ' expi re s_at_t ime ' ] [ ' exp i r e s ' ] = expire s_at_t ime show_coo ki e ( c )

Этот пример включает два метода настройки куки с ограниченным сроком действия. Один из них устанавливает максимальный срок действия куки в секун­ дах, другой определяет точные дату и время истечения срока действия куки.

798

$

Г/18811 12. Интернет

python3 h t tp_co o k i e s_Mo r s e l . py

S e t -Cookie : e ncoded_va l ue_c o o ki e = " \ " co o k i e \ 0 5 4 va l ue \ 0 7 3 \ " " ; C omrnent=Has e s caped punctuat i on S e t -Cooki e : expi re s_a t_t ime=coo k i e_va l u e ; exp i r e s =S a t , 1 4 Feb 2 009 19 : 30 : 14 S e t-Cooki e : r e s t r i c t e d_coo k i e=coo kie_va lue ; Doma i n= PyMOTW ; Path= / s uЬ/pat h ; S e cure S e t -Cooki e : w i t h_max_age= " expi r e s i n 5 minut e s " ; Max-Age = 3 0 0 ke y = w i t h _ma x_age va lue expi r e s in 5 minut e s coded_va lue " e xp i r e s i n 5 minut e s " max - age = 3 0 0 =

=

key

=

key

r e s t r i c t e d coo k i e value cooki e va l ue coded value = cookie va l ue doma i n = PyMOTW path / s uЬ/path s e cure = T rue

expi re s_at_t ime value = cookie va lue coded value = coo kie va lue expi r e s = S a t , 14 Feb 2 0 0 9 1 9 : 3 0 : 1 4

=

=

=

ke y

e ncoded value coo kie va lue = " co o k i e , va lue ; " coded_va lue " \ " cooki e \ 0 5 4 va l ue \ 0 7 3 \ " " comrne n t Has e s cape d punctua t i on

=

=

=

Оба объекта, Cookie и Mo r s e l , работают подобно словарям. Объект Mor s e l со­ держит фиксированный набор ключей: • expi res • path • comment • doma in • max-age • secure • ve r s ion Ключами экземпляра Cookie являются имена отдельных сохраняемых сооkiе-файлов. Эта информация также доступна из атрибуrа key объекта Mor se 1 . 1 2 .6.3.

Кодированные значения

Чтобы значения для заголовков куки могли быть корректно проанализирова­ ны, они должны кодироваться.

12.6. http.cookles: сооklе-феААы НПР

799

Листинг 1 2. 39. http_cookies coded_value . ру _

from http import coo k i e s

с = coo k i e s . S imp l e C o o ki e ( ) с [ ' integer ' ] = 5 c [ ' with_quo t e s ' ] = ' Н е s a i d , 11 He l l o , Wor l d ! 11 1 fo r name i n [ ' i n t e g e r ' , ' wi t h_quotes ' ] : p r i n t ( c [ name ] . key ) { } ' . format { с [ name ] ) ) pr i n t ( ' print ( ' value= { ! r } ' . forma t ( c [ name ] . va l ue ) ) print ( ' coded-value= { ! r } ' . forma t ( c [ name ] . coded-value ) ) print ( )

В атрибуге Mo r s e l . va lue всегда хранится декодированное значение куки, тогда как в атрибуrе Mor se l . coded_va lue всегда хранится его представление, используемое для передачи клиенту. Оба значения всегда являются строками. Значения, сохраняемые в куки, которые не являются строками, автоматически преобразуются в строки. $ pythonЗ http_co o k i e s_coded_va l ue . py integer Set -Cooki e : i n teg e r= 5 value= ' 5 ' coded_va l ue= ' 5 ' wi th quo t e s S e t - Cooki e : wi t h_quot e s = 11 He s a i d \ 0 5 4 \ 1 1 He l l o \ 0 5 4 Wo r l d ! \ 11 11 va l ue= ' He s a i d , 1 1 He l l o , World ! 1 1 1 coded_va l ue= "' He s a id \ \ 0 5 4 \ \ 11 He l l o \ \ 0 5 4 Wo r l d ! \ \ 1 1 11 1 __

1 2.6.4.

Получение и анализ заголовков сооkiе-файлов

Как только клиент получает заголовки Set -Cookie, он отправляет их обрат· но серверу вместе с последующими запросами, используя заголовок Cookie. Входящая строка за1·оловка Cookie может содержать несколько значений куки , разделенных точками с запятой ( ; ) . C o o k i e : i n t e g e r= 5 ; wi th_ quot e s= " He s a i d ,

\ " He l l o , Wor l d ! \ 11 11

В зависимости от веб-сервера и фреймворка объекты cookie можно получить непосредственно из заголовка или из переменной среды Н Т Т Р _COOKIE. Листинг 1 2 .40. http_cookies_Parse . py from http import coo k i e s

HTT P_COOK I E = ' ; ' . j o in ( [ r ' i n t e g e r=5 ' ,

800 1)

Гмва 12. Инrернет r ' wi th_quo t e s= " He s a i d , \ " He l l o , W o r l d ! \ " " ' ,

p r i nt ( ' Frorn cons t ruct o r : ' ) с = cooki e s . S imp l e C o o ki e ( HTTP_COOK I E ) p r i nt ( c ) print ( ) print ( ' Frorn l oad ( ) : ' ) с = cookie s . Simp l e C o o k i e ( ) c . l oad ( HTT P_COOKI E ) p r i nt ( c )

Чтобы декодировать объекты coo k i e , следует передать строку без префикса заголовка конструктору объекта S impleCookie при его инстанциализации или ис­ пользовать метод load ( ) .

$

pythonЗ http_co o k i e s_pa rse . py

Frorn const ruct o r : S e t -C o o ki e : i nteger=5 S e t -Cookie : wi t h_qu ot e s = " He said,

\ " He l l o , W o r l d ! \ " "

Frorn l oad ( ) : S e t -Cookie : integer=5 S e t - C o o k i e : wi th_quo t e s = " He said,

\ " He l l o , w o r l d ! \ " "

1 2.6.5. Альтернативные выходные форматы Помимо использования заголовка Set-Cookie серверы могут доставлять сооkiе-файлы клиенту с помощью сценариев JavaScript. Классы S imp l eCookie и M o r s el обеспечивают представление выходной информации в виде JavaScript­ дoкyмeнтa с помощью метода j s output ( ) . _

Листинг 1 2.41 . http_cookie s_j s_output . py f r orn http irnport c o o k i e s irnport t extwrap

с = cookie s . Sirnp l e C o o k i e ( ) c [ ' rnyco o k i e ' J ' co o ki e_va l u e ' c [ ' another_coo ki e ' ] = ' s e cond va l ue ' j s_text = c . j s_output ( ) p r i n t ( textwrap . dedent ( j s_t e x t ) . l s t r ip ( ) ) =

Результат представляет собой полноценный дескриптор script, содержащий инструкции для установки сооkiе-файлов.

$

pythonЗ ht tp_coo k i e s_j s_ou tput . py

< s cript t ype= " text / j ava s c r i pt " > < ! - - be g i n h i d i ng

12.7. weЬbrowser: оrображенме ве6-странмц :locument . coo kie

=

1 1 end h i d i ng - - >

801

" another c o o k i e = \ " s econd value \ '"' ;

< / s c r ipt > < s c r i p t t ype= " t ext / j ava s cript " > < ! - - beg i n h i d i ng :locurnent . co o k i e " myco o k i e = c o o k i e_v a l ue " ; 1 1 end h i d i n g - - > < /script> =

Дополнительные ссылки •

Раздел документации стандартной библиотеки, посвященный модулю http . coo ki e s 20 • h t t p . c o o ki e j a r . Модуль, обеспечивающий работу с сооkiе-файлами на стороне клиента.



RFC 2 1 og2 1 • НТТР Stote Monogement Mechonism.



1 2 . 7 . webbrowser: отображение веб-страниц Модуль webbrow ser включает функции, позволяющие открывать веб-докумен­ ты в интерактивных браузерных приложениях. Он предоставляет реестр доступ­ ных браузеров, если в системе имеется несколько вариантов. Кроме того, браузе­ ры можно контролировать с помощью перемен ной среды BROWSER.

1 2. 7. 1 . Простой пример Чтобы открыть страницу в браузере, следует использовать функцию open ( ) .

Листинг 1 2.42. webbrowser_open . ру irnpo r t webbrow s e r webb r owse r . open ( ' https : / /docs . python . org / 3 / l ibr a r y /webb r ows e r . html '

Документ, расположенный по указанному URL-aдpecy, открывается в окне бра­ узера, и это окно выводится на передний план. В документации говорится о том , что п о возможности используется открытое окно, н о фактическое поведение за· висит от настроек браузера. В случае использования браузера 1''irefox в MacOS Х всегда создается новое окно.

1 2. 7 2 Окна и вкладки .

.

Если необходимо, чтобы всегда открывалось новое окно, следует использовать функцию open new ( ) . _

20 21

https : / /docs . python . o r g / 3 . 5 / l i brary/http . co o k i e s . h trnl https : / / t o o l s . i e t f . org /htrnl / r f c2 1 0 9 . html

802

ГА888 12. Интернет

Листинг 1 2.43. weЬЬrowser_open new . ру _

import webbrow s e r webbrows e r . open new ( ' ht t p s : / /doc s . python . o r g / 3 / l ibra r y/webbrows e r . html '

Если вместо этого желательно открывать новую вкладку, то необходимо ис­ пользовать функцию open ne w tab ( ) . _

_

1 2. 7 .3. Использование конкретного браузера Если по каким-либо причинам приложению необходимо использовать кон­ кретный браузер, то можно получить доступ к набору зарегистрированных брау­ зерпых контроллеров, используя функцию get ( ) . Браузсрный контроллер имеет методы open ( ) , open ne w ( ) и open new tab ( ) . В следующем примере принуди­ тельно используется браузер Ly11x. _

_

_

Листинг 1 2.44. weЬЬrowser_get . ру import webbrow s e r Ь = webbrows e r . get ( ' l ynx ' ) b . ope n ( ' ht tps : / /docs . python . o r g / 3 / l i b r a r y /webbrows e r . html ' )

Список доступных типов браузеров приведен в документации модуля.

1 2 .7.4. Переменная

BROWSER

Пол1.зователи могут управлять модулем webbrowser извне приложения посред­ ством указания в переменной среды BROWSER имен браузеров или команд, которые можно пытаться использовать. Значение этой переменной должно включать по­ следошt·rельносгь имен браузеров, разделенных символом o s . pa thsep. Ji.сли имя включает спецификатор % s , то оно интерпретируется как литеральная команда, в которой данный спецификатор заменяется URL-aдpecoм. В противном случае имя передается функции get ( ) для получения объекта контроллера из реестра. Например, следующая команда открывает веб-страницу в браузере lупх, при ус­ ловии , что она доступна, независимо от того, какие браузеры зарегистрированы.

$

BROWS ER= l ynx python3 webbrowse r_ope n . py

Если ни одно из имен, указанных в переменной BROWSER, не работает, то вос­ станавливается поведение браузера, используемое по умолчанию.

1 2.7.5. Интерфейс командной строки Все средства модуля webbrowser доступны как из программ на яаыке Pytlюn, так и из командной строки.

$

python3 -m webbrow s e r

U s a ge :

. . . / l iЬ /pytho n 3 . S /webbrows e r . py [ - n 1 - t ] u r l

12.8. uuld: универаw.ные уникмыtые ИАеН1ИФМ1С81Оры

803

-n : open new window - t : open new t ab Дополнительные ссылки • •

Раздел документации стандартной библиотеки, посвященный модулю webbrow s e r 22 • wha t t hewh a t 23 • Выполняет предоставленную программу на языке Pyt hoп и в случае необходимости запускает поисковый механизм Google для поиска любых возможных сообщений об ошибках.

1 2.8. uu id: универсальные уникальные

идентификаторы Модуль uuid реализует стандарт идентификации UUID ( U пi ve rs a lly Uнiчue Ideпtifiers) , определенный в документе RFC 4 1 22 �!1 . В этом документе описана процедура создания уникальных идентификаторов ресурсов, не требующих ис­ пользования централизованной системы регистрации. Значения lJUIП представ­ ляют собой 1 28-битовые номера, которые, как ска:Jано в справочном руковод b ' 3 л \ xa2 \ x 8 2 \xcd\xed\ x l l \xe 6 \x 9e \xde \ xc 8 * \x 1 4 Y \ x 8 8 u ' byt e s hex 3 3 5 e a 2 8 2 cde d l l e 6 9edec8 2 a 1 4 5 9 8 8 7 5 68 2 8 1 99 9 8 0 3 4 8 092 8 7 07 2 0 2 1 52 6 7 0 6 950 9 8 4 8 5 int urn urn : uu i d : 3 3 5 e a 2 8 2 - cded- l l e 6 - 9ede - c 8 2 a 1 4 5 9 8 8 7 5 v a r i a nt spe c i f i ed i n RFC 4 1 2 2 1 ve r s i on f i e lds ( 8 61 8 4 0002 , 527 17 , 4 58 2 , 158 , 2 2 2 , 22 008 305559358 9 ) t irne l ow 8 6184 0002 52717 t irne rnid

12.В. uuld: унинрс8/\Wlwе уни1СМЬН1111е ИА8Н'1М... IС810РЫ t irne_hi_ve r s i o n c l o c k_s eq_h i_va r i ant : cl o c k_s eq_l ow node t ime c l o c k_s eq

805

4 582 158 222 220083055593589 1 3 7 0 2 3 2 5 7 3 3 4 1 62 0 5 0 7 902

Из-за наличия временной составляющей каждый вызов функции uuidl ( ) воз­ вращает новое значение. Листинг 1 2.47. uuid_uuidl_repeat . py import uuid f o r i i n range ( 3 ) : p r i n t ( uu i d . uui d l ( ) )

В этом выводе изменяется лишь временная составляющая (начальная часть строки) . $

pyt hon3 uu id_uu idl_repe a t . py

3 3 6 9ab5c - cded- l l e 6 - 8 d5e - c 8 2 a 1 4 5 9 8 8 7 5 3 3 6e e a 2 2 - cded- l l e 6 - 9 9 4 3 - c 8 2 a 1 4 5 9 8 8 7 5 3 3 6eeb5e - cded- l l e 6 - 9 e 2 2 - c 8 2 a 1 4 5 9 8 8 7 5

Поскольку МАС-адреса у всех компьютеров ра:�ные, результаты, полученные для других систем , будут совершенно другими. В следук>щем примере выполнение этого же кода на других хостах имитируется посредject Notatioп -JSON). Преимуществом форматаJSОN 110 сравнению с модулем p i c kl e является то, что он реализован во многих языках программи· рования (включая JavaScript). Чаще всего формат JSON используется для обмена данными между веб-сервером и клиентом в REST API, но также может быть поле­ зен для этих целей и в друrих приложениях.

1 2 .9. 1 .

Кодирование и декодирование простых типов данных

Кодировщик распознает встроенные типы Руtlюп (т.е., s t r , in t, float, l i s t , tuple и di c t ) п о умолчанию. Листинг 1 2.53. j son_simple_type s . ру import j s on da ta [ { ' а ' : ' А' , ' Ь ' : ( 2 , 4 ) , p r i n t ( ' DATA : ' , rep r ( da t a ) ) =

'с' :

3. О} ]

d a t a_s t ring j son . dumps ( dat a ) p r i nt ( ' JS ON : ' , da t a_ s t r i n g ) =

Способ преобразования значений в процессе кодирования внешне напомина­ ет тот, который используется при выводе данных с помощью функции repr ( ) в Руtlюп.

$

pytho n 3 j son_s impl e_type s . py

DAT A : JS ON :

[ { ' с ' : 3 . 0, [ { "с" : 3 . 0,

' а ' : 'А' , ' Ь ' : " а " : "А" , " Ь " :

(2, 4) } ] (2, 4] } ]

Типы объектов, восстановленных в результате декодирования , могут не совпа­ дать с исходными.

25 26

https : / / docs . python . o rg / 3 . 5 / l i brary/uu i d . html h t t p s : / / t o o l s . i e t f . org/html / r f c 4 1 2 2 . html

ГА888 12. ИН18РН8Т

810 Листинг 1 2. 54. j son_s imple_ types_decode . py import j s o n data [ ( 'а' : p r i nt ( ' DATA =

'А' , 'Ь' : : ' , dat a )

(2,

4) ,

'с' : 3.0} ]

j s on . dumps ( da t a ) data_s t r i ng p r i nt ( ' ENCODED : ' , da t a_s t r i ng ) decoded = j s on . l oads ( da t a_s t r i ng ) p r i nt ( ' DECODED : ' , decode d ) p r i nt ( ' OR I G I NAL : ' , t ype ( da t a [ О ] [ ' Ь ' ] ) ) p r i nt ( ' DECODED : ' , t ype ( decoded [ О ] [ ' Ь ' ] ) )

В частности, кортежи преобразуются в списки. $ pythonЗ j s on_s impl e_t ype s_de code . py DATA ENCODED : DECODED : OR I G I NAL : DECODED :

[ ( 'Ь' : [ ( " Ь" : [ ( 'Ь' :

INSTANCE ARGS : { ' s ' : ' i n s t ance va lue g o e s h e re ' } [ ]

1 2.9.6.

Работа с потоками и файлами

До сих пор предполагалось, что преобразованная структура данных могла це­ ликом храниться в памяти. В случае крупн ых структур более предпочтительной может оказаться запись результатов преобразования непосредственно в файло­ вый объект. Функции load ( ) и durnp ( ) получают ссылки на подобные файловые объекты, используемые как для чтения , так и для записи данных. Листинг 1 2.65. j son_dump_file . ру import i o import j s on data = [ { ' а ' :

' А' ,

'Ь' :

(2, 4) ,

'с' : 3.0}]

f io . StringIO ( ) j s on . dump ( da t a , f ) =

print ( f . getva l ue ( ) )

Дескриптор сокета или обычного файла работал бы точно так же, как и буфер S t r ing I O в этом примере. $

python3 j s on_dump_f i l e . py

[ { " с" : 3 . 0 , "Ь" :

[ 2 , 4 ] , " а " : "А" } ]

1 lесмотря на то что функция l oad ( ) не оптимизирована для чтения данных по частям, она позволяет инкапсулировать логику генерирования объектов из вход­ но1·0 потока.

Листинг 1 2.бб. j s on_load_file . ру import i o impor t j s on f = io . StringIO ( ' [ { " a " : "А" , " с" : 3 . 0 , p r i n t ( j s o n . l oad ( f ) )

"Ь" :

(2, 4] ) ] ' )

Как и функция dump ( } , функция load ( ) получает в качестве аргумента любой файловый объект. $

python 3 j s on_load_f i l e . py

[ { ' с' : 3 . 0,

'Ь' :

(2, 4) ,

'а' :

'А' } ]

12.9. jlon: .J8VllScrlpt OЬject Notвtlon

811

1 2 .9.7. Смешанные потоки данных Класс JSONDecoder включает метод raw_decode ( ) , позволяющий декодиро­ вать структуру данных, за которой следуют другие данные, как в случае данных JSON с замыкающим текстом. Дан ная функция возвращает объект, созданный в результате декодирования входных данных, и индекс позиции, в которой закан­ чивается декодированный объект. Листинг 1 2.67. j son_llli xed_da ta . py import j s on de code r

j s on . JSONDecode r ( )

=

de f g e t de coded and rema inde r ( i nput d a t a ) : de code r . raw_decode ( input_da t a ) о ь j : end rema i n i ng = i nput_dat a [ end : ] return ( ob j , end, rema in i ng ) =

encoded_ob j ect ' [ { " а " : "А" , " с " : 3 . О , ext ra text ' Th i s text i s not JSON . ' =

"Ь" :

[2, 4] ) ] '

=

p r i n t ( ' JS ON f i r s t : ' ) data ' ' . j o i n ( [ encoded obj ect , extra text ] ) obj , end, rema i n i n g get_de code d_a nd_rema i nde r ( da t a ) =

p r i n t ( ' Ob j ect p r i n t ( ' End of p a r s e d i nput p r i n t ( ' Rema i n i ng t e xt

• .

1



1



1

.

.

obj ) end ) ' repr ( rema i n i ng ) ) '

'

print ( ) p r i n t ( ' JSON emЬedded : ' ) t ry : dat a ' ' . j o i n ( [ e xt ra text , encoded ob j e c t , extra t e xt ] ) obj , end , rema i n ing ge t_de coded_and_r ema i nde r ( data ) except ValueError as e r r : print ( ' ERROR : ' , e r r ) =

=

К сожалению, этот подход работает лишь в тех случаях, когда объект встреча­ ется в начале входных данных.

$

python3 j s on_mixed_da t a . py

JSON f i r s t : Ob j e ct [ { ' с' : 3 . 0, ' Ь ' : [2, 4 ] , ' а ' : 35 End of p a r s e d i nput Rema i n i ng text ' This t e x t i s not JSON . ' JS ON emЬedded ERROR : Expect ing value : l ine 1 column 1 ( char 0 )

'А' ) ]

820

Гмва 12. Интернет

1 2.9.8. JSON и командная строка Модуль j s on . tool реализует утилиту командной строки, позволяющую пере­ форматировать данные JSON в более удобочитаемый формат. [ ( " а " : "А" ,

"с" :

3 . 0,

"Ь" :

[2, 4] } ]

Входной файл example.json содержит словарь. порядок следования ключей кото­ рого отличается от алфавитного. В первом примере представлены данные, пере­ форматированные в том же порядке, тогда как во втором примере ключи отобра­ жения сортируются с помощью параметра - - sort - keys , прежде чем выводиться на консоль. $ python3 -rn j s o n . t o o l ex arnp l e . j s on

"а" : "с" : " Ь" :

$ python3

-rn

1 1 А 11 , 3 . О, [ 2, 4

j s o n . t o o l - - s o r t - keys ex amp l e . j s o n

[ 11 а 11 : " А " , " Ь" : [ 2, 4 ], "с" : 3 . О

Дополнительные ссылки • •





27 28 29

Раздел документации стандартной библиотеки, посвященный модулю j s o n 27 • Замечания относительно портирования программ из Python 2 в Python 3, касающиеся модуля j s o n (раздел А.б.23). lntгoducing JSON28• Домашняя страница сайта JSO N, содержащая документацию JS ON и ссылки на реализации в других языках программир ования. j s onpi ck l e 29 • Модуль, обеспечивающий сериализацию любого объекта Python в фор­ мат JSO N. https : / /docs . python . o r g / 3 . 5 / l ibrar y / j s on . html h t tp : / / j s o n . o r g / https : / / j s o np i c kl e . g i thub . i o

12.10. xrnlrpc.cllent: миент XМL-RPC

821

1 2 . 1 0 . xmlrpc . cl ient: клиент XML-RPC XML-RPC ( ExtensiЫe Markup Laпg11age Reшote Procedure Call - ХМl,-вызов удаленных процедур) - это упрощенlfый протокол вызова удаленных процедур, построенный поверх н1·rр и ХМL. Модуль xml rpcl ib позволяет программам на языке Руtlюп взаимодействовать с сервером XMI.-RPC, I1аписа1111ым на любом языке программирования. Во всех примерах, приведенных в этом разделе, используется сервер, кото­ рый определен в файле xmlrpc_ ser ver . py, доступном в исходном дистрибутиве Pytl1011 и представленном ниже для справки. Листинr 1 2.68. xmlrpc_server . ру f r om xml rpc . s e rver impor t S impl eXMLRPCServer f r om xml rpc . c l i e nt import Binary import da t e t ime c l a s s Exampl e S e rvice : de f ping ( s e l f ) : '""' П ростая функция , срабатыв ающа я при выэове для демонстрации подключения . 11 11 11

r e t u r n T rue de f n ow ( s e l f ) : " " " в о звращае т те кущие да ту и время сервера . " " " return dat e t ime . da t e t ime . now ( ) de f show_t ype ( s e l f , arg ) : " " " Иллюс трируе т передачу типов в серверные ме т оды . Получает один аргумен т любого типа . Воэвраща е т кортеж со строковым представлением э наче ния , типа и само э наче ние .

имя 11 11 11

return ( s t r ( a r g ) , s t r ( t ype ( a rg ) ) , arg ) de f rai s e s_except i o n ( s e l f , ms g ) : " Все гда в оэбужда е т исключе ние Run t i me E r r o r с переда нным сообще нием " ra i s e Runt i me E r r o r ( ms g ) d e f send_ba c k_binary ( s e l f , b i n ) : " " " Получае т один двоичный аргуме н т , который распаковыв ается и перепаковыв ается дл я воэврата . " " " data Ь i n . da t a print ( ' s end_back_Ь i n a ry ( { ! r } ) ' . fo rmat ( da t a ) ) response B i n a ry ( da t a ) return response =

=

822 if

rмва 12. Интернет ' -ma in- ' · S impl eXMLRPCServe r ( ( ' l o ca lhos t ' , 9 0 0 0 ) , l ogReque s t s=True , a l low_none=True ) s e rver . re g i s t er_i n t r o spect i o n_fun c t i on s ( ) s erve r . regi s t e r_mu l t i ca l l_funct i on s ( ) name s e rve r

==

=

s e rve r . re g i s t e r_ins t ance ( Examp l e S e rvi ce ( ) ) try : p r i n t ( ' Us e Cont r o l -C to e x i t ' ) s e rver . s e r ve_foreve r ( ) except Keyboa rdinte rrupt : p r i n t ( ' Ex i t i ng ' )

1 2 . 1 0. 1 .

Подключение к серверу

Простейшим способом подключения клиента к серверу является создание объ­ екта Server Proxy с передачей его конструктору URI сервера в качестве аргумен­ та. Так, в приведенном ниже примере демо11страцио11ный сервер выполняется с использованием имени loca lhos t и порта 9 0 0 0 . Листинг 1 2.69. xmlrpc ServerProxy . ру _

import xml rpc . c l i e nt server xml rpc . c l i e nt . Serve r Proxy ( ' ht tp : / / l ocalho s t : 9 0 0 0 ' ) print ( ' Ping : ' , s e rver . p ing ( ) ) =

В данном случае метод ping ( ) службы не имеет аргументов и возвращает оди­ ночное булево значение.

$

pythonЗ xml rpc_Serve r Proxy . py

Ping : True

Также доступны другие опции, поддерживающие альтернативные транспорт­ ные протоколы для взаимодействия с серверами. Готовая поддержка протоколов НТТР и НТТРS предоставляется прямо "из коробки" вместе со средствами базо­ вой аутентификации. Чтобы реали:ювать новый коммуникационный канал, тре­ буется всего лишь создать новый транспортный класс. Например, в качестве ин­ тересного упражнения можно предложить реализацию XML-RPC поверх SMTP. Листинг 1 2. 70. xml rpc ServerProxy_verbose . ру _

import xml rpc . cl i e n t se rve r

xml rpc . c l i ent . S e rverProxy ( ' ht tp : / / l oca lho s t : 9 0 0 0 ' , ve rbo se=True ) pri n t ( ' Pi n g : ' , s e rve r . ping ( ) ) =

Опция verbo se задает генерирование отладочной информации, которая мо­ жет быть полезной для поиска причин ошибок, возникающих при взаимодей­ ствии с сервером.

12.10. xmlrpc.cllent: КАИент XМL-RPC

$

823

pyt honЗ xml rpc_S e r v e r Proxy_ve rbo s e . py

s e nd : b ' POST /RPC2 HTT P/ 1 . 1 \ r \ nHos t : l oc a l h o s t : 9 0 0 0 \ r \ n Accept -Encoding : g z i p \ r \ nContent -Type : t e x t / xml \ r \n U s e r -Agent : Python-xml rp c / 3 . 5 \ r \ nContent -Length : 9 8 \ r \ n \ r \ n ' s e nd : b " < ?xml ve r s i on= ' l . 0 ' ? > \ n \ n p i ng< /methodName > \n\n< /params > \ n < /met hodCal l > \ n " repl y : ' НТТ Р / 1 . 0 2 0 0 OK\ r \ n ' heade r : S e r v e r heade r : Date heade r : Con t e n t - t ype heade r : Cont e n t - l ength body : b " < ? xml ve r s i on= ' l . 0 ' ? > \ n \ n \ n \n l < /boo l e an>< / va l u e > \ n < / p a ram> \n< /params > \ n< /me t hodRe spon s e > \ n " Ping : T rue

Вместо используемой по умол " , { ' а ' : 1 , ' Ь ' : ' Ь g oe s here ' } ] о 2 : MyObj ( 2 , MyOb j ( 1 , ' Ь g o e s her e ' ) ) [ " { ' Ь ' : { ' Ь ' : ' Ь goes here ' , ' а ' : 1 } , ' а ' : 2 } " , " < c l a s s ' di ct ' > " , { ' а ' : 2 , ' Ь ' : { ' а ' : 1 , ' Ь ' : ' Ь goe s h e re ' } } ]

1 2 . 1 0.4. Двоичные данные Все значения, передаваемые серверу, автоматически кодируются с использо­ ванием Еsсаре-последовательностей в необходимых случаях. Однако некоторые типы данных могут содержать символы , не являющиеся действительными симво­ лами XML. Например, двоичные данные изображений могут включать байтовые значения из диапазона 0-3 1 таблицы ЛSСП , который соответствует управляю­ щим символам. Для передачи двоичных данн ых лучше всего использовать класс Binary, обеспечивающий кодирование данных в процессе их передачи. Листинг 1 2.77. xml rpc_Binary . ру impo rt xrnl rpc . c l i e nt irnport xrnl . pa r s e r s . expat s e rver

=

xrnl rpc . cl i ent . Server Pr oxy ( ' ht tp : / / l oca l ho s t : 9 0 0 0 ' )

s b ' Th i s i s а s t ring with con t r o l cha r a c t e r s \ x O O ' p r i n t ( ' Loca l s t r i ng : ' , s ) =

data xrnl rpc . c l i ent . Bi nary ( s ) r e sponse s e rve r . s end_back_b i n a r y ( da t a ) p r i n t ( ' As Ь i na r y : ' , respons e . da t a ) =

=

try : print ( ' As s t r i ng : ' , s e r ve r . show t ype ( s ) ) except xrnl . pa r s e r s . expat . Expa tError a s e r r : p r i n t ( ' \nERROR : ' , e r r )

Если функции show_t уре ( ) передается строка, содержащая байт NULL, то в процесс обработки ответа ХМl,-анализатором возбуждается исключение. $

python З xrnl rpc_Binary . py

Local s t r i ng : b ' Th i s i s а s t r i n g w i t h cont r o l cha racte r s \ x D D ' As b i na ry : b ' T h i s i s а s t r i n g wi th con t r o l cha racte r s \ x D O ' ERROR : not we l l - forrned ( i nva l i d token ) : l i n e 6 , colurnn 5 5

12.10. xmlrpc.c:llent

ммент XМL-RPC

821

Объекты Binary могут также использоваться для передачи объектов средства­ ми модуля p i c kl e ( раздел 7. 1 ) . В подобных ситуациях не следует забывать о воз­ можных угрозах безопасности, связанных с передачей по сети исполняемого кода (т.е. не следует использовать эту возможность, если нет уверенности в безопасно­ сти канала передачи данных) . import xml rpc . cl i ent import pi c kl e import ppr i n t c l a s s MyObj : de f _i n i t_ ( s e l f , а , Ы : self . a а Ь se l f . b =

de f _repr_ ( s e l f ) : re t u r n ' MyObj ( { ! r } , s e rver

=

{ ! r } ) ' . f o rma t ( s e l f . а , s e l f . Ь )

xml rpc . c l i ent . Se rverProxy ( ' ht tp : / / l ocalhost : 9 0 0 0 ' )

о = MyObj ( l , ' Ь g oe s here ' ) print ( ' Local : ' , id ( о ) ) print ( o ) p r i n t ( ' \ nAs obj e ct : ' ) ppr i n t . ppri n t ( s e r ve r . show_t ype ( o ) ) р Ь r

p i c kle . dumps ( o ) xml rpc . c l i ent . B i nary ( p ) s e r ve r . s end_b a c k_bi nary ( b )

=

p i ckle . l o ads ( r . dat a ) о2 p r i n t ( ' \ nFrom pi c kl e : ' , i d ( о 2 ) ) ppr i n t . ppr i n t ( o2 ) •

Атрибут data экземпляра Binary содержит версию объекта, сериализован­ ную с помощью модуля pickle, которая должна быть десериализована перед ее использованием. Резулы·атом этого является получение новою объекта (с другим значением ID) . $

python3 xml rpc_B i nary_p i c kl e . py

Loca l : 4 3 2 7 2 6 2 3 0 4 MyObj ( 1 , ' Ь g oes he re ' ) As obj ect : [ " { ' а ' : 1 , ' Ь ' : ' Ь goes here ' } " , " " , { ' а ' : 1 , ' Ь ' : ' Ь goe s here ' } J From p i c kl e : 4 3 2 7 2 6 2 4 7 2 MyObj ( l , ' Ь g oes h e re ' )

830

ГАа8а 12. Икrернет

1 2. 1 0. 5 . Обработка исключений Если учесть тот факт, что сервер XMl,-RPC может быть написан на любом язы­ ке программирования, то становится понятным, что классы исключений нельзя передавать непосредственно. Вместо этого исключения, возбужденные на серве­ ре, преобразуются в объекты Fau l t и возбуждаются как исключения локально на стороне клиента. Листинг 1 2.78. xml rpc_exception . py import xml rpc . c l i ent server xml rpc . c l i e nt . S e r ve r Proxy ( ' h t tp : / / l oc a l ho s t : 9 0 0 0 ' ) try: s e rver . r a i s e s_e xcept i on ( ' A me s s age ' ) except Except i o n a s e r r : p r i n t ( ' Fau l t code : ' , e r r . f a u l t Code ) p r i nt ( ' Me s sage : ' , e r r . faul t S t r i ng ) =

Исходное сообщение об ошибке сохраняется в атрибуl'е fau l t S t r i ng , тогда как в атрибуге faul tCode сохраняется строка с номером ошибки XMI,-RPC. $ pyt honЗ xml rpc_except i on . py Fau l t code : 1 Me s s a ge < c l a s s ' Runt ime E r r o r ' > : A me s s a ge

1 2. 1 0.6. Комбинирование вызовов в одном сообщении Multicall это расширение протокола XML-RPC, позволяющее объединить и отправить в виде одного запроса сразу несколько вызовов, ответы на которые со­ бираются и возвращаются клиенту. -

Листинг 1 2.79. xml rpc_МultiCall . py impo r t xml rpc . cl i e nt s e rver

=

xml rpc . c l i e nt . ServerP roxy ( ' ht tp : / / l oc a l ho s t : 9 0 0 0 ' )

mu l t i c a l l xml rpc . c l i e nt . Mu l t i C a l l ( s e r ve r ) mu l t i ca l l . p i ng ( ) mul t i c a l l . show_t ype ( l ) mu l t i ca l l . show_t ype ( ' s t r i ng ' ) =

for i , r i n enume r ate ( mu l t i ca l l ( ) ) : print ( i , r )

Чтобы использовать экземпляр Mul tiCa l l , следует вызвать те же методы, что и для экземпляра Server Proxy, а затем вызвать объект, не имеющий ар1"}'ментов, для фактического выполнения удаленных функций. Возвращаемым значением яв­ ляется итератор, который возвращает результаты всех вызовов.

12.10. xmlrpc.cllent: КАМенr XМL-RPC

831

$ pythonЗ xml rpc_Mu l t iCa l l . py О True 1 2

[ ' 1 ' , " < c l a s s ' i nt ' > " , 1 ] [ ' s t r i ng ' , " < c l a s s ' s t r ' > " ,

' s t r i ng ' ]

Если один из вызовов приводит к ошибке, то при получении результата из ите­ ратора возбуждается исключение, и больше никакие другие рс:iульт-аты не стано­ вятся доступными. Листинг 1 2.80. xmlrpc Мul tiCal l exception . ру _

_

import xml rpc . cl i e nt s e rve r

=

xml rpc . c l i ent . S e rver Proxy ( ' ht tp : / / loca lho s t : 9 0 0 0 ' )

::nul t i ca l l xml rpc . c l i e n t . Mul t i C a l l ( s e rve r ) .:nul t i ca l l . p i ng ( ) ::nul t i c a l l . show_t ype ( 1 ) :nul t i ca l l . r a i s e s except i on ( ' Next - t o - l a s t ca l l s t ops execut i on ' ) :nul t i ca l l . show_t ype ( ' s t r i ng ' ) =

t ry : f o r i , r i n enume rat e ( mu l t i ca l l ( ) ) : print ( i , r ) except xml rpc . cl i ent . Faul t a s err : p r i n t ( ' ERROR : ' , e rr )

ПоскоJ1ьку третий ответ, получаемый от функции r a i s e s_except ion ( ) , гснt."­ рирует исключение, ответ функции show type ( ) недоступен. _

$

pyt h o n З xml rpc_Mu l t i C a l l_except i on . py

О T rue [ ' 1 ' , " < c l a s s ' i nt ' > " , 1 ] ERROR : < Fau l t 1 : " < c l a s s ' Runt imeError ' > : Next - t o - l a s t c a l l s t ops execut i on " > 1

Дополнительные ссьUJки •

Раздел документации стандартной библиотеки, посвященный модулю 30 c l ient •

• • •

xml rpc .

(раздел 1 2. 1 1 ). Реализация сервера XML-RPC. (раздел 1 2.5). Реализация НПР-сервера. XML-RPC НОWТО 31 • Описано использование протокола XML-RPC для реализации кли­ ентов и серверов с помощью различных языков программирования. xml rpc . s e rver

http . se rver

30 https : / / do c s . python . o rg / 3 . 5 / l ibrary/xml rpc . cl i ent . html 31 www . t l dp . o r g / HOWTO/XМL-RPC-HOWTO / i ndex . html

832

ГА8118 12. Интернет

1 2 . 1 1 . xml rpc . server: сервер XML-RPC Модуль xmlrpc . s erver содержит классы, предназначенные для создания крос­ сплатформенных серверов XML-RPC, не зависящих от языка программирования. Клиентские библиотеки существуют для многих языков помимо Python , что дела­ ет протокол XMl,-RPC удобным для реализации RРС служб. -

Примечание

Все примеры, приведенные в этом разделе, включают клиентский модуль, который взаи­ модействует с демонстрационным сервером. Для выполнения примеров необходимо ис­ пользовать два окна: одно для сервера и одно для клиента.

1 2. 1 1 . 1 . Простой сервер Используемый ниже простой сервер предоставляет всего одну функцию, ко­ торая получает имя каталога и возвращает его содержимое. Сначала необходимо создать сервер и передать ему адрес, по которому следует прослушивать запросы (в данном случае - порт 9000 локального хосга) . Далее определяется функция, яв­ ляющаяся частью службы, которую необходимо зарегистрировать, чтобы сервер знал, как ее вызывать. Последний шаг заключается в организации бесконечного цикла для получения запросов и отправки ответов. Предупреждение

Данная реализация содержит очевидные уязвимости. Не используйте ее на общедоступ­ ном сервере в Интернете или в любой другой незащищенной среде. Листинг 1 2.81 . xmlrpc functi on . ру _

from xml rpc . s e rver import S i mp l e XMLRPC Se rver import l og g i ng impor t o s # Включить протоколирование операций l o g g i n g . ba s i cConfi g ( l eve l = l o gg i ng . I NFO ) s e rve r S impl eXMLRPCSe rve r ( ( ' l ocalhost ' , 9 0 0 0 ) , l ogReque s t s=True , =

# Предоставит ь функцию d e f l i s t_content s ( di r_name ) : l ogging . in f o ( ' l i s t_con t e n t s ( % s ) ' , d i r_name ) return o s . l i s t d i r ( di r_n ame )

s e rve r . r e g i s t e r_fun ct i on ( l i s t_cont ent s ) # Заnустит ь сервер try:

12.U. xmlrpc.вerver: сервер XМL-RPC

833

print ( ' Us e Cont r o l -C to e x i t ' ) s e rve r . s e rve_foreve r ( ) except Keyboa rdi n t e r rupt : p r i n t ( ' Exi t i n g ' )

ДОС'I)'П к серверу по URI.-aдpecy http : / / loca lhos t : 9 0 0 0 можно получить с помощью модуля xmlrpc . c l i ent (раздел 1 2. 1 0) . В приведенном ниже листинге клиентского кода продемонстрировано, как вызвать службу l i s t_content s ( ) из Python. Листи нг 1 2 .82. xmlrpc_function_cl ient . py imp o r t xml rpc . c l i ent proxy xml rpc . c l i ent . S e r ve r P r oxy ( ' ht tp : / / l ocalhost : 9 0 0 0 ' ) p r i n t ( proxy . l i s t_cont e n t s ( ' / tmp ' ) ) =

Сначала экземпляр ServerProxy соединяется с сервером, используя его базо­ вый URL-aдpec, после чего методы вызываются непосредственно для прокси-объ­ екта. Каждый метод, вызываемый для прокси, транслируется в запрос к серверу. Аргументы форматируются с использован ием XMI., а затем пересылаются на сер­ вер в составе РОSТ-сообщсния. Сервер распаковывает ХМL-формат и определя­ ет, какую функцию следует вызвать, на основании имени метода, активизируемо­ го клиентом. Аргументы передаются функции, а возвращаемое значение трансли­ руется обратно в XML и возвращается клиен'I)'. При запуске сервера выводится следующая информация. $

pythonЗ xml rpc_funct i on . py

U s e Cont r o l - C t o e x i t

Выполнение кл иента во втором окне приводит к отображению содержимого каталога / tmp. $

pythonЗ xml rpc_func t i on cl i ent . py

[ ' com . app le . l aunchd . aoGXon n B nV ' , ' com . appl e . l aunchd . i l ryi aQug f ' , ' example . db . db ' , ' KSOut 0 f P r oce s s Fe t che r . 5 0 1 . ppfihqXOvj aTSb8AJYobDV7 C u 6 8 = ' , ' pymotw_import_ex ampl e . she lve . db ' J

Пос.ле отправки ответа в окне сервера выводится информация о запросе. $ pythonЗ xml rpc_funct i on . py U s e Cont r o l - C t o exit INFO : root : l i s t content s ( /tmp ) 1 2 7 . 0 . 0 . 1 - - ( 1 8 / Jun /2 0 1 6 1 9 : 5 4 : 5 4 ]

" POST / R PC2 НТТ Р / 1 . 1 " 2 0 0 -

Первая строка вывода происходит от вызова функции l ogging . info ( ) , вы­ полняющегося в теле функции l i s t_conten t s ( ) . Вторая строка - это вывод про­ токолируемой информации о запросе, поскольку для флага l ogReque s t s было установлено значение True.

834

Гмва 12. И нтернет

1 2. 1 1 .2. Альтернативные имена API Иногда имена функций, исполыуемые в модуле или библиотеке, не совпадают с теми , которые должны использоваться во внешнем API. Необходимость в из­ менении имен может возникать по разным причинам : в силу специфических для платформы особенностей загруженной реализации , в силу того, что API службы создается динамически на основании конфигурационного файла, или потому, что реальные функции могли быть заменены заглушками в целях тестирования . Чтобы зарегистрировать функцию с альтернативным именем, следует передать это имя в качестве второго аргумента функции regi ste r_funct ion ( ) . Листинг 1 2.83. xml rpc_alternate_name . py f r orn xrnl rpc . s e rve r irnport Sirnp l eXMLRPCSe r ve r irnport o s s e rver

=

S irnp l e XMLRPCServer ( ( ' loca l h o s t ' , 9 0 0 0 ) )

de f l i s t_content s ( di r_narne ) : " Предоставляе т функ цию с а л ь терна тив ным име нем" return o s . l i s tdi r ( di r_narne ) s e rve r . regi s t e r_funct ion ( l i s t_con t e n t s , ' di r ' ) t ry : p r i n t ( ' Us e Con t r o l - C t o e x i t ' ) s e r ve r . s e rve_foreve r ( ) except Keybo a r d i n t e r rupt : p r i n t ( ' Ex i t i ng ' )

Клиент должен теперь использовать имя d i r ( ) вместо l i s t contents ( ) . _

Листинг 1 2.84. xmlrpc_al ternate_name_c l ient . py irnport xrnl rp c . cl i e nt p r oxy = xrnl rpc . c l i ent . S e r v e r Proxy ( ' h t tp : / / l ocalho s t : 9 0 0 0 ' ) p r i nt ( ' di r ( ) : ' , proxy . di r ( ' / trnp ' ) ) try: p r i n t ( ' \ n l i s t_conte nt s ( ) : ' , proxy . l i s t_con t e n t s ( ' /trnp ' ) ) except xrnl rpc . cl i ent . Fa u l t a s e r r : p r i n t ( ' \ nERROR : ' , e r r )

Вызов функции l i s t_content s ( ) приводит к ошибке, потому что теперь ее имя отсутствует в списке зарегистрированных функций. $

pythonЗ xrnl rpc_a l t e rnat e_narne_c l i e n t . py

di r ( ) : [ ' com . app l e . l aun chd . a oGXonn 8 nV ' , ' corn . appl e . l a unchd . i l ryi aQug f ' , ' exarnpl e . db . dЬ ' , ' KSOut OfProce s s Fe tche r . 5 0 1 . ppfihqX O v j aTSb 8AJYobDV 7 Cu 6 8 = ' , ' pyrnotw_impo r t_exarnp l e . she l ve . dЬ ' ] ERROR : < Faul t 1 : ' < c l a s s \ ' Except i o n \ ' > : rne thod " l i s t conten t s " i s not suppo r t e d ' >

12.U. xmlrpc.мrver: сервер XМL-RPC

835

1 2 . 1 1 . 3 . И мена API с точками Некоторые функции могут регистрироваться с именами, которые обычно являются недопустимыми идентификаторами в Python. Например, имена мо­ гут включать символы "точка" ( ) , разделяющие пространства имен в службе. Следующий пример расширяет службу "diгectory", добавляя в нее вызовы "сгеаtе" и "гешоvе". Все функции регистрируются с префиксом dir . , чтобы тот же сервер мог предоставлять другие услуги , используя другой префикс. Еще одним отличи­ ем является то, что некоторые из функций возвращают значение None, поэтому серверу необходимо сообщить, что такое значение должно транслироваться в ну­ левое значение. .

Листинг 1 2.85. xmlrpc_dotted_name . py f r om xml rpc . se rver import S imp l e XMLRPCServe r import o s s e r ve r = S impleXMLRPCServer ( ( ' l o calho s t ' ,

9 0 0 0 ) , a l l ow_none=True )

s e r ve r . re g i s t e r_funct i on ( os . l i s t d i r , ' di r . l i s t ' ) s e rve r . re g i s t e r_funct i on ( os . mkd i r , ' di r . create ' ) s e rver . re g i s t e r_funct i on ( o s . rmdi r , ' di r . remove ' ) t ry : p r i n t ( ' U se Contro l - C to e x i t ' ) s erve r . s e r ve_fore ve r ( ) exce pt Keyboa r d i n t e r rupt : p r i n t ( ' Ex i t ing ' )

Чтобы вызвать функцию службы , клиенту достаточно сослаться на ее имя с ис­ пользованием точечной нотации. Листинг 1 2.86. xmlrpc_dotted_name_client . py import xml rpc . c l i e n t proxy print ( print ( print ( print ( print (

xml rpc . c l i ent . Serve r Proxy ( ' ht tp : / / l oc a l h o s t : 9 0 0 0 ' ) ' BE FORE : ' , ' ЕХАМРLЕ ' in p roxy . di r . l i s t ( ' / tmp ' ) ) ' CREATE : ' , proxy . d i r . cre a t e ( ' / tmp /EXAМPLE ' ) ) ' SHOULD EX I ST : ' , ' ЕХАМРLЕ ' i n proxy . di r . l i s t ( ' / tmp ' ) ) ' REMOVE : ' , pr oxy . d i r . remove ( ' / tmp /EXAМPLE ' ) ) : ' , ' ЕХАМ РLЕ ' in p r oxy . di r . l i s t ( ' / tmp ' ) ) ' AFТER

=

В предположении, что в текущей системе файл / tmp/ EXAМPLE отсутствует, дан­ ный клиентский сценарий выведет следующую информацию. $ pythonЗ xml rpc_dot t e d_name_c l i e nt . py BE FORE CREATE S HOULD EXIST REMOVE AFТER

Fa l s e None T rue None Fa l se

Гмвв 12. Интернет

1 2. 1 1 .4. Произвольные имена API Еще одной интересной возможностью является регистрация функций с име­ нами, представляющими собой допустимые имена атрибутов объектов Pytlюn. В следующем примере служба регистрирует функцию с именем mul t ip l y args. Листинг 1 2.87. xml rpc_arЬi trary_name . ру f r orn xrnl rpc . s e rve r irnport S irnpl eXMLRPCServer s e rver

=

S irnp l eXMLRPCSe rve r ( ( ' l o c a l h o s t ' ,

9000 ) )

d e f rny_funct i on ( a , Ь ) : return а * Ь

s e rver . re g i s t e r_fun ct i on ( rny_func t i o n ,

' rnu l t i p l y args ' )

try: p r i n t ( ' Us e Cont r o l - C t o e x i t ' ) s e rver . s e rve_foreve r ( ) except Keybo a r d ! n t e rrupt : print ( ' Ex i t ing ' )

Поскольку зарегИ [ < f i l e narne s > ] Returns а l i s t con t a i n ing the con t e n t s o f t h e named d i r e c tory . "

, , "

r e turn o s . l i s t di r ( di r_name ) s e rver . reg i s t e r_ i n s t a nce ( DirectoryS e r v i ce ( ) ) t ry : p r i n t ( ' Us e Con t r o l - C t o e x i t ' ) se rver . se r ve_ f oreve r ( ) except Keyboa rdi n t e r rupt : p r i n t ( ' Ex i t i n g ' )

В данном случае функция l i s t_puЫ ic_methods ( ) выполняет поиск в про­ странстве имен экземпляра и возвращает вызываемые атрибуты, имена которых не начинаются с символа подчеркивания (_) . Чтобы применить требуемые прави· ла, следует переопределить метод _ l i s tMethods ( ) . Аналогичным образом в этом простом примере метод _methodHe lp ( ) возвращает строку документации функ· ции, но е1·0 можно переписать для создания строки справки из другого источника. Клиент посылает запрос серверу и выводит имена всех общедоступных методов. Листинг 1 2.96. xml rpo_introspeotion_olient . py import xml rpc . c l i ent proxy xml rpc . c l i ent . Se rve r P roxy ( ' ht t p : / / loca lho s t : 9 0 0 0 ' ) for me thod_name i n proxy . s ystem . l i s tMethods ( ) : =

842

Г11ава 12 . Интернет print pri n t print print print

( ' = ' * 60 ) ( me thod_n ame ) ( ' - ' * 60 ) ( proxy . s y s t em . methodHe lp ( me t hod_name ) ) ()

Результаты включают системные методы. $ python З xml rpc_i nt ro spect i on_c l i e nt . py

= • e--.• �e=�=8Ca'Ca:-:a:a:a:&::aC"a:: = =z=-=�z==== - --===ь--= -==:1'8

list l i s t ( di r_name ) => [ < f i l e name s > ] Returns а l i s t conta i n i n g the conte n t s o f t h e n amed di rec t o r y . s y s t em . l i s tMethods s y s t e m . l i s tMe thods ( ) = > [ ' a dd ' ,

' s ub t r act ' ,

' mul t i p l e ' ]

Returns а l i s t o f the me thods suppo r t ed Ьу the s e rver . s y s t em . me thodHe l p s y s t e m . me thodH e l p ( ' add ' ) => "Adds t w o intege r s togethe r " Re t u rns а s t ri n g cont a i n i ng documen t a t i on f o r the speci f i e d me thod . s y s t e m . me thodS i gnature s y s t em . met hodS i g n a ture ( ' add ' ) => [ douЫ e , i n t , i n t ] Returns а l i s t de s cr i bing the s i gn a t u r e of the me thod . I n the above examp l e , the add me thod t a ke s two i n t e g e r s a s ar guments and returns а douЫ e r e s u l t . Thi s s e rve r do e s NOT support s y s t em . me thodS i gnature . Дополнительные ссылки •

Раздел документации стандартной библиотеки, посвященный модулю 32 s e rve r •

• •

32 33

xml rpc .

xml rpc . c l i en t (раздел 1 2. 1 0). Клиент ХМL-RРС. XML-RPC НОWТО33 . Описание способов реализации клиентов и серверов, работаю­ щих по протоколу XML-RPC, с использованием различных языков программирования.

https : / / docs . python . o rg / 3 . 5 / l i b r a r y / xmlrpc . s e rve r . html www . t l dp . o r g / HOWTO/XML -RPC -HOWTO/ i ndex . html

Гл а ва 1 3 Эл е ктро н н а я п о чта Электронная почта - один из старейших, н о по-прежнему один и з наиболее популярных видов цифровой связи. Стандартная библиотека Pythoп включает модули, предназначенные для отправки, получения и храпения сообщений элек­ тронной почты. Модуль smtpl ib (раздел 1 3. l ) взаимодействует с сервером, обеспе•1ивая до­ ставку сообщений. Модуль smtpd (раздел 1 3.2) позволяет создавать пользователь­ ские почтовые серверы и предоставляет классы, которые могут быть полезными при отладке механизмов электронной почты из других приложений. Модуль imap l ib (раздел 1 3.4) использует протокол IМАР для манипулирова­ ния сообще11иями, хранящимися на сервере. Он предоставляt.-т низкоуровневый API для клиентов IМАР и обеспечивает возможности запроса, извлечения, пере­ мещения и удаления сообщений. Модуль mai lbox (раздел 1 3.3) позволяет создавать и изменять локальные архи­ вы сообщений с использованием нескольких стандартных форматов, в том числе таких популярных, как mЬох и Maildir, которые используются многими клиент­ скими программами электронной почты.

1 3 . 1 . smtpl ib :

клиент SMTP

Модуль smtpl ib включает класс SMTP, позволяющий связываться с почтовыми серверами для отправки электронных сообщений. Примечание

Несмотря на то что в приведенных ниже примерах используются фиктивные адреса элек­ тронной почты, имена хостов и IР-адреса, они адекватно отражают последовательность выполнения команд и получения ответов.

1 3. 1 . 1 . Отправка сообщений В типичных случаях протокол SMTP (Siшple Mail Traпsfer Protocol - простой протокол передачи почты) используется для подключения к почтовому серве­ ру и отправки сообщений. Имя хоста и номер порта почтового сервера можно передать либо непосредственно конструктору, либо явно вызванному мето­ ду connect ( ) . После установления соединения с сервером вызывается метод sendmai l ( ) с параметрами конверта и тела сообще11ия. Текст сообщения должен быть оформлен в полном соответствии с требованиями документа RFC 5322 1 , по­ скольку модуль smtpl ib вообще не изменяет содержимое или заголовки. Это оз­ начает, что поля From и То заголовка должны быть заполнены вызывающим кодом. 1

https : / / t oo l s . i e t f . o rg /html / r f c 5 3 2 2

844

Г11аве 13. ЭАекqюнная ПОЧ1'8

Листинr 1 3. 1 . smtplib_sendmail . ру import smtp l ib import ema i l . ut i l s f rom ema i l . mime . t ext import MIMETe xt # Созда ть сооб щение msg M I METext ( ' Th i s is the body of the me s s age . ' ) msg [ ' То ' ] = ema i l . ut i l s . forma t addr ( ( ' Re c i p i e n t ' , ' re c i p i e n t @ ex ampl e . com ' ) ) ms g [ ' From ' ] emai l . uti l s . f o rma taddr ( ( ' Author ' , ' autho r @ exarnp l e . com ' ) ) ms g [ ' Subj ect ' ] = ' S impl e te s t me s sage ' =

=

s e rve r smtpl ib . SМТ P ( ' l o ca lho s t ' , 1 0 2 5 ) s e rve r . s e t_debu g l e ve l ( T rue ) # отображать информацию # о подключении к серверу try: s e r ve r . sendrna i l ( ' autho r @ ex ampl e . corn ' , [ ' re c i pi en t @ examp l e . com ' ] , ms g . as_s t r i ng ( ) ) final l y : s e r ve r . qu i t ( ) =

В этом примере, для того что бы отобразить детали процесса взаимодействия клиента с сервером, включен режим отладки , иначе вывод вообще отсутствовал бы .

$

pytho n 3 smtp l i b_sendma i l . py

s end : ' eh l o l . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . o . ip б . a rpa \ r \ n ' rep l y : Ь ' 2 5 0 - 1 . О . О . 0 . О . О . О . О . О . 0 . О . О . 0 . О . О . О . О . О . О . О . О . О . О . О . О . О . o . o . o . o . o . o . i p б . a rpa \ r \ n ' rep l y : b ' 2 5 0 -S I Z E 3 3 5 5 4 4 3 2 \ r \ n ' repl y : Ь ' 2 5 0 HEL P \ r \ n ' repl y : re t code ( 2 5 0 ) ; Ms g : b ' l . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . o . o . o . o . o . o . o . o . o . o . o . o . o . o . ip б . a rpa \ ns r z E 3 3 5 5 4 4 3 2 \ nHELP ' s end : ' ma i l FROM : s i z e= 2 3 6 \ r\ n ' repl y : Ь ' 2 5 0 OK\ r\ n ' repl y : r e t code ( 2 5 0 ) ; M s g : Ь ' ОК ' s e n d : ' r cpt TO : < recipi e n t @exampl e . com> \ r \ n ' repl y : Ь ' 2 5 0 OK\ r \ n ' repl y : r e t code ( 2 5 0 ) ; M s g : Ь ' ОК ' s end : ' da t a \ r \ n ' rep l y : Ь ' 3 5 4 End data w i t h . \ r \ n ' repl y : r e t code ( 3 5 4 ) ; Msg : b ' End data w i t h . ' d a t a : ( 3 5 4 , b ' End data w i th . ' ) se nd : b ' Cont ent -Type : t ex t /pl a i n ; cha r s e t = " u s - a s ci i " \ r \ nMIME-Ve r s i on : 1 . 0 \ r \ nContent -Trans f e r - Encodi ng : 7b i t \ r \ nT o : Re c i p i e n t < r e c i p i e n t @ e x ampl e . com> \ r \ n From : Aut hor \ r \nSu bj e ct : S imp l e test me s s a ge \ r \ n \ r \nTh i s is the body o f the mes s a g e . \r\n . \ r\n ' repl y : Ь ' 2 5 0 OK \ r \ n '

13.1. smtpllЬ: КАМеНТ SМТР repl y : data : s end : rep l y : reply :

845

r e t c ode ( 2 5 0 ) ; Msg : Ь ' ОК ' ( 2 5 0 , Ь ' ОК ' ) ' qui t \ r \n ' Ь ' 2 2 1 Bye \ r \ n ' ret code ( 2 2 1 ) ; Msg : Ь ' В уе '

Второй аргумент метода sendma i l ( ) , определяющий получателей , передается в виде списка. Сообщение будет поочередно доставлено каждому из получателей , адреса которых включены в этот список. Поскольку информация о конверте от­ делена от заголовков сообщения, возможна отправка скрытых копий некоторым адресатам посредством включения их адресов в аргумент метода, а не в заголовок сообщения.

1 3. 1 .2. Аутентификация и шифрование Класс SMTP позволяет использовать средства аутентификации и шифрова­ ния протокола TLS (Tra11sport Layer Security - протокол защиты транспортного уровня) , если они поддерживаются сервером. Ч тобы определить, поддержива­ ет ли сервер работу с протоколом TLS, следует предоставить ему идентифика­ тор клиента и запросить список доступных расширений, вызвав метод ehlo ( ) . Результаты можно проверить, вызвав метод has _ extn ( ) . После запуска TLS сле­ дует повторно вызвать метод ehlo ( ) , прежде чем будет выполнена процедура ау­ тентификации пользователя. В настоящее время многие провайдеры почтовых услуг поддерживают толъко соединения на основе TI.S. Связываясь с такими сер­ верами , следует использовать экземпляр класса SMTP_ SSL для установления шиф­ рованного соединения. Листинг 1 3 .2. smtplib_authenti cated . py import smtpl i b import ema i l . u t i l s from ema i l . mime . t e x t import MIMET e x t import getpas s # В ыв е сти приглашение для в в ода # информа ции о соединении t o ema i l i nput ( ' Recipient : ' ) s e rve rname i nput ( ' Ma i l s e rver name : ' ) s e rverport i nput ( ' Server port : ' ) i f s e rverpor t : s e rverp o r t i n t ( s e rverport ) else : s e rverport 25 u s e_t l s i nput ( ' U s e TLS ? ( ye s /no ) : ' ) . l owe r ( ) u s e rname i nput ( ' Ma i l u s e rname : ' ) p a s s word = g e t pa s s . getpa s s ( " % s ' s p a s s word : " % u s e rname ) =

=

=

=

# Созда т ь сообщение msg MIMET ext ( ' T e s t me s s age from PyMOTW . ' ) msg . s e t_un i x f r om ( ' author ' ) ms g [ ' To ' ] ema i l . ut i l s . forma taddr ( ( ' Re c i p i ent ' , to_emai l ) ) msg [ ' From ' ] ema i l . ut i l s . forma t addr ( ( ' Author ' , =

=

=

Г11&ва 13. ЭМКIJJОНИВR 11ОЧ18

msg [ ' Subj ect ' ]

=

' author @exampl e . com ' ) ) ' T e s t from PyMOTW '

i f u s e_t l s ' ye s ' : p r i n t ( ' s t a r t ing with а s e cure connect i on ' ) s e rver smtp l i b . SMT P_S S L ( s e r vername , se rve rpo r t ) e l se : p r i n t ( ' s t a r t i ng with an i n s e cure connect i on ' ) se rver smt p l i b . SМТ P ( s e rvername , s e rverpo r t ) try: se rver . s e t_debuglevel ( T rue ) ==

=

=

# Иден тифицировать се бя , за прашив а я информа цию о # поддерживае мых средст в ах s e r ve r . eh l o ( ) # Исполь з о в а т ь шифрование в э т ой се ссии , если оно п оддерживается if s e r ve r . ha s_ex t n ( ' STARTTLS ' ) : p r i n t ( ' ( s t a r t i n g TLS ) ' ) s e rver . s t a r t t l s ( ) se rver . eh l o ( ) # пов т орно иден тифицировать себя # п о средств ом Т L S - соедине ния e l se : p r i n t ( ' ( n o STARTTLS ) ' ) i f s e r ve r . h a s _extn ( ' AUTH ' ) : p r i n t ( ' ( l ogg i ng i n ) ' ) s e r ve r . l o g i n ( u s e rname , p a s swo r d ) else : p r i n t ( ' ( no AUTH ) ' ) s e rve r . s endma i l ( ' author @ ex ampl e . com ' , [ t o_ema i l ] , ms g . a s_s t r i n g ( ) ) f i na l l y : s e rve r . qui t ( )

После активизации TI.S расширение манду ЕНLО.

START T L S

не появляется в ответе на

$ python3 source / smtpl iЬ / smtpl i b_a uthe n t i cated . py Re c i p i en t : doug@pymotw . com Ma i l se rver name : l ocalhost Server p o r t : 1 0 2 5 U s e TLS ? ( ye s / no ) : n o Ma i l use rname : t e s t t e s t ' s pas s word : s t a r t i n g w i t h an i n se cure conne c t i on s end : ' eh l o 1 . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . 0 . 0 . О . О . 0 . 0 . 0 . 0 . 0 . ip б . arpa\ r \ n ' rep l y : Ь ' 2 5 0 - 1 . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . О . o . o . o . o . o . o . ip б . arpa \ r \ n ' repl y : b ' 2 5 0 -S I ZE 3 3 5 5 4 4 3 2 \ r \n '

ко­

13.1. &mtplJЬ: К/\И8НТ 5МТР rep l y : Ь ' 2 5 0 HEL P \ r \ n ' rep l y : ret code ( 2 5 0 ) ; Msg : b ' l . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . o . o . o . o . o . o . o . o . o . o . o . o . o . o . i pб . a r pa \ ns r z E 3 3 5 5 4 4 3 2 \ nHELP ' ( no STARTTL S ) ( no AUTH ) send : ' rna i l FROM : < author@ exarnp l e . com> s i ze=2 2 0 \ r \ n ' rep l y : Ь ' 2 5 0 OK\ r \ n ' rep l y : r e t c ode ( 2 5 0 ) ; Msg : Ь ' ОК ' send : ' rcpt T O : < doug @pymotw . com> \ r \ n ' rep l y : Ь ' 2 5 0 OK\ r \ n ' rep l y : ret code ( 2 5 0 ) ; Ms g : Ь ' ОК ' send : ' da t a \ r \ n ' repl y : Ь ' 3 5 4 End da t a wi th . < CR> \ r \ n ' rep l y : r e t code ( 3 5 4 ) ; Ms g : b ' End data w i t h . ' dat a : ( 3 5 4 , b ' End da t a wi th . < CR> ' ) send : b ' Content -Type : text / p l a i n ; cha r s e t = " u s - a s ci i " \ r \ n MIME-Ve rs i on : 1 . 0 \ r \ nContent -Trans fer-Encoding : 7bi t \ r \ nT o : Re c i p i e n t \ r \ nFrorn : Author \ r \nSubj ect : Te s t f r orn PyMOTW\ r \ n \ r \ nT e s t rne s s ag e f r orn PyMOTW . \r\n . \r\n ' rep l y : Ь ' 2 5 0 OK\ r \ n ' rep l y : r e t code ( 2 5 0 ) ; Msg : Ь ' ОК ' da t a : ( 2 5 0 , Ь ' ОК ' ) send : ' qui t \ r \ n ' rep l y : Ь ' 2 2 1 Bye \ r \ n ' rep l y : r e t code ( 2 2 1 ) ; Msg : Ь ' Вуе '

$ python 3 source / srnt p l i Ь / srntpl ib_authe n t i cated . py Re c i p i ent : doug @pymo t w . com Ma i l s e r ve r narne : rna i l . i sp . net Server port : 4 6 5 U s e T L S ? ( ye s /no ) : ye s Ma i l u s e rnarne : dough e l lrnann @ i s p . ne t doughel lrnann@ i sp . net ' s pa s sword : s t a r t i ng w i t h а s e cure conne c t i o n send : ' e h l o 1 . 0 . о . о . о . о . о . о . о . о . о . о . о . о . о . о . о . о . о . о . о . о . о . о . о . о . о . 0 . 0 . 0 . 0 . 0 . i pб . a rpa \ r \ n ' rep l y : b ' 2 5 0 -rna i l . i sp . ne t \ r \ n ' rep l y : b ' 2 5 0 - P I PEL I NING \ r \ n ' rep l y : b ' 2 5 0- S I Z E 7 1 0 0 0 0 0 0 \ r \ n ' rep l y : b ' 2 5 0-ENНANCE DSTATUSCODE S \ r \ n ' rep l y : b ' 2 5 0 - 8 BITMIME \ r \n ' rep l y : b ' 2 5 0 -AUTH PLA I N LOG I N\ r \ n ' rep l y : Ь ' 2 5 0 AUTH= PLAIN LOG I N \ r \ n ' rep l y : r et code ( 2 5 0 ) ; Ms g : b ' rna i l . i sp . ne t \ n P I PEL I N I NG \ n S I Z E 7 1 0 0 0 0 0 0 \ nENHANCEDSTATUSCODE S \ n B B I TMIME \ nAUTH PLAIN LOGI N \ n AUTH=PLA I N LOGI N ' ( no STARTTLS ) ( lo g g i n g i n ) send : ' AUTH PLAI N AGRvdWdoZWx sbWFub kBrnYXNObWFpbC 5rnЬQBUTUZ 3MDBrnZrnF z dG l haWw= \ r \ n ' repl y : Ь ' 2 3 5 2 . 0 . О OK \ r \ n ' repl y : r e t code ( 2 3 5 ) ; M s g : Ь ' 2 . О . О ОК '

847

848

ГАВва 13. Змкqюннм noчra

s e nd : ' ma i l FROM : s i ze=2 2 0 \ r \n ' rep l y : Ь ' 2 5 0 2 . 1 . О O k \ r \ n ' rep l y : retcode ( 2 5 0 ) ; Ms g : Ь ' 2 . 1 . О Ok ' s e nd : ' r cpt T O : \ r \ n ' reply : Ь ' 2 5 0 2 . 1 . 5 Ok\ r \ n ' reply : ret code ( 2 5 0 ) ; Msg : Ь ' 2 . 1 . 5 Ok ' send : ' data \ r \ n ' repl y : Ь ' 3 5 4 End d a t a with . \ r \ n ' rep l y : retcode ( 3 5 4 ) ; Ms g : b ' End data with . ' d a t a : ( 3 5 4 , b ' End data w i t h . ' ) s e nd : b ' Content -Type : text / pl a i n ; cha r s et= " u s - a s ci i " \ r \ n MIME-Ve r s i on : 1 . 0 \ r \nCon t e n t -T r a n s f e r - Encoding : 7bi t \ r \ nT o : Re c i p i e nt \ r \ nFrom : Author \ r \ nSubj ect : T e s t f rom PyMOTW\ r \ n \ r \nT e s t me s s a g e f r om PyMOTW . \r\n . \r\n ' repl y : Ь ' 2 5 0 2 . 0 . О Ok : queued a s AOEF7 F2 9 B 3 \ r \ n ' rep l y : ret code ( 2 5 0 ) ; Ms g : Ь ' 2 . О . О Ok : queued a s AOEF7 F2 9 B 3 ' data : ( 2 5 0 , Ь ' 2 . О . 0 Ok : queued a s AOEF7 F2 9 B 3 ' ) s e nd : ' qu i t \ r \ n ' repl y : Ь ' 2 2 1 2 . 0 . 0 Bye \ r\ n ' repl y : ret code ( 2 2 1 ) ; Msg : Ь ' 2 . О . О Вуе '

1 3. 1 .3. Верификация адреса электронной почты Протокол SMTP включает команду VRFY для проверки корректности имени адресата. Обычно ее отключают, чтобы затруднить спамерам определение истин· ных почтовых адресов. Но если она используется, то клиент может запросить у сервера информацию об адресе и получить код состояния, позволяющий убедить· ся в том, что адрес корректный. Также будет получено полное имя пользователя, если оно доступно. Листинг 1 3.3. smtpl ib_verify . py import smtpl ib s e rve r smtpl i b . SMT P ( ' ma i l ' ) s e rver . s et_debu g l e ve l ( True ) # ото бражать информацию # о соедине нии с сервером try : dhe l lmann r e s u l t = s e rve r . ve r i fy ( ' dhe l lmann ' ) notthere re s u l t s e rve r . ve r i f y ( ' notthere ' ) f i na l l y : s e r ve r . qui t ( ) =

=

p r i n t ( ' dhe l lmann : ' , dhe l lmann_r e s ul t ) p r i n t ( ' notthere : ' , not the re_re s u l t )

Последние две строки вывода свидетельствуют о том, что адрес dhe llmann корректный, в то время как адрес notthere некорректный. -

$

python3 smtplib_ve ri fy . py

s e nd :

' vr fy \ r \ n '

-

13.2. smtpd: примеры почтовых серверов

849

repl y : ' 2 5 0 2 . 1 . 5 Doug H e l lmann \ r \ n ' r ep l y : ret code ( 2 5 0 ) ; Ms g : 2 . 1 . 5 Doug H e l lmann s e nd : ' v r fy < no t t h e r e> \ r \ n ' r epl y : ' 5 5 0 5 . 1 . 1 < n o t t h e r e > U s e r un known \ r \ n ' repl y : re t code ( 5 5 0 ) ; Ms g : 5 . 1 . 1 . . . U s e r unknown s e nd : ' qu i t \ r \ n ' r epl y : ' 2 2 1 2 . 0 . О ma i l c l o s i ng connect i on \ r \ n ' r eply : re t code ( 2 2 1 ) ; M s g : 2 . 0 . О mai l c l o s i ng conne c t i o n dhe l lma n n : ( 2 5 0 , ' 2 . 1 . 5 Doug Hel lmann < dh e l lmann@ma i l > ' ) not t here : ( 5 5 0 , ' 5 . 1 . 1 U s e r un known ' ) .



.







Дополнительные ссылки • •

Раздел документации стандартной библиотеки, посвященный модулю smtpl ib2 . RFC 821 3 . Спецификация SMTP - простого протокола передачи почты.



RFC 1 Sб g4 . SMTP Service Extensions. Расширение базового протокола.



RFC 822 5 . Stondard for the Format of ARPA lnternet Text Messages. Первоначальная спец­ ификация формата сообщений электронной почты.



RFC 53226 . lnternet Message Format. Обновление формата сообщений электронной по­ чты .





ema i l . Модуль стандартной библиотеки, предназначенный для создания и анализа со­ общений электронной почты. smtpd (раздел

1 3.2.

1 3.2). Модуль, реализующий простой SМТР-сервер.

smtpd:

примеры почтовых серверов

Модуль smtpd включает классы , предназначенные для создания SМТР-серве­ ров. Эти классы реализуют серверную часть протокола, испольауемую модулем smtpl ib ( раздел 1 3. 1 ) .

1 3.2. 1 . Базовый класс почтового сервера В приведенных ниже примерах серверов в качестве базового класса использу­ ется класс SMTPSe rve r. Он управляет взаимодействием с клиентом и получением входных данных, предоставляя удобную возможность организации пользователь­ ской обработки сообщения , как только оно становится полностью доступным. В качестве аргументов конструктор получает локальный адрес, по которо­ му следует нрослушинать соеди нения, и удаленные адреса, по которым должны быть доставлены сообщения. Проиаводный класс может переопределить метод proce s s_mes sage ( ) , который вызывается сразу же после получения всего сооб­ щения и поддерживает следующие аргументы. 2 3 4 5 6

https : / / docs . python . o r g / 3 . 5 / l ibrary/ smtpl ib . htm https : / / t o o l s . i e t f . o r g / html / r fc8 2 1 . html https : / / t o o l s . i e t f . o r g / html / r f c 1 8 6 9 . html https : / / to o l s . i e t f . or g / html / rfc8 2 2 . html https : / / t o o l s . i et f . o r g /html / r f c 5 3 2 2 . html

ГАвва 13. Эмк�рон нм почта

850 •

peer. Адрес клиента в виде кортежа, содержащего I Р-адрес и входящий порт. • rna i l frorn. И нформация, предоставляемая серверу клиентом с помощью ко­ манды MAI L FROM. Эта информация может не всегда совпадать с информа­ цией , содержащейся в поле Frorn заголовка. • rcpttos. Список получателей из конверта сообщения. Опять-таки, этот список может не всегда совпадать с информацией, содержащейся в поле Т о заголовка, особенно есл и получателю направляется скрытая копия. • data. Полное тело сообщения в соответствии с документом RFC 5322 . Предосrавляемая по умолчанию реализация метода proces s_rne s s age ( ) воз­ буждает исключение Not irnplernentedError. В следующем примере определяется подкласс, который переопределяет данный метод для вывода информации о по­ лучаемом сообщении. Л истинг 1 3.4. smtpd_cus tom . py import smtpd import a s ynco re

c l a s s Cus t omSMT P S e rve r ( smtpd . SMT P S e rve r ) : d e f proces s_me s s a g e ( se l f , pee r , ma i l f rom, rcpt t o s , dat a ) : p r i nt ( ' Re c e i v i n g me s s age f r om : ' , p e e r ) p r i n t ( ' Me s s a g e addre s s ed from : ' , ma i l from ) p r i nt ( ' Me s s a g e addr e s sed t o : ' , rcpt t o s ) : ' , l e n ( da t a ) ) p r i nt ( ' Me s s ag e length

s e rver

=

Cus t omSMT P S e rver ( ( ' 1 2 7 . 0 . 0 . 1 ' ,

1 0 2 5 ) , None )

a s yncore . l oop ( )

Класс SMTPServer использует модуль asyncore, поэтому сервер запускается посредством вызова a s yncore . loop ( ) . Чтобы продемонстрировать работу сервера, требуется клиент. Для создания клиента, который будет посылать дан ные тестовому серверу. выполняющемуся локально и работающему через порт 1 025, можно воспользоваться адаптирован­ ной версией одного из примеров, приведенных при обсуждении модуля srntpl ib (раздел 13. l ) . Листинг 1 3.5. smtpd_senddata . py impo r t smtpl ib import ema i l . ut i l s f rom ema i l . mime . t ext import MIMET e x t # Со здать сообщение ms g MIMEText ( ' T h i s is t he body of the me s s age . ' ) ms g [ ' T o ' ] ema i l . ut i l s . forma t add r ( ( ' Recipi ent ' , ' r ecipi e nt @ e xamp l e . com ' ) ) =

=

13.2. lmtpd: nрммерw llOЧ10llЫX серверов ms g [ ' From ' ]

851

ema i l . ut i l s . format addr ( ( ' Author ' , ' author @ exampl e . com ' ) ) ms g [ ' Subj e ct ' ] ' S impl e t e s t me s s ag e ' =

=

s e r ve r smtpl ib . SМТP ( ' l 2 7 . 0 . 0 . l ' , 1 0 2 5 ) # о т обража т ь информацию о s e rver . s et_debugleve l ( T rue ) # в з аимодейс т вии с сервером try : s e rve r . sendmai l ( ' auth o r @ exampl e . com ' , [ ' r e c i p i e n t @ examp l e . com ' ] , ms g . a s s t r i ng ( ) ) f i na l l y : s e r ve r . qui t ( ) =

_

Чтобы протестировать работу п1юграмм, следует выполнить сценарий smtpd cus tom . ру в одном окне терминала, а сценарий smtpd s endda ta . ру в другом.

_

_

$

-

python3 зmtpd_cus t om . py

Re c e i ving me s s a g e from : ( ' 1 2 7 . 0 . 0 . 1 ' , 5 8 5 4 1 ) Me з s age add r e s s e d f rom : author@ examp l e . com Me s s a g e addr e s s ed to : [ ' r e c i p i e nt @ exampl e . com ' ] Me s s ag e l ength : 2 2 9

Отладочный вывод отражает все этапы взаимодействия с сервером.

$

python3 smtpd_senddat a . py

s end : ' eh l o l . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . o . o . o . o . o . o . ip б . a rpa \ r \ n ' repl y : b ' 2 50 - l . O . O . O . O . O . O . O . O . O . O . O . O . O . O . O . O . O . O . O . O . O . O . O . O . O . o . o . o . o . o . o . ip б . arpa \ r \ n ' repl y : b ' 2 5 0 S I ZE 3 3 5 5 4 4 3 2 \ r \ n ' repl y : Ь ' 2 5 0 HELP\ r \ n ' r ep l y : r e t code ( 2 5 0 ) ; Msg : b ' l . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . o . o . o . o . o . o . o . o . o . o . o . o . o . o . ip б . a rpa \ ns r zE 3 3 5 5 4 4 3 2 \ nHELP ' s end : ' ma i l FROM : з i z e= 2 3 6 \ r \ n ' repl y : Ь ' 2 5 0 OK\ r \n ' repl y : r e t code ( 2 5 0 ) ; Ms g : Ь ' ОК ' s end : ' r cpt TO : < r e c i p i e nt @exampl e . com> \ r \ n ' repl y : Ь ' 2 5 0 OK\ r \n ' repl y : r e t code ( 2 5 0 ) ; Msg : Ь ' ОК ' s end : ' da t a \ r \ n ' repl y : Ь ' 3 5 4 End dat a with . < CR> \ r \n ' reply : r e t code ( 3 5 4 ) ; Msg : b ' End da t a wi t h < CR> . < CR> ' data : ( 3 5 4 , b ' End data wi t h . < C R> ' ) s end : b ' Cont ent -Type : t ext /pl a i n ; cha r s e t = " u s - a s c i i " \ r \nMIME-Ve r s i on : 1 . 0 \ r \ nCont ent T r a n s fer- Encoding : 7bi t \ r \nT o : Recipi ent < r e c i p i e nt @ examp l e . com> \ r \n From : Auth o r \ r \nSu bj ect : S impl e test me s s a g e \ r \ n \ r \nTh i s is the body o f the me s s a g e . \ r\n . \ r\n ' repl y : Ь ' 2 5 0 OK\ r \ n ' repl y : r e t code ( 2 5 0 ) ; Msg : Ь ' ОК ' -

-

l'Nl88 13. 3мкrрОНН8Я ПСМ'll

852 data : s end : reply : rep l y :

( 2 5 0 , Ь ' ОК ' ) ' qu i t \ r \ n ' Ь ' 2 2 1 Bye \ r \ n ' re t code ( 2 2 1 ) ; Ms g : Ь ' Вуе '

Для остановки сервера следует нажать комбинацию клавиш .

1 3.2.2.

Отладочный сервер

В предыдущем примере было продемонстрировано использование аргументов метода proce s s _rne s s age ( ) , но модуль smtpd включает также сервер Debugging Serve r, с11ециально предна:шаченный для выполнения более полной отладки кода. Этот сервер выводит на консоль входящее сообщение целиком, после чего прекращает обработку (не пересылает сообщение реальному почтовому серверу). Листинг 1 3.б. smtpd_deЬug . ру impo rt smtpd import a s yncore s e rve r

=

smtpd . Debuggi ngSe rve r ( ( ' l 2 7 . 0 . 0 . l ' , 1 0 2 5 ) , None )

a s yncore . l oop ( )

Совместное использование приведен ной ранее клиентской программы smtpd senddata . ру с сервером DebuggingServer дает следующий вывод. _

---------­

- - - - - - - - - - MESSAGE FOLLOWS Content -Type : text /pl a i n ; cha rs e t = " u s - a s c i i " MIME - Ve r s i on : 1 . 0 C o n t e n t - T r an s fe r- Encodi ng : 7 b i t Т о : Re c i p i ent < r e c i p i ent @ examp l e . com> From : Author Subj ect : S impl e t e s t me s s a ge X- Pee r : 1 2 7 . 0 . 0 . 1 This i s the body of t he me s s a ge . - - - - - - - - - - - - EN D MES SAGE - - - - - - - - - - - -

1 3. 2 . 3 .

Прокси-сервер

Класс Pure Proxy реализует прокси-сервер. Входящие сообщения переадресо­ вываются серверу, указанному в качестве аргумента конструктора. Предупреждение

В разделе документации стандартной библиотеки, посвященном модулю smt pd, отме­ чается, что "запуская этот сервер, вы рискуете создать открытый шлюз, так что будьте осторожны".

Процедуры настройки проке.и-сервер а и отладочного сервера примерно оди­ наковы .

13.Э. mallЬoll: мвниnумро118н11е архмвемм ме�nронноi nolf11il

853

Листинг 1 3.7. sшtpd_Proxy . py impo rt smtpd impo rt a s yncore s e rv e r = smt pd . Pure Proxy ( ( ' l 2 7 . 0 . 0 . l ' , 1 0 2 5 ) ,

( ' mai l ' , 2 5 ) )

a s yncore . l oop ( )

Эта программа не выводит никакой информации. Чтобы убедиться в том, что она правильно работает, следует обратиться к журналу сервера. Aug 2 0 1 9 : 1 6 : 3 4 home r s endma i l [ 6 7 8 5 ] : m9JNGXJЬ 0 0 6 7 8 5 : from= , s i ze=2 4 8 , c l a s s = O , n r cpt s= l , rns g i d= < 2 0 08 1 0 1 9 2 3 1 6 . m9JNGXJЬ 0 0 6 7 8 5 @home r . e xampl e . com> , prot o=ESМТ P , daemon=МТA, r e l a y= [ l 9 2 . 1 6 8 . l . 1 7 ] Дополн ительные ссылки • • • • •



Раздел документации стандартной библиотеки, посвященный модулю smtpd7 • smt p l i b (раздел 1 3 . 1 ). Предоставляет интерфейс клиента. ema i l . Выполняет синтаксический анализ сообщений электронной почты. a s yncore. Базовый модуль, предназначенный для создания асинхронных серверов. RFC 28228 . /nternet Message Format. Определяет формат сообщений электронной почты. RFC 53229 • Заменяет документ RFC 2822.

1 3 . 3 . mai lbox: манипулирование архивами

электронной почты Модуль rnai lbox определяет общий Al)I для доступа к сообщениям электрон­ ной почты, сохраненным с использованием форматов локального диска, включая следующие: •

Maildir



mbox



мн



ВаЬуl



MMI>J:o'

Существуют базовые классы Mai lbox и Me s s age, и каждый формат почтовоl'О ящика включает соответствующую пару подклассов для реализации деталей дан­ ного формата .

7

https : / /doc s . python . o rg / 3 . 5 / l ibra r y / smtpd . html

8

https : / / t o o l s . i e t f . o r g / html / r fc2 8 2 2 . html https : / / too l s . i et f . o r g / htrnl / r fc5 3 2 2 . html

9

1"11888 13. Эмкrроннм почта

854

1 3 .3 . 1 . mbox Формат почтового ящика шlюх - простейший для отображения в документа­ ции, поскольку он представляет собой п ростой текст. Каждый почтовый я щ ик со­ храняется в отделыюм файле, содержащем конкатенированные сообщения. Если встречается строка, начинающаяся с текста "l . "

1 3.3. 1 . 1 . Создание почтового ящика mbox Чтобы создать экземпляр класса mЬох, b ' I I ENO CAPABI LITY ' 1 9 : 4 0 . 7 3 < Ь ' * CAPAB I LITY I МA P 4 r e v l LI TERAL+ SAS L - I R LOGIN- RE F ERRALS I D ENAВLE I DLE AUTH= PLAI N ' 1 9 : 4 0 . 7 3 < b ' I I ENO ОК Pre - l og i n c apabi l i t i e s l i s t e d , pos t - l o g i n capabi l i t i e s h ave mo re . ' 1 9 : 4 0 . 7 3 CAPAB I L I T I E S : ( ' IМAP4 REV 1 ' , ' LITERAL+ ' , ' SASL- I R ' , ' L OGI N - RE FERRALS ' , ' I D ' , ' ENABLE ' , ' I DLE ' , ' AUTH= PLAI N ' ) 1 9 : 4 0 . 7 3 > b ' I I EN l LOGIN e xamp l e " TMFw O O fpymotw" ' 1 9 : 4 0 . 7 9 < Ь ' * CAPAB I LITY IМAP4 revl LI TERAL+ SAS L - I R LOGIN-REF ERRALS ID ENAВLE I DLE S ORT S ORT=DI S PLAY THREAD=REFERENCES THREAD =REFS THREAD=ORDEREDSUBJECT MULT IAPPEND URL- PART IAL CATENATE UNS ELECT CHI LDREN NAМES PACE UI DPLUS L I ST - EXTENDED I l 8 NLEVEL= l CONDS TORE QRES YNC ES EARCH E SORT S EARCHRES W I T H I N CONTEXT=SEARCH L I S T ­ STATUS S PEC IAL-USE B I NARY MOVE ' 1 9 : 4 0 . 7 9 < b ' I I ENl ОК Logged i n ' 1 9 : 4 0 . 7 9 > b ' I I EN2 EXAМINE INBOX ' 1 9 : 4 0 . 8 2 < Ь ' * FLAGS ( \ \An s we red \ \ Fl agged \ \ D e l e t e d \ \ S e e n \ \ D r a ft ) ' 1 9 : 4 0 . 8 2 < Ь ' * ОК [ PERМANENT FLAG S ( ) ] Read-on l y ma i lbox . ' 1 9 : 4 0 . 8 2 < Ь ' * 2 EX I ST S ' 1 9 : 4 0 . 8 2 < Ь ' * О RECENT ' 1 9 : 4 0 . 8 2 < Ь ' * ОК [ UNSEEN 1 ) Fi r s t u n s e e n . ' 1 9 : 4 0 . 8 2 < Ь ' * ОК [ U I DVAL I D I T Y 1 4 5 7 2 9 7 7 6 9 ) U I Ds v a l i d ' 1 9 : 4 0 . 8 2 < Ь ' * ОК [ U I DNEXT 6 ) Predi c t e d next U I D ' 1 9 : 4 0 . 8 2 < Ь ' * ОК [ H I GHESTMODSEQ 2 0 ) Highest ' 1 9 : 4 0 . 8 2 < b ' I I EN2 ОК [ READ-ONLY ] Examine comp l e t e d ( О . О О О s e c S) . '

1 9 : 4 0 . 8 2 > b ' I I EN3 FETCH 1 ( BODY . PEEK [ HEADER] FLAGS ) ' 1 9 : 4 0 . 8 6 < Ь ' * 1 FETCH ( FLAGS ( ) BODY [ HEADE R] { 3 1 0 8 ) ' 1 9 : 4 0 . 8 6 read l i te ra l s i z e 3 1 0 8 1 9 : 40 . 86 < Ь ' ) ' 1 9 : 4 0 . 8 9 < b ' I I EN3 ОК Fe t c h comp l e t e d . ' 1 9 : 4 0 . 8 9 > b ' I I EN4 LOGOUT ' 1 9 : 4 0 . 9 3 < Ь ' * ВУЕ Logging out ' 1 9 : 4 0 . 9 3 БУЕ r e spon s e : b ' Loggi ng out ' [ ( b ' l ( FLAGS ( ) BODY [ HEADER ] { 3 1 0 8 ) ' , b ' Return- Pat h : \ r \ nReceived : from compu t e 4 . i n t e rn a l ( '

13.4. lrnepllb: КАИеН11:К8R бмбNкmtкв IМАР4

877

b ' comput e 4 . nyi . i nt e rnal [ 1 0 . 2 0 2 . 2 . 4 4 ] ) \ r \ n \ t Ьу s l ot i 2 6 t 0 1 ( Су rus 3 . 0 . 0 -bet a l ' b ' - g i t - fa s tma i l - 1 2 4 1 0 ) w i t h LМТ PA ; \ r \ n \ t Sun , 0 6 Ma r 2 0 1 6 1 6 : 1 6 : 03 -0500\r ' b ' \ nX-Si eve : CMU S i eve 2 . 4 \ r \ nX - Spam- known - s e nde r : yes , fadd l c f 2 - dc 3 a- 4 9 8 4 -a 0 ' b ' Bb- 0 2 ce f3 c f 1 2 2 l= " doug" , \ r \ n e a 3 4 9ad0 - 9 2 9 9 - 4 7b5-b6 3 2 - 6 f f l e 3 9 4 cc 7 d= " both he ' b ' l l f l y " \ r \ nX-Spam- s c o r e : 0 . 0 \ r \ nX- Spam-h i t s : ALL-TRUSTED - 1 , BAYES 0 0 - 1 . 1 Ь ' 9 , LANGUAGES un known , BAYES USED g l obal , \ r \ n SA VERS I ON 3 . 3 . 2 \ r \nX- Spam ' b " - source : I P• ' 1 2 7 . 0 . 0 . l ' , H o s t = ' un k ' , Coun t r y= ' un k ' , FromНead e r= ' com ' , \ r \ n " Ь" Ma i l From= ' com ' \ r \ nX-Spam-cha r s et s : p l a i n= ' us - a s c i i ' \ r \ nX-Re s o l ved - t o : d" b ' oughe l lmann@fas tmai l . fm\ r \ nX - De l i vered- t o : doug@doughe l lmann . com\ r \ nX-Ma ' b ' i l - from : doug@ doughe l l mann . com\ r \ n Re c e i ved : from mx 5 ( [ 1 0 . 2 0 2 . 2 . 2 04 ] ) \r ' b ' \ n Ьу comput e 4 . i n t e rna l ( LМТ РРrоху ) ; Sun , 0 6 Mar 2 0 1 6 1 6 : 1 6 : 0 3 - 0 5 0 0 \ r \nRe ' b ' c e i ved : from mx 5 . nyi . i n t e rnal ( l o ca l h o s t [ 1 2 7 . 0 . 0 . 1 ] ) \ r \ n \tb у mx 5 . nyi . i n t e r ' b ' nal ( Po s t f i x ) wi t h ESMT P id 4 7 CBA2 8 0 DB 3 \ r \ n \ t fo r ; S ' b ' un , 6 Ma r 2 0 1 6 1 6 : 1 6 : 0 3 - 0 5 0 0 ( E ST ) \ r \ nRe c e i ved : from mx 5 . n yi . i n t e rnal ( 1 ' b ' o c a l h o s t [ 1 2 7 . 0 . 0 . 1 ) ) \ r \ n Ьу mx 5 . nyi . i nt e r n a l ( Authent i ca t i o n Mi l t e r ) w ' b ' i t h ESМТ P \ r \ n i d A7 1 7 8 8 6 8 4 6 E . 3 0BA4 2 8 0 D 8 1 ; \ r \ n Sun , 6 М ar 2 0 1 6 1 6 : 1 ' Ь ' 6 : 0 3 - 0 5 0 0 \ r \ nAut hent i ca t i on-Res u l t s : mx 5 . nyi . i nt e rna l ; \ r \ n dkim=pa s s ' Ь ' ( 1 0 2 4 -bi t r s a key ) heade r . d=me s sagingengi ne . com heade r . i = @m e s s ag i ngeng i ' b ' ne . c om he ade r . b=Jr sm+pC o ; \ r \ n x - l o c a l - ip•pa s s \ r \ nRec e i ved : from ma i l o ' b ' ut . nyi . i nt e rnal ( ga t ewayl . nyi . i n t e r n a l [ 1 0 . 2 0 2 . 2 . 2 2 1 ) ) \ r \ n \ t ( us i ng TLSvl . 2 ' b ' wi t h cipher ECDHE-RSA-AES2 5 6 - GCM- S НA3 8 4 ( 2 5 6 / 2 5 6 bit s ) ) \ r \ n \ t ( No c l i ent c e r ' b ' t i fi ca t e r eque sted ) \ r \ n \ tby mx 5 . ny i . i nt e r n a l ( Po s t fi x ) w i th ЕSМТ Р S id 3 0ВА4 ' b ' 2 8 0 D B 1 \ r \ n \ t for ; S un , 6 Ma r 2 0 1 6 1 6 : 16 : 03 -0500 ( Е ' b ' ST ) \ r \ nRe c e i ved : from comput e 2 . i n t e rnal ( c ompu t e 2 . nyi . i n t e rn al [ 10 . 2 02 . 2 . 4 ' b ' 2 ) ) \ r \ n \ tby ma i l out . nyi . i n t e rnal ( Po s t f i x ) wi t h ЕSМТ Р i d 1 7 4 0 4 2 0 DOA\ r \ n \ t f ' b ' or ; Sun , 6 Ma r 2 0 1 6 1 6 : 1 6 : 0 3 - 0 5 0 0 ( EST ) \ r \ nRec e i ' b ' ved : from frontend2 ( [ 1 0 . 2 0 2 . 2 . 1 6 1 ) ) \ r \ n Ьу comput e 2 . i n t e rn -

1"А888 13. Змктроннв nсма

878 al

( ME P rox y ) ; ' b ' S u n , 0 6 Mar 2 0 1 6 1 6 : 1 6 : 0 3 - 0 5 0 0 \ r \nDKIM- S i gnat ure : v= l ; a=rs a - s ha l ; c= r e l a ' b ' x ed / r e l axed ; d= \ r \ n \ t me s sa g i nge n g i n e . com; h=con t e n t - t rans fer -encod i n g : cont e ' b ' n t - t ype \ r \ n \ t : da t e : from : me s sage - i d : mime-ve r s i o n : s ubj ect : t o : x -sasl -enc\r \n ' b ' \ t : x - s a s l -e n c ; s =smtpout ; bh= P 9 8 NT s Eo 0 1 5 s uwJ4 g k7 l knAWLa 4 = ; Ь =Jrsm+ \ r \ n \ t ' b ' pCovRi oQI Ryp 8 Fl OL 6JHO I 8 sbZy2 obx 7 02 8 JF2 iT l TWmX3 3Rhlq9 4 0 3XRklw N 3 JA\ r \ n \ t 7 KS Pq ' b ' MTp 3 0Qdx 6 y I UaADwQqlO+QMuQq / QxBHdj e ebmdhgVf j hqx r zTbSMww / ZNhL\ r \ n\ t Ywv/QM / o DH ' b ' bXi LSUlB3Qrg+ 9ws E / O j U / EOi s i U= \ r \nX - S a s l -enc : 8 Z J+ 4 Z RE 8 AG P z dL RWQFi vGymJb Bpa ' b ' 4 G 9JGcb7 k4 xKn + I 1 4 5 7 2 9 8 9 6 2 \ r \ nRec e i ve d : from ( 1 9 2 . 1 6 8 . 1 . 1 4 ] ( 7 5- 1 3 7 - 1 - 3 4 . d ' b ' hcp . nwnn . ga . c ha r t e r . c om ( 7 5 . 1 3 7 . 1 . 3 4 ] ) \ r \ n \ tby ma i l . me s s a g i n geng i n e . com ( Ро ' b ' s t f i x ) wi t h ESMT PA id C O B3 6 6 8 0 1 C D \ r \ n \ t fo r ; Sun , 6 ' Ь ' Ma r 2 0 1 6 1 6 : 1 6 : 0 2 - 0 5 0 0 ( E ST ) \ r \ n From : Doug H e l lmann \ r \ nC o n t e n t - T ype : t e x t / pl a i n ; c ha r s e t = u s - a s c i i \ r \ nCo n t e nt -Trans f e r - E n ' b ' codi ng : 7 bi t \ r \ nS ubj e c t : PyMOTW Examp l e me s s ag e 2 \ r \nMe s sage - I d : < O OABCD ' b ' 4 6 - DADA- 4 9 1 2 -A4 5 1 - D2 7 1 6 5BC3A2 F@doughe l lmann . com> \ r \nDate : Su n, 6 ма r 2 0 1 6 ' Ь ' 1 6 : 1 6 : 0 2 - 0 5 0 0 \ r \nTo : Doug Hel lmann \ r \ nMime-Ve r s ' b ' i on : 1 . 0 ( Мае OS Х Ma i l 9 . 2 \ \ ( 3 1 1 2 \ \ ) ) \ r \ nX-Mai l e r : App l e М ail ( 2 . 3112 ) ' Ь ' \ r\n\r\n ' ) , Ь' ) , ]

Полученный с помощью команды FETCH ответ начинается с флагов, а далее он указывает на то, что сообщение содержит 595 байт заголовочных данных. Клиент конструирует кортеж с ответом, а затем закрывает последовательность одиноч­ ной строкой, содержащей правую круглую скобку ( ) ) , которую сервер посылает в конце ответа на запрос FETCH. Возможно, при таком способе форматирования проще извлекать различные фрагменты информации по отдельности или реком­ бинировать ответ и анализировать его на стороне клиента. Листинг 1 3.31 . imapl ib fetch separa tel у . ру _

_

import imap l i b impo rt ppr int impo rt imap l i b_connect w i t h imapl ib_conne ct . open_conne c t i on ( ) a s с : c . s e l e c t ( ' I NBOX ' , readon l y=True )

13.4. lmapllЬ: IWl8НТCIC8R 6м6Аиоrема IМАР4

870

p r i nt ( ' HEADE R : ' ) t yp , ms g_data c . f e t ch ( ' 1 ' , ' ( BODY . PEEK [ HEADER ] ) ' ) for response part i n msg dat a : i f i s i n s t ance ( re spons e pa r t , t up l e ) : p r i n t ( re spons e_part [ l ] ) =

p r i n t ( ' \nBODY ТЕХТ : ' ) t yp , ms g_dat a = с . f e t ch ( ' 1 ' , ' ( BODY . РЕЕК [ ТЕХТ ] ) ' ) for re spon s e part i n ms g dat a : i f i s i n s t ance ( re sponse_pa r t , t up l e ) : p r i n t ( r e s pons e_part [ l ] ) p r i nt ( ' \ n FLAGS : ' ) t yp , msg data = с . f e t ch ( ' 1 ' , ' ( FLAGS ) ' ) f o r r e spon s e_pa rt i n ms g_da t a : pr i nt ( re spons e_pa r t ) p r i nt ( imapl i b . P a r s e Fl a g s ( r e s p o n s e_part ) )

Дополнительным преимуществом раздельного извлечения значений является что такой подход упрощает исп оль.зование метода ParseFla g s ( ) для выделе­ ния фла1-ов из ответа.

то,

$

python3 imapl i b_fet ch_separat e l y . py

HEADER : b ' Retu rn- Pat h : \ r \ nR e c e i ved : from compute 4 . i n t e rnal ( comput e 4 . nyi . i n t e rnal [ 1 0 . 2 0 2 . 2 . 4 4 ] ) \ r \ n \ t Ьу s l o t i 2 бt O l ( Cyrus 3 . 0 . 0 -bet a l - g i t - fa s t ma i l - 1 2 4 1 0 ) w i t h LMT PA ; \ r \ n \ t S u n , О б Ma r 2 0 1 б l б : l б : О 3 - 0 5 0 0 \ r \ nX - S i eve : CMU S i eve 2 . 4 \ r \nX-Spa m- known- s e nde r : ye s , fadd l c f 2 - d c 3 a- 4 9 8 4 - a 0 8 b - 0 2 c e f 3 c f 1 2 2 l = " doug " , \ r \ n e a 3 4 9ad0 - 9 2 9 9 - 4 7 b 5 - b б 3 2 - б f f l e 3 9 4 c c7d=" both he l l f l y " \ r \ nXSpams co r e : 0 . 0 \ r \ nX- Spam-hi t s : ALL TRUSTED - 1 , BAYES 00 - 1 . 9 , L ANGUAGES un known , BAYES USED g l oba l , \ r \ n SA-VERS ION 3 . 3 . 2 \ r \ nXSpamsource : I P= \ ' 1 2 7 . 0 . 0 . 1 \ ' , H o s t � \ ' unk \ ' , Count ry= \ ' un k \ ' , Fr omНe ade r=\ ' com\ ' , \ r \ n Ma i l From� \ ' c om\ ' \ r \ nX- Spam- cha r s et s : p l a i n= \ ' u s - a s c i i \ ' \ r \ nX-Res o l ved-t o : doughel lmann@fastma i l . fm\ r \ nX - D e l i ve red-to : doug@doughel lmann . com\ r \ nX-Ma i l - from : doug@doughe l l mann . com\ r \ nRecei ved : f rom mx 5 ( [ 1 0 . 2 0 2 . 2 . 2 0 4 ] ) \ r \ n Ь у comput e 4 . in t e rnal ( LMT PProxy ) ; Sun , О б Ma r 2 0 1 б l б : l б : О 3 - 0 5 0 0 \ r \ nRe c e i v ed : from mx 5 . nyi . i n t e rnal ( l ocalhost [ 1 2 7 . 0 . 0 . 1 ] ) \ r \ n \ tby mx 5 . ny i . i nt e rnal ( Po s t f i x ) wi t h ЕSМТ Р id 4 7 CBA2 8 0 DB 3 \ r \n \ t fo r ; Sun, б Ma r 2 0 1 б l б : l б : О 3 - 0 5 0 0 ( EST ) \ r\nRe c e i v e d : from mx 5 . nyi . i n t ernal ( l ocalhos t [ 1 2 7 . 0 . 0 . 1 ] ) \ r \ n Ьу mx 5 . nyi . i nt e rnal ( Authent i ca t i o n Mi l t e r ) wi t h ESМТ P \ r \ n i d А7 1 7 8 8 б 8 4 б E . 3 0 BA4 2 8 0 D 8 1 ; \ r \ n Sun, б Ma r 2 0 1 б l б : l б : О 3 - 0 5 0 0 \ r \nAut h e nt i c a t i on - Re s ul t s : mx5 . nyi . i n t e r n a l ; \ r \ n dkim�pa s s ( 1 02 4 -b i t r s a key ) heade r . d=me s s a g i ngeng i n e . com heade r . i =@me s s ag i n g e n g i n e . сот heade r . b=Jrsm+pCo ; \ r \ n x- l o c a l - ip=pa s s \ r \nRecei ved : from ma i l o ut . nyi . i n t e rna l ( gat eway l . n y i . i n t e rnal [ 1 0 . 2 0 2 . 2 . 2 2 1 ] ) \ r \ n \ t ( u s i n g TLSvl . 2 w i t h c ipher ECDHE- RSA-AES 2 5 б-GCM-SHA3 8 4 ( 2 5 б / 2 5 б b i t s ) ) \ r \ n \ t ( No c l i ent c e r t i fi c a t e reque s t e d ) \ r \ n \ tby mx 5 . nyi .

rмва 13. Эмкrроннм почта

880

i nt e rn a l ( Po s t f i x ) wi t h ЕSМТ РS id 3 0 BA4 2 8 0 D 8 1 \ r \ n \ t for ; Sun , 6 Ma r 2 0 1 6 1 6 : 1 6 : 0 3 - 0 5 0 0 ( EST ) \ r \ nR e c e i v e d : from comput e 2 . i n t e rna l ( c omput e 2 . ny i . i n t e rna l [ 1 0 . 2 0 2 . 2 . 4 2 ) ) \ r \ n \ tby mai lout . nyi . i n t e rnal ( Po s t f i x ) with ESMT P i d 1 7 4 0 4 2 0 DOA\ r \ n \ t f o r ; Sun , 6 Ma r 2 0 1 6 1 6 : 1 6 : 0 3 - 0 5 0 О ( EST ) \ r \ nRece ived : f rom f rontend2 ( [ 1 0 . 2 0 2 . 2 . 1 6 1 ) ) \ r \ n Ьу сот put e 2 . i n t e rnal ( ME P roxy ) ; S un , О б Ma r 2 0 1 6 1 6 : 1 6 : 0 3 - 0 5 0 0 \ r \n DKI M- S i gnature : v= l ; a=r s a - sha l ; c=re l axed / r e l axed ; d= \ r \ n \tme s s a g i ngeng i n e . com; h=conten t - t ran s f e r - e ncoding : content - t ype \ r \ n \ t : dat e : from : me s s a g e - i d : mime - ve r s i on : subj e c t : t o : x - s a s l -e n c \ r \ n\ t : x - s a s 1 - e n c ; s = smtpout ; bh=P 9 8 NT s E o 0 1 5 suwJ4 g k7 1 knAWLa 4 = ; b=Jr sm+ \ r \n \ t pCovRioQIRyp 8 F l O L 6 JHOI 8 s bZ y 2 obx 702 8 JF2 i T l TWmX3 3Rhl q 9 4 0 3XRklwN 3 JA \ r \ n \ t 7 KS P qМТp3 0Qdx б y I UaADwQqlO+QMuQq / QxBHdj e ebmdhgVfj hqx r zTbSMw w/ ZNhL \ r \n \ t Ywv /QM/ oDHbX i L S Ul B 3Qrg+9ws E / O j U / EOi s iU= \ r \ nX- S a s l - en с : 8 Z J + 4 ZREBAGP zdLRWQFi vGymJb8pa 4 G 9JGcb7 k4 xKn + I 1 4 5 7 2 9 8 9 6 2 \ r \ nRe c e i ved : from [ 1 9 2 . 1 6 8 . 1 . 1 4 ) ( 7 5 - 1 3 7 - 1 - 3 4 . dhcp . nwnn . ga . ch a r t e r . co m [ 7 5 . 1 3 7 . 1 . 3 4 ) ) \ r \ n \ t by ma i l . me s s ag i n g e n g i n e . com ( Po s t f i x ) with ЕSМТ РА id C O B 3 6 6 8 0 1 C D \ r \ n \ t f o r ; Sun , 6 Ma r 2 0 1 6 1 6 : 1 6 : 0 2 - 0 5 0 0 ( EST ) \ r \ nFrom : Doug Hel lmann \ r \ nContent -Type : t e xt /pl a i n ; cha r set=us-a s c i i \ r \nC ontent-Trans f e r -Encodi ng : 7 b i t \ r \ nSubj e c t : PyMOTW Examp l e me s s ag е 2 \ r \ nMes s a g e - I d : < 0 0AВCD4 6 - DADA- 4 9 1 2 -A4 5 1 - D2 7 1 6 5BC3A2 F@doughel lmann . com> \ r \nDate : Sun , 6 Mar 2 0 1 6 1 6 : 1 6 : 0 2 - 0 5 0 0 \ r \nTo : Doug Н e l lmann \ r \ nMime -Ve r s i o n : 1 . 0 ( Ма е OS Х М a i l 9 . 2 \ \ ( 3 1 1 2 \ \ ) ) \ r \ nX-Ma i le r : App l e Ma i l ( 2 . 3 1 1 2 ) \ r \ n \ r \n ' BODY ТЕХТ : b ' Thi s i s t he s e cond exampl e me s s age . \ r \ n ' FLAGS : Ь ' 1 ( FLAGS ( ) ) ' ()

1 3 .4. 1 0 .

Извлечение всего сообщения

Как было показано ранее, клиент может запрашивать у сервера конкретные части сообщений по отдельности. Также существует возможность извлечения все­ го почтового сообщения целиком, отформатированного в соответствии с требо­ ваниями документа RFC 822 1! \ и его анализа с помощью классов, содержащихся в модуле ema i l . Листинг 1 3.32. imaplib_fetch rfc8 2 2 . ру _

impo rt imap l i b import ema i l import ema i l . pa r s e r impo r t imapl i b_connect

with imapl ib_connect . open_c onne c t i on ( ) a s с : c . s e l e c t ( ' I NBOX ' , readon l y=T rue )

15

https : / / t o o l s . i e t f . o r g /html / r f с в 2 2

13.4. lmapllb: КАМмm:tеаА би6Аисnеа 1МАР4

88 1

t yp , ms g_dat a c . fe t ch ( ' l ' , ' ( R FC8 2 2 ) ' ) for r e sponse_part i n ms g_da t a : i f i s i ns t ance ( re sponse_part , t upl e ) : ema i l_pa r s e r ema i l . pa r s e r . Byt e s FeedPa r s e r ( ) ema i l_pa r s e r . feed ( re spon s e_pa rt [ l ] ) msg ema i l_pa r s e r . c l o s e ( ) for heade r i n [ ' subj ect ' , ' t o ' , ' from ' ] : p r i nt ( ' { : " 8 } : { } ' . fo rmat ( heade r . upp e r ( ) , msg [ he ade r ] ) ) =

=

=

Предлагаемый модулем ema i l синтаксический анализатор значительно упро· щаст доступ к сообщениям и манипулирование ими. В этом приме ре выводятся лишь несколько заголовков для каждого сообщения. $ pythonЗ imapl ib_fetch_r f c8 2 2 . py SUBJECT

то

FROM

PyMOTW Examp l e me s sage 2 Doug H e l lmann Doug H e l lmann

1 3 .4. 1 1 . Выгрузка сообщений Чтобы добавить в почтовый ящик новое сообщение, нужно создать экземпляр Mes sage и передать его методу append ( ) вместе с временной меткой. Листинr 1 3.33. imaplib_append . ру imp o r t import import import

imap l i b t ime ema i l . me s sage i map l i b_connec t

new_me s s age ema i l . me s s a g e . Me s s age ( ) new_me s s a g e . s e t_uni x f rom ( ' pymotw ' ) new_me s s a ge [ ' Subj e ct ' ] ' subj e ct g o e s he r e ' new_me s s a g e [ ' From ' ] ' pymot w@examp l e . c om ' new_me s s a g e [ ' T o ' ] ' examp l e @ exampl e . com ' new_me s s a g e . s e t_payload ( ' Th i s i s the body of the me s s age . \ n ' ) •

=

=

=

p r i nt ( ne w_me s s a ge ) w i t h i map l i b_connect . open_conne c t ion ( ) as с : с . append ( ' INBOX ' , ' ' , imapl i b . Time 2 I nt e rnaldat e ( t i me . time ( ) ) , s t r ( new_me s s age ) . encode ( ' ut f - 8 ' ) ) # Отобразить загол ов ки дл я в с е х соо бщений в почто вом ящике с . s e l e c t ( 1 INBOX ' ) t yp , [ms g_i ds ] c . s e arch ( None , ' ALL ' ) for num i n ms g_i ds . sp l i t ( ) : t yp , msg_da ta = c . fetch ( num, ' ( BODY . PEEK [ HEADER ] ) ' ) for response_pa rt i n ms g_da t a : i f i s i n s t a n c e ( response_p a r t , tup l e ) : =

rмва 13. ЭмкrронН8'1 почта

882 p ri nt ( ' \n { ) : ' . format ( num) ) p r i nt ( response_pa rt [ l ] )

В этом примере полезной нагрузкой (payload) является тело сообщения в виде простого текста. Класс Me s s a g e поддерживает также МIМЕ-сообщения с состав­ ным типом содержимого. $ python3 imapl i b_append . py Subj e ct : s uЬj e c t goe s h e re From : pymotw@exampl e . com Т о : examp l e @examp l e . com Thi s is the body o f the me s s a g e . Ь' 1' : b ' Return- Path : < doug @doughel lmann . com> \ r \nRe ceived : from comput e 4 . i nt e rnal ( comput e 4 . nyi . i nt e rnal [ 1 0 . 2 0 2 . 2 . 4 4 ) ) \ r \n \ t Ьу s l o t i 2 бt O l ( Cyrus 3 . 0 . 0 -be t a l - g i t - fas tmai l - 1 2 4 1 0 ) with LМТ PA; \ r \ n \ t Su n , 06 Ma r 2 0 1 6 1 6 : 1 6 : 0 3 - 0 5 0 0 \ r \ nX - S i eve : CMU S i eve 2 . 4 \ r \ nX-Spa m- known- s e nde r : yes , fadd l c f 2 -dc 3 a - 4 9 8 4 -a 0 8 b - 0 2 c e f 3 c f 1 2 2 1 = " doug" , \ r \ n e a 3 4 9ad0 - 9 2 9 9 - 4 7b5-b 6 3 2 - б f f l e 3 9 4 c c 7 d= " bot h hel l f l y " \ r \ nXSpams c o re : 0 . 0 \ r \ nX- Spam-hi t s : ALL_TRU S T E D - 1 , BAYES_O O - 1 . 9 , L ANGUAGES un known , BAYES_USED g l obal , \ r \ n SA_VERS I ON 3 . 3 . 2 \ r \ nXSpams ou r c e : I P= \ ' 1 2 7 . 0 . 0 . 1 \ ' , Ho s t = \ ' un k\ ' , Count ry= \ ' un k \ ' , Fr omНeader= \ ' com\ ' , \ r \ n Ma i l From= \ ' c om\ ' \ r \ nX- Spam- cha r s e t s : p l a i n= \ ' u s - a s c i i \ ' \ r \ nX-Resol ved-t o : dough e l lmann@ fa stmai l . fm\ r \ nX - D e l i ve red- t o : doug @doughe l lmann . com\ r \ nX-Ma i l - from : doug@dough e l l mann . com\ r \ nRece i ve d : f rom mx 5 ( [ 1 0 . 2 0 2 . 2 . 2 0 4 ) ) \ r \ n Ьу c omput e 4 . i nt e rnal ( LMT P P roxy ) ; Sun , О б Ma r 2 0 1 6 1 6 : 1 6 : 0 3 - 0 5 0 0 \ r \ nR e c e i v e d : from mx 5 . nyi . i n t e rnal ( l o c a l ho s t [ 1 2 7 . 0 . 0 . 1 ) ) \ r \ n \ tby mx 5 . ny i . i nt e rnal ( Po s t f i x ) with ESMT P i d 4 7 CBA2 8 0 DB 3 \ r \ n \ t f o r ; Sun , 6 Ma r 2 0 1 6 1 6 : 1 6 : 0 3 - 0 5 0 0 ( EST ) \ r \ nRece i v e d : from mx 5 . nyi . i nt e rnal ( l ocalhost [ 1 2 7 . 0 . 0 . 1 ) ) \ r \ n Ь у mx 5 . nyi . i nte rnal ( Authe nt i ca t i on Mi l t e r ) w i t h ESMT P \ r \ n id А7 1 7 8 8 6 8 4 6E . 3 0 BA4 2 8 0 D 8 1 ; \ r \ n Sun , 6 Ma r 2 0 1 6 1 6 : 1 6 : 0 3 - 0 5 0 0 \ r \ nAuth e n t i cat i on-Re sul t s : mx 5 . nyi . i nt e rnal ; \ r \ n dkim=pa s s ( 1 0 2 4 -bi t r s a ke y ) heade r . d=me s s a g i ngengi ne . c om heade r . i =@me s s a g i n g e n g i n e . сот heade r . b=Jrsm+pCo ; \ r \ n x - l o ca l - i p=pa s s \ r \nReceived : from ma i l out . ny i . i n t e rnal ( gat ewa y l . nyi . i nt e rnal [ 1 0 . 2 0 2 . 2 . 2 2 1 ) ) \ r \ n \ t ( us i ng TLSvl . 2 with c i ph e r ECDHE- RSA-AES2 5 6 - GCM-SHA3 8 4 ( 2 5 6 / 2 5 6 b i t s ) ) \ r \ n \ t ( No c l i ent c e rt i fi ca t e reque s t ed ) \ r \ n \ tby mx 5 . nyi . i n t e rnal ( Po s t f i x ) w i t h ESMT PS i d 3 0 BA4 2 8 0 D 8 1 \ r \ n \ t fo r < doug@dou ghel lmann . com> ; Sun , 6 Ma r 2 0 1 6 1 6 : 1 6 : 0 3 - 0 5 0 0 ( EST ) \ r \ nRe c e i v e d : f rom c omput e 2 . i nt e rnal ( comput e 2 . nyi . i n t e rnal [ 1 0 . 2 0 2 . 2 . 4 2 ) ) \ r \ n \ tby ma i l out . nyi . i n t e rna l ( Po s t fi x ) w i t h ESMT P id 1 7 4 0 4 2 0 DOA\ r \ n \ t fo r < dou g@dough e l lmann . com> ; Sun , 6 Ma r 2 0 1 6 1 6 : 1 6 : 0 3 - 0 5 0 О ( EST ) \ r \ nRe c e i ved : f rom frontend2 ( [ 1 0 . 2 0 2 . 2 . 1 6 1 ) ) \ r \ n Ь у com put e 2 . inte rnal ( ME P rox y ) ; Sun , 0 6 Ma r 2 0 1 6 1 6 : 1 6 : 0 3 - 0 5 0 0 \ r \ nDKI M - S i gnature : v= l ; a = r s a - s ha l ; c=re laxed / re l axed ; d= \ r \ n\ tme s s a g i ngeng i n e . com; h=con t e nt - t ra n s f e r - encodi ng : c on t e n t - t ype \ r \ n \ t : dat e : from : me s s ag e- i d : mime -ve r s i on : subj e c t : t o : x - s a s l - e nc \ r \ n \ t : x- s a s

883

13.4. lmapllb: К1\МН1'С1СМ 6м6Аисnека IMAP4 1 - enc ; s=smtpout ; bh= P 9 8NTsEo 0 1 5 s uwJ4 gk7 l knAWLa 4 = ; b=Jrsm+ \ r \n \ t pCovRi oQI Ryp 8 F l O L 6JHOI 8 sbZy2obx 7 02 8 JF2 i T lTWmX 3 3 Rh l q 9 4 0 3XRk l wN 3 JA \ r \n\ t 7 KS P qМТp3 0Qdx 6yI UaADwQqlO+QMuQq / QxBHdj e ebmdhgVfj hqxr zTbSMw w / ZNhL \ r \n \ t Ywv / QM/ oDHbXi LSU l B3Qrg+ 9ws E / O j U / EOi s iU= \ r \nX - S a s l - en с : 8 ZJ+ 4 ZRE 8AG P zdLRWQFi vGymJЬ 8 p a 4 G 9 JGcb 7 k 4 xKn+ I 1 4 5 7 2 9 8 9 6 2 \ r \nRe c e i ved : f r om ( 1 9 2 . 1 68 . 1 . 1 4 ) ( 7 5- 1 3 7 - 1 - 3 4 . dhcp . nwnn . ga . cha r t e r . co m ( 7 5 . 1 3 7 . 1 . 3 4 ) ) \ r \n\tby mai l . me s s ag i ngengine . com ( Po s t f i x ) w i t h Е SМТ РА id C O B 3 6 6 8 0 1CD\ r \n \ t f o r ; Sun, 6 Ma r 2 0 1 6 1 6 : 1 6 : 0 2 - 0 5 0 0 ( EST ) \ r \nFrom : Doug H e l lmann \ r \nCoпt ent-Type : t ext /pl a i n ; cha rset=us -a s c i i \ r \nC ontent -Trans f e r - Encodi ng : 7bi t \ r \nSubj e c t : PyMOTW Ex amp l e mes s a g е 2 \ r \nМe s s a ge - I d : < O OABCD4 6 - DADA- 4 9 1 2 -A4 5 1 - D2 7 1 6 5BC 3A2 F@doughe l lmann . com> \ r \nDat e : S u n , 6 Mar 2 0 1 6 1 6 : 1 6 : 0 2 - 0 5 0 0 \ r \nTo : Doug Н e l lmann < doug@dough e l l maпп . c om> \ r \nMime-Ve r s i o n : 1 . 0 ( Мае OS Х М a i l 9 . 2 \ \ ( 3 1 1 2 \ \ ) ) \ r \ nX-Mai l e r : App l e Ma i l ( 2 . 3 1 1 2 ) \ r \ n \ r \ n ' Ь'2' : b ' Subj e c t : s ubj e c t goe s h e re \ r \ n From : p ymotw@examp l e . com\ r \nTo : e xamp l e @ exampl e . com\r \ n \ r \ n '

1 3.4. 1 2 . Перемещение и копирование сообщений Как только сообщение оказалось на сервере, его можно перемещать и копиро­ вать с помощью методов move ( ) и сору ( ) так, как если бы оно располагалось на локальном компьютере. Как и метод fetch ( ) , эти методы могуг работать с диапа­ зонами идентификаторов сообщений. Листинг 1 3 .34. imapliЬ_archive_read . py imp o r t imapl ib i mport imap l i b_connect with imap l ib_conпec t . open_conne c t i on ( ) a s с : # Най ти сообщения с флагом " S EEN" в папке INBOX с . s e l e c t ( ' INBOX 1 ) t yp , [ re spon s e ] c . s e a rch ( None , ' SEEN ' ) i f t yp ! = ' ОК ' : r a i s e Run t i meE r r o r ( response ) ms g_i d s ' , ' . j o i n ( r e spons e . decode ( ' ut f - 8 ' ) . sp l i t ( ' =

=

# Со здать новый почтовый ящик " Exampl e . Today" t yp , c r e a t e_respons e = c . c re a t e ( ' Examp l e . Today ' ) p r i n t ( ' CREATED Examp l e . Today : ' , c reat e_response ) # Копиров ать сообщения print ( ' COPYING : ' , ms g_i d s ) c . copy ( ms g_ids , ' Examp l e . Today ' ) # Про смо треть резул ь т а ты с . s e l e c t ( ' Examp l e . Today ' ) t yp , [ re spon s e ] = c . s ea r ch ( None , p r i n t ( ' CO P I ED : ' , r e s pons e )

' ALL ' )

' ) )

Гм• 13. ЭмlОРОНная ПОЧ1'8

884

Этот сценарий создает новый почтовый ящик в папке Examp l e и копирует в него все прочитюшые сообщения из папки INBOX.

$

pythonЗ imapl ib_a rchi ve_re ad . py

C REATED Exampl e . Today : COPYING : 2 COPIED : b ' l '

[ b ' Comp l e t ed ' ]

Повторное 11ыполнепие этого же сценария показывает важность проверки ко­ дов завершения . Вместо возбуждения исключения при вызове метода create ( ) для создания 1ю1юго почтового ящика выводится сообщение, извещающее о том , что п о что вый ящик с указанным именем уже существует.

$

pythonЗ imapl ib_a rchi ve_re ad . py

C REATED Examp l e . Toda y : 1] COPYING : 2 COP I E D : b ' l 2 '

[ Ь ' [ ALREADYEX I ST S ] Mai lbox al ready e x i s t s

1 3 .4. 1 3 . Удаление сообщений Несмотря на то что многие современные клиенты используют модель "корзи­ ны" для работы с удаленными сообщениями, сообщения редко отправляются n фактическую папку корзины. Вместо этого набор их флагов обновляется за счет добавления флага \ De l eted. Операция "опустошения" корзины выполняется но­ средс·гвом команды EX PUNGE. В следующем примере выполняется поиск в ' l i s t ' 1 ) => ' pr i n t ' 2 ) => ' s t a r t ' 3 ) => ' s top ' 4 ) => None mat che s : [ ' l i s t ' , 0 ) => ' l i s t ' 1 ) => ' pr i n t ' 2 ) => ' s t a r t ' 3 ) => ' s t op ' 4 ) => None

' pr i n t ' ,

' s t a rt ' ,

' s t op ' ]

' pr i n t ' ,

' start ' ,

' s t op ' ]

Первая последовательность является результатом первого нажатия клавиши . Алгоритм автозавершения запрашивает все варианты продолжения, но не расширяет пустую строку ввода. После второго нажатия клавиши список возможных вариантов продолжения заново пересчитывается, чтобы его можно бьvю вывееги для пользователя. Если далее ввести букву 1 и нажать клавишу , будет получен следующий вывод: Prompt

( " s t op " to qui t ) : l i s t

Теперь в журнале отображаются другие аргументы для передачи методу cornplete ( ) . ' 1 ' ma t che s : [ ' l i s t ' ] comp l e t e ( ' l ' , 0 ) => ' l i s t ' comp l e t e ( ' l ' , 1 ) => None

В результате нажатия клавиши метод input ( ) вернет значение, и цикл wh i l e продолжит свою работу. Dispatch l i s t Prompt ( " s t o p " t o qui t ) :

Команду, начинающуюся с буквы s , можно завершить двумя способами. В ре­ зультате ввода буквы s и последующего нажатия клавиши отображаются

ГАНа 14. CтpoмntAWtwe бАсжм прможенмii

два варианта продолжения - s tart и s t op, но средство автозавершения доrюлня· ет текст на экране лишь буквой t. В файле журнала отображается следующая информация. ' s ' ma tche s : [ ' st a rt ' , ' s t op ' ] comp l e t e ( ' s ' , 0 ) => ' s t a r t ' comp l e t e ( ' s ' , 1 ) => ' s t op ' comp l e t e ( ' s ' , 2 ) => None

Теперь на экране генерируется следующий вывод: Prompt ( " s t o p " to qu i t ) : s t Примечание

Если метод comp l e t e r ( ) возбуждает исключение, оно игнорируется, и модуль предполагает, что подходящие варианты дл я продолжения ввода отсутствуют.

read l i n e

1 4.З.З. Доступ к буферу автозавершения ввода Алгоритм автозавершения ввода, предусмотренный в классе S irnpleCornpleter, просматривает лишь текстовый аргумент, переданный функции, но никак н е ис· пользует любую другую информацию о внутреннем состоянии модуля read l i ne. Функции модуля readl ine можно использовать также для манипулирования тек· стом , находящимся в буфере ввода. Листинг 1 4.37. readline_buffer . ру t ry : i mport gnurea d l i n e a s readl i n e except Impo rtErro r : impo rt readl i n e import l og g i ng LOG F I LENAМE = ' / tmp / comp l e t e r . l o g ' l o g g i n g . ba s i cCon f i g ( format= ' % ( me s s a ge ) s ' , f i l ename=LOG_FI LENAМE , l eve l = l og g i ng . DEBUG ,

c l a s s Buf f e rAwa reComp l e t e r : def

init ( s e l f , opt i on s ) : s e l f . opt i o n s opt i o n s s e l f . current candida t e s = [ ] =

def compl e t e ( s e l f , t ext , s t a t e ) : re spon s e = None i f state == О : # Э то первое о бра щение к данному т ексту , # по э тому создаем п олный с писок вариантов

14.З. readllne: бмб.\мсmнсв QNU Readllne

927

o r i g l ine = readl ine . get_l i n e_bu f f e r ( ) b e g i n = readl i n e . g e t_begidx ( ) end = readl i ne . g e t endidx ( ) b e i n g_comp l e t ed = o r i g l i n e [ be g i n : end] wo rds o r i g l i n e . sp l i t ( ) =

l og g i n g . debug ( l og g i ng . debug ( l o g g i n g . debug ( l og g i n g . debug ( l og g i n g . debug (

' o r i g l ine=% s ' , repr ( o r i g l i ne ) ) ' be g i n= % s ' , b e gi n ) ' e nd= % s ' , end ) ' b e i n g_comp l e t e d= % s ' , being_comp l e t e d ) ' words= % s ' , words )

i f not words : s el f . current candida t e s s e l f . opt i on s . keys ( )

s o rted (

else : try : i f b e g i n == О : # Первое сло во candida t e s = s e l f . opt ions . keys ( ) else : # П о сле д нее сло в о f i r s t = words [ O J candida t e s s e l f . op t i o n s [ f i r s t ] =

i f b e i n g_comp l e t ed : # Сопо ставить варианты с завершаемой # частью в в ода s e l f . current candidates w for w in candi dat e s i f w . s t a r t s w i t h ( be i n g_comp l e t ed ) =

else : # При сопоставлении с п усто й строкой # и споль з овать в с е по дхо д ящие варианты candida t e s s e l f . current candida t e s =

logging . debug ( ' cand i d a t e s = % s ' , s el f . curre nt_candida t e s ) except ( Ke yEr r o r , I nde x E r ro r ) as e r r : l og g i n g . e r r o r ( ' comp l e t i on e r r o r : % s ' , e r r ) [) s el f . current candida t e s try : response = s e l f . current_candida t e s [ s t a t e ] except I ndexErro r : re sponse = None l o g g i n g . debug ( ' comp l et e ( % s , % s ) = > % s ' , repr ( t ext ) , s t at e , re sponse ) return response

928 def i nput_l oop ( ) : l i ne = ' ' whi l e l ine ! = ' s t op ' : l i ne i nput ( ' Prompt ( n s t o p " t o qui t ) : p r i n t ( ' D i s pa t ch { } ' . fo rma t ( l i ne ) ) =

')

# Зарегистрировать фун кцию завершения в вода comp l e t e r BufferAwa reComp l e t e r ( { ' l i s t ' : [ ' f i l e s ' , ' di r e c t o r i e s ' ] , ' pr i nt ' : [ ' byname ' , ' by s i z e ' ] , ' s t op ' : [ ] , )) readl i n e . s e t_compl e t e r ( complet e r . compl e t e ) =

# И споль зо вать кла вишу < t ab> для завершения ввода readl ine . parse_and_b i пd ( ' t ab : comp l e t e ' ) # Предложить поль зователю в вести те кст i nput_l oop ( )

В этом примере выполняется автозавершение команд с дополнительными оп­ циями. Метод cornplete ( ) должен просмотреть позицию завершаемого текста во входном буфере, чтобы определить, является ли он частью первого или последне­ го слова. Если обнаружено совпадение с первым словом, то в качестве возможных вариантов используются ключи словаря опций. Если это не первое слово, то пер­ вое слово используется для поиска подходящих вариантов из словаря опц и й. В дан ном случае имеются три команды верхнего уровня, из которых две име­ ют подкоманды. •

list - fi l e s - directories



pri nt - byname - bysize



s top

Если действовать в той же последовательности, что и ранее, то в результате двукратного нажатия клавиши отобра.1ятся три команды верхнего уровня. $ pythonЗ read l ine_buffe r . py Prompt ( " s t op " to qu i t ) : l i s t p r i nt s t op Prompt ( " s t op " to qui t ) : l i s t p r i n t s t op Prornpt ( " s topn to qui t ) :

Журнал включает следующую информацию.

14.З. reedllne: бмбN4сmнса GNU ReadHne

o r i g l i ne= ' ' b e g i n= O e nd=O b e i ng_comp l e t ed= wo rds= [ ] c omp l e t e ( ' ' , 0 ) => c omp l e t e ( ' ' , 1 ) = > c omp l e t e ( ' ' , 2 ) = > comp l e t e ( ' ' , 3 ) => o r i g l i ne= ' ' begin=O e nd=O b e i ng_comp l e t e d= words= [ ] comp l e t e ( ' ' , 0 ) = > comp l e t e ( ' ' , 1 ) => comp l e t e ( ' ' , 2 ) = > comp l e t e ( ' ' , 3 ) = >

929

list print s t op None

list print s t op None

Если первым введенным словом является ' l i s t ' (с пробелом в ко нц е ) , то ва­ рианты для заверше н ия команды будут различными. Prompt ( " s t o p " t o qui t ) : l i s t d i re c t o r i e s f i l e s

В журнале отражается тот факт, что завершаемый текст строку, а является лишь частью, следующей после слова l i s t.



образует полную

o r i g l ine= ' l i s t ' b e g i n= S e nd= S b e i ng_comp l e t ed= words= [ ' l i s t ' ] candi dat e s = [ ' f i l e s ' , ' di r e ct o r i e s ' J comp l e t e ( ' ' , 0 ) => f i l e s comp l e t e ( ' ' , 1 ) => d i r e c t o ri e s c omp l e t e ( ' ' , 2 ) = > None o r i g l ine= ' l i s t ' begin=S e nd=S b e i ng_comp l e ted= words= [ ' l i s t ' ] cand i da t e s = [ ' f i l e s ' , ' di rect o ri e s ' ] c omp l e t e ( ' ' , 0 ) = > f i l e s c omp l e te ( ' ' , 1 ) => d i r e c t o r i e s c omp l e t e ( ' ' , 2 ) => None

1 4. З .4.

История ввода

Модуль readl ine автоматически отслеживает историю ввода. Для работы с историей ввода можно использовать два набора функций. Доступ к истори и вво-

930

да текущего сеанса осуществляется с помощью функций ge t_current_h i s tory_ length ( ) и ge t_h i s tory_i t em ( ) . Историю можно сохранить в файле, а впослед­ ствии загрузить, используя функции w r ite_hi s tory_fi l e ( ) и read_h i s t ory_ file ( ) соответственно. По умолчанию сохраняется вся история ввода, но с помощью функции s e t h i s tory l ength ( ) можно установить максимал ьную дли­ ну файла. Значению - 1 соответствует отсуrствие ограничений. _

_

Листинг 1 4. 38. readl ine_hi s to ry . py try: import gnureadl i ne a s readl i n e e x c ept Impor t E r ro r : import read l i ne import l ogging import o s LOG FI LENAМE ' / tmp / comp l e t e r . l og ' H I S TORY_FI LENAМE ' / tmp/ comp l et e r . h i s t ' =

=

l og g i ng . ba s i cConf i g ( forma t = ' % ( me s sage ) s ' , f i l e name=LOG_FI LENAМE , l ev e l = l o g g i n g . DEBUG ,

def get_h i s t o r y_i t ems ( ) : num_it ems readl i n e . ge t_cur r ent_h i s t o r y_l ength ( ) + 1 return [ readl ine . get_h i s t o r y_ i t em ( i ) f o r i i n rang e ( l , num_ i t ems ) =

c l a s s H i s t o r yCompl e t e r : def

ini t ( sel f ) : s e l f . ma t ches [] =

d e f c omp l e t e ( se l f , t ex t , s tat e ) : r e spon se None if state О: h i s t o ry_va l u e s = get_h i s t o r y_items ( ) l o g g i ng . debug ( ' h i s t o r y : % s ' , h i s t o ry_va l u e s ) i f t ext : s e l f . ma t ch e s s o rt ed ( h for h in h i s t o r y_va l u e s i f h and h . s t a r t s w i t h ( t ext ) =

==

=

else : s el f . ma t ch e s [] l og g i ng . debug ( ' ma t che s : % s ' , s e l f . ma t che s ) =

14.З. reedПne: 6м&мсmt1С11 GNU Rndllne

931

try: response = s e l f . ma t che s [ s t a t e ] ex cept I ndexErro r : re s p o n s e = None l ogging . debug ( ' comp l e t e ( % s , % s ) => % s ' , repr ( t ex t ) , s t a t e , re p r ( re s pon s e ) ) re t urn r e s pon s e d e f i nput_l oop ( ) : i f o s . pa t h . ex i s t s ( H I STORY F I LENAМE ) : re a d l i n e . read_h i s t o r y f i l e ( H I STORY_ F I LENAМE ) p r int ( ' Мах h i s t o r y f i l e l e n g t h : ' , readl i n e . get_h i s t o ry_l ength ( ) ) print ( ' S t a r tup hi s t o ry : ' , get_h i s t o ry_i t ems ( ) ) try: wh i l e т rue : l i ne i nput ( ' Prompt ( " s t o p " t o qu i t ) : ' ) i f l i ne == ' s t op ' : break i f l i ne : p r i n t ( ' Adding { ! r } t o the hi s t o r y ' . fo rma t ( l i ne ) ) f i na l l y : p r i n t ( ' Fi n a l h i s t o r y : ' , get_hi s t o ry_it ems ( ) ) read l i n e . wri t e_h i s t o ry_ f i l e ( H I STORY_FI LENAМE )

=

=

# Заре гистриров а т ь фу н кцию зав ершения ввода read l i n e . s et_compl e t e r ( H i s t or yC omp l e t e r ( ) . c ompl e t e ) # Исnоль зовать клавишу < t ab> дл я завершения ввода read l i n e . pa r s e_a nd_bind ( ' t ab : comp l e t e ' ) # Предложить поль зователю вве сти текст i nput_l oop ( )

Класс H i s toryCornpleter за п оминает все, что вводит пользователь, и исполь­ зует эти значения для автозавершения последующего ввода.

$

pythonЗ read l i n e_h i s t o r y . py

Мах h i s t o ry f i l e l e ngth : - 1 S t a r t up hi s t o r y : [ ] Prompt ( " s t op " t o qui t ) : foo Adding ' foo ' t o t h e h i s t o r y P rompt ( " s t op" t o qui t ) : bar Addi ng ' ba r ' t o the h i s t o r y P rompt ( " s top" t o qui t ) : Ы а h Adding ' Ы аh ' t o t h e h i s t o r y Prompt ( " s t op" t o qui t ) : Ь ba r Ы аh Prompt ( " s t op " to qui t ) : Ь Prompt ( " s t o p " t o qui t ) : s t op F i n a l h i s t or y : [ ' foo ' , ' ba r ' , ' Ы аh ' ,

' s t op ' ]

932

П осле ввода буквы Ь и двукратно ю нажатия клавиши в жур н ал е отобра­ жаетс я следу ю щая информа ция. h i s to r y : ma t che s : compl e t e compl e t e compl e t e history: ma t che s : compl e t e c ompl e t e compl e t e

( ( (

( ( (

[ ' f oo ' , ' ba r ' , ' Ы аh ' ] [ ' ba r ' , ' Ы а h ' ] ' b ' , 0 ) => ' ba r ' 'b' , 1) > ' Ы аh ' ' b ' , 2 ) = > None [ ' f oo ' , ' ba r ' , ' Ьl ah ' ] [ ' ba r ' , ' Ьl ah ' ] ' b ' , О ) => ' ba r ' ' b ' , 1 ) => ' Ы аh ' ' b' , 2 ) > None =

=

Пр и последующем запуске :-этого сценария вся исто рия ввода ч итаетс я из файла. $ pyt honЗ read l i n e_h i s t o r y . py Мах h i s t o r y f i l e l e n g t h : - 1 S t a r t up h i s t o r y : [ ' f oo ' , ' ba r ' , Pr ompt ( " s t o p " t o qui t ) :

' Ы аh ' ,

' s t op ' ]

Также доступны фу н кции, обеспечиваю щие удал е н ие отдел ьн ых элементов истории и ее полную очистку.

1 4.3.5. Функции-перехватчики Действия , являю щ иеся часть ю и нте рактивного взаимодей ствия, можно ини­ ци и ровать с помощью функ ций-п е рехватчиков. Перехватчик запуска вызыва­ ется непосредственно п еред в ы водом интерактивной подсказки , п ерехватчик предввода - после ее вывода, но до чтения текста, вводимо го пользователем. Листинr 1 4.39. readline_hooks . py try : impo r t g nu r e adl i n e as r e a d l i n e e xcept I mpor t E r ro r : impo r t r eadl i ne

def s t a r t up_hoo k ( ) : readl i n e . i n s e rt_t e x t ( ' f r om s t a r t up_hoo k ' )

d e f pre i nput hoo k ( ) : re ad l i n e . in s e rt_t e x t ( ' f r om p r e_i nput_hook ' ) readl i n e . r e d i spl a y ( )

r e adl i n e . s et_s t a r t up_hoo k ( s t a r tup_ho o k ) r e adl i n e . s e t p r e i nput hook ( pr e i nput hoo k ) readl ine . par;- e_a �d_Ь i nd ( ' t ab : compl e t e ' )

933 wh i l e T rue : l i ne i nput ( ' P rompt ( " s t op " to qui t ) : i f l i ne ' s t op ' : break p r i n t ( ' ENTERED : { ! r } ' . fo rma t ( l i ne ) ) =

' )

==

Л юбая из :�тих фу нкций п редоставляет отличн ую во зм ожн о сть ис пользо вать функцию insert text ( ) для изменения буфер а ввода. _

$

pythonЗ readl i ne_hoo ks . py

Pr ompt ( " st o p " t o qu i t ) : from s t a r t up_hook f r om pre_i nput_h o o k

Если буфер изменен в теле пе рехватчика п редввода, то необходимо обн о ви ть эк ра н , вызвав функцию redisplay ( ) . Дополнительные ссылки • • •



Раздел документации стандартной библиотеки, посвященный модулю read l i n e4. GNU Readline Librar/. Документация библиотеки GNU Readline. Readline init file formaf'. Описание формата файла инициализации и конфигурирования библиотеки Readline. Сайт EffЬot: The readline module7. Руководство Effbot по использованию модуля readl i ne.









8 gnuread l i n e . Версия read l i ne, статически связанная с библиотекой GNU Readline, доступная для многих платформ и устанавливаемая посредством программы pi p. 9 pyr e a d l i n e • Модуль Python, предоставляющий GNU Readline API для использования на платформах Windows. cmd (раздел 1 4. 5). Модуль cmd интенсивно использует модуль read l i n e для реализа­ ции автозаверwения ввода при работе с командным интерфейсом. Некоторые при­ меры, приведенные в этом разделе, были взяты в адаптированном виде из раздела, посвященного модулю cmd. rl c omp l e t e r . Данный модуль использует модуль read l i ne для добавления возможно­ стей автозаверwения ввода в интерактивный интерпретатор Python.

1 4.4. getpas s : безопасный ввод пароля Во многих случ аях пользовател ю , взаимодействующему с про г р аммой ч ер е з те р минал, п р иходится вводить п ароль, и в подо б н ы х ситуациях никогда нс п оме­ ш ает позволить делать это таким образом, чтобы никто из п осторонних не смо г подсмотр еть е го на экране . И менно такой бс:юпасный способ ввода п а роля о бе­ спечивает модуль ge tpas s . 4 5 6 1 8 9

https : / /doc s . python . org / 3 . 5 / l ib r a r y / readl i ne . html h t tp : / / t i swww . ca s e . edu /php / che t / readl i n e / readl i ne . html h t tp : / / t i swww . ca s e . e du /php/ che t / readl i n e / readl i ne . html # SEC l O h t tp : / / sandbox . e f fbot . org / l i bra rybook/read l i ne . htm h t t p s : / /pyp i . python . org/pyp i /gnu r e a d l i n e h t tp : / / i python . or g / pyreadl ine . html

934

1 4.4. 1 . Пример Функция ge tpa s s ( ) выводит подсказку, а затем читает вводимый пользовате­ лем текст, пока не будет нажата клавиша . Введенный текст возвращается вызывающему коду в виде строки. Листинг

1 4.40. getpas s defaul ts . ру _

import ge t pa s s try : ge tpa s s . getpas s ( ) р except Except ion a s e r r : p r i nt ( ' ERROR : ' , e r r ) else : p r i nt ( ' You e n t e red : ' , р ) =

По умолчанию выводится подсказка "Password:", если в вызывающем коде не был определен другой текст. $ pythonЗ getpas s_de fau l t s . py Pas swo rd : You entered : s e kr e t

Вместо этой подсказки можно использовать любой другой подходящий текст. Листинг 1 4.41 . g e t p a s s_p romp t . py

import ge tpa s s ge tpa s s . getpa s s ( prompt = ' What i s y o u r favo r i t e c o l o r ? ' ) р i f p . l owe r ( ) == ' Ьl ue ' : p r i n t ( ' Ri gh t . Off you go . ' ) else : p r i n t ( ' Auuuuugh ! ' ) =

С целью повышения безопасности некоторые программы требуют ввода це­ лой ф разы , а не просто слова-пароля. $ pythonЗ ge tpa s s_prompt . py Wha t i s your favo r i t e c o l o r ? Right . Off y o u go . $ pythonЗ getpas s_prompt . py Wha t i s your favo r i t e c o l o r ? Auuuuugh !

По умолчанию функция getpas s ( ) использует для вывода строки стандарт­ ный поток sys . s tdout . В случае программ, которые выводят в поток s y s . stdout полезную информацию, подсказку лучше направлять в другой поток, например sys . stde r r .

14.4. getpвu: беэоnвсный •ВОА паром

935

Листинг 1 4.42. getpas s_ stream . py impor t getpa s s impo r t s y s р = g e t pa s s . getpa s s ( s t ream= s ys . s tde r r ) p r i n t ( ' You e n t e red : ' , р )

Испош.зование потока sys . s tderr для вывода подсказки позволяет перена­ править сгандартн ый п оток вывода в канал или файл. В этом случае значение, введенное пользователем, не будет отображаться на экране. $ pytho n З ge t pa s s_s t ream . py > /dev /nul l Pas sword :

1 4.4.2. Использование функции qe tpas s О без терминала В Uнix, для того чтобы можно было отключить эхо-отображение ввода, функ­ ции ge tpa s s ( ) требуется терминал ( tty) , которым она могла бы управлять через интерфейс , предоставляемый модулем terrnios. При таком подходе значения , поступающие из нетерминального потока, перенаправленного на стандартный ввод, не будут читаться . Вместо этого функция getpa s s ( ) пытается получит�, доступ к терминалу для п роцесса, и если ей это удается , то никакой ошибки не возникает. $ echo " not sekret " 1 pyt honЗ getpas s_de faul t s . py P a s s wo r d : You e n t e red : s e k r e t

В этом случае ответственность за определение того , что входной поток не яв­ ляется терминальным, и использование альтернативного метода для чтения вход­ ных данных возлагается на вызыва ю щий код. Листинг 1 4.43. getpass_noterminal . ру import ge t pa s s import s ys i f s y s . s t di n . i s a t t y ( ) : ge t pa s s . ge t pa s s ( ' Us i n g getpas s : р el se : p r i n t ( ' Us i ng readl ine ' ) р = s ys . stdin . read l i ne ( ) . r s t r i p ( ) =

p r i n t ( ' Read :

')

' , р)

Вывод в случае чтения пароля с терминала. $ pyt honЗ . / getpas s_no t e rmina l . py U s i ng g e t pa s s : Read : s e kret

938

Вывод в случае чтения пароля не с терминала.

$

e cho " s e k re t "

1

pythonЗ . / g e t p a s s_no t e rrni na l . py

U s i ng readl i n e Re ad : s e kret Дополнительные ссылки • •

Раздел документации стандартной библиотеки, посвященный модулю getpa s s 10• r e a d l i n e (раздел 1 4.3). Интерактивная библиотека для работы с командной стр окой .

1 4. 5 . cmd: построчные командные процессоры Модуль cmd содержит общедоступный класс Cmd, предназначенный для исполь­ :юван ия в качестве базового класса для интеракти в ных оболочек и других интер­ претаторов команд. По умолчан ию он использует модуль readline ( раздел 14.3) для обработки интерактивного ввода, редактирования командной строки и авто­ завсршения команд.

1 4. 5. 1 . Обработка команд Интерпретатор команд, создав аемый с помощью модуля cmd, использует цикл для чтения входных строк, их синтаксического анализа и последующей передачи команды соответствующему обработчику. Входные строки разбиваются на две ча­ сти: команду и другой текст, находящийся в данной строке. Например, если поль­ зователь введет foo ba r , а класс интерпретатора включает метод с именем do_ foo ( ) , то этот метод будет в ызван с "bar " в качестве единствен ного аргумента. Маркер конца файла передается методу do_EOF ( ) . Если обработчик команды возвращает значение, эквивале н тное True, то вы п олняется корректный выход из программы. Таким образом, чтобы обеспечить корректный выход из интерпрета­ тора, необходимо реализовать метод do _EOF ( ) , который будет возвращать значе­ ние T rue. Ниже при веден пример простой программы, которая поддерживает команду "gгeet". Л истинг 1 4.44. cmd_simple . ру irnport crnd c l a s s He l l oWorl d ( crnd . Cmd ) : de f do_greet ( s e l f , l i ne ) : pr i n t ( " he l l o " ) def do_EOF ( s e l f , l i ne ) : return T rue

10

https : / /do c s . python . org / 3 . 5 / l ib r a r y /getpa s s . htrnl

2.4.5. cmd: nосщючнwе КОМ8НNIЫ• процессоры if

937

name ' mai n ' · He l l oWorl d ( ) . cmdl oop ( ) ==

Запуск программы в интерактивном режиме позволяет продемонстрировать выполнение команд и некоторые возможности, предоставляемые классом Crnd. $ pythonЗ cmd_simp l e . py ( Cmd )

Первое, на что следует обратить внимание, - это командная подсказка, ( Crnd ) . Ее можно настраивать с помощью атрибута prornp t . Значение подсказки - дина­ мическое. Иными словами, если обработчик команды изменяет атрибут prornpt, то для приглашения к вводу следующей команды используется новое значение. Do cume n t e d commands ( t ype h e l p < t op i c> ) : help Undocume n t e d commands : EOF g r e e t

В класс Crnd встроена команда help. Будучи вызванной без аргументов, она ото­ бражает список доступных команд. Если ввод включает имя команды, выводится более подробная информация, ограниченная описанием этой команды. Если команда - greet , то для ее обработки вызывается метод d o_gree t ( ) . ( Cmd ) g re e t he l l o

Если в классе н е предусмотрен обработчик для конкретной команды, то вызы­ вается метод de faul t ( ) , которому в качестве аргумента передается вся входная строка . Встроенная реализация метода defaul t ( ) выводит сообщение об ошибке. ( Cmd ) foo * * * Un known s yntax : foo

Поскольку метод do_EOF ( ) возвращает значение True, нажатие комбинации клавиш (для Wi11dows - ) приводит к выходу из интерпретатора. ( Cmd ) л D $

Символ новой строки не выводится п р и выходе из программы, поэтому резуль­ таты выглядят немного неопрятно.

1 4.5.2. Аргументы команд Следующий пример включает усовершенствования, устраняющие некоторые недостатки предыдущей 11ро1·раммы, и справку для команды greet.

rмва 14. Строиrем.нwе 6№ки прМАО11Се нмй

938 Листинг 1 4.45. cmd_arguments . py import cmd

c l a s s He l l oWor l d ( cmd . Cmd ) : def do_g re e t ( s e l f , pe r s on ) : " " " g reet [ pe r s o n ) G r e e t t h e named person " " " i f person : print ( " h i , " , pe r s o n ) else : p r i n t ( ' hi ' ) de f do_EOF ( s e l f , l i ne ) : r e t u rn T rue def po s t l oop ( s e l f ) : p r i nt ( )

if

name ' ma i n '· H e l l oW o r l d { ) . cmdl oop ( ) ==

Добавленная в метод do_greet ( ) строка документации становится текстом справки для данной команды . $ pythonЗ cmd_a rgume nt s . py ( Cmd ) he l p Do cumen ted commands ( t ype he l p < t opi c> ) : greet help Undocumented commands : EOF ( Cmd ) he l p g reet g reet [ pe r s o n ] G r e e t the name d person

В выводе справки отображается информация о том, что команда greet имеет один необязательный аргумент: person. Несмотря на то что этот аргумент нео­ бязательный, различие между командой и методом обратного вызова очевидно. Метод всегда получает арl'умент, но и ногда его значением является пустая строка. Обработчик команды 01·вечает за определение того, является ли пустой аргумент допустимым значением или необходимо продолжить анализ и обработку коман­ ды. В данном примере , если предоставляется имя человека, то выводится персо­ нализированное приветствие.

939 ( Cmd ) greet Al i c e hi , Al i ce ( Cmd ) greet hi

Независимо от того, предоставил ли пользователь аргумент или не предоста­ вил, передаваемое обработчику значение не включает саму команду. Это упроща­ ет анализ команды в обработчике, особенно если имее-rся несколько аргументов.

1 4.5.3. Активная справка Форматирование справочного текста в предыдущем примере все еще страда­ ет некоторыми недостатками. Поскольку источником справки являt."'I'ся строка документирования , справочный текст наследует все отступы из исходного фай­ ла. Можно было бы удалить лишние пробелы непосредственно в источнике , но тогда неудачно отф орматированным оказался бы код приложения. Лучшее ре­ шение - реализовать обработчик справки для команды gree t , присвоив ему имя help_greet ( ) . Обработчик справки обеспечивает получение текста справки для команды с указанным именем. Листинг 1 4.46. omd_do_help . py t ry : impo rt gnure a d l i ne import sys sys . modul e s [ ' read l i ne ' ] e xcept Impo r t E r ro r : pa s s

gnureadl ine

impo rt cmd

c l a s s He l l oWo rl d ( cmd . Cmd ) : def do_greet ( s e l f , p e r s on ) : i f person : p r i nt ( " hi , " , pe r s o n ) else : p r i nt ( ' hi ' ) def h e l p g reet ( s e l f ) : p r i nt ( ' \ n ' . j o i n ( [ ' g reet [ pe r s o n ] ' , ' G r e e t the named p e r s o n ' , ]) ) de f do_EOF ( s e l f , l i ne ) : return T rue

if

name ' ma in ' · He l l oWo r l d ( ) . cmdl oop ( ) ==

В этом примере текст справки остается статическим, но имеет более привле­ кательный внешний вид. Кроме того, существует возможность использовать со­ стояние предыдущей команды для адаптации справочного текста к текущему кон­ тексту. $ python З cmd_do_h e l p . py ( Cmd ) h e l p g re e t g r e e t [ pe r s o n ] G r e e t t h e named pe r s on

Решение о том, следует ли вывести справочное сообщение на экран или про­ сто вернуть его для посл едующей обработки другим кодом, принимается самим обработчиком справки.

1 4. 5.4. Автозавершение ввода Класс Cmd включает методы, обеспечивающие поддержку автозавершения вво­ да команд на основании их имен. Пользователь активизирует автозавершение на­ жатием клавиши < tab> EOF greet h e l p ( Cmd ) h < t ab> ( Cmd ) h e l p

Как только стала известна команда, завершение аргумента обрабатывается ме­ тодами с префиксом comp l e t e_. Это позволяет новым обработчикам автозавер­ шения ввода сформировать список возможных вариантов завершения , используя любые критерии ( например, путем запроса к базе данных или поиска файла или каталога в файловой системе) . В данном случае в коде запрограммирован набор друзей ( FR I ENDS ) , для которых выводится менее формальное приветствие, чем то, которое используется для пользователей с другими именами или анонимных незнакомцев. Вероятно, реальная программа хранила бы его где-то в другом ме­ сте, а затем читала однократно и кешировала содержимое для анализа в случае необходимости. Листинг 1 4.47. cmd_arg_completion . py t ry : impo r t gnureadl i ne

14.5. cmcl: nос:qючнwе камаНАНwе процессоры

impo rt s ys s ys . modu l e s [ ' read l i ne ' J except Impo r t E r ro r : pa s s

941

gnuread l i n e

impo rt cmd

c l a s s He l l oWo rld ( cmd . Cmd ) : FRI ENDS = [ ' Al i ce ' ,

' Adam ' ,

' Ba rbara ' ,

' ВоЬ ' ]

def do_g r e e t ( s e l f , pe r s on ) : " G r e e t the person" if p e r s on and pe r son i n s e l f . FRI ENDS : ' h i , { } ! ' . fo rma t ( pe r s on ) g re e t i ng e l i f person : g re e t i ng ' he l l o , { } ' . fo rma t ( pe r son ) els e : g re e t i n g = ' he l l o ' p r i n t ( gr e e t i n g ) =

d e f comp l e t e_g ree t ( s e l f , t e x t , l i ne , begidx , endi dx ) : i f not t e x t : c omp l e t i on s s e l f . FRIENDS [ : ] e l se : c omp l e t i on s f for f in s el f . FRI ENDS i f f . s t a r t s w i t h ( t ext ) return comp l e t i on s def do_EOF ( s e l f , l i ne ) : return T r u e

if

name == ' ma i n ' · H e l l oWo r l d ( ) . cmdl oop ( )

При наличии введенного текста метод comp l e te_greet ( ) возвращает список дррей. имена которых соответствуют вводу. В щютив1юм случае возвращается ПОЛНЫЙ CIIИCOK друзей. $ pythonЗ cmd_arg_compl e t i on . py ( Cmd ) greet < t ab> Adam Al i ce B a rbara ВоЬ ( Cmd ) g r e e t A< t ab> Adam Al i ce ( Cmd ) g reet Ad ( Cmd ) g ree t Adam hi , Adam !

942

Если предоставленное имя не встречается в списке друзей , то выводится фор­ малыюе приветствие. ( Cmd ) g r e e t Joe he l l o , Joe

1 4.S.S. Переопределение методов базового класса Класс Cmd включает несколько методов, которые могут быт1, переопределены в качестве перехватчиков для выполнения определенных действий или измене­ ния поведения базового класса. Приведенный ниже пример не является исчерпы­ вающим, но иллюстрирует применение методов, которые удобно использовать в повседневной работе. Листинг 1 4.48. cmd_i l lus trate_methods . py try: import gnureadl i n e irnport s ys s ys . rnodu l e s [ ' read l i ne ' ] except Irnpo r t E r ro r : pa s s

gnuread l i ne

import crnd

c l a s s I l l u s t r a t e ( cmd . Crnd ) : " Иллюстри рует исполь зовани е ме тода б азового кла сса . " def crnd l o op ( s e l f , i n t ro=None ) : p r i nt ( ' cmdl oop ( { ) ) ' . fo rma t ( i n t ro ) ) return crnd . Cmd . crndl oop ( s e l f , i n t r o ) de f pre l oop ( s e l f ) : p r i n t ( ' prel oop ( ) ' ) def po s t l oop ( s e l f ) : p r i n t ( ' po s t l oop ( ) ' ) de f pa r s e l ine ( s e l f , l i ne ) : p r i n t ( ' pa r s e l i ne ( { ! r ) ) => ' . fo rrna t ( l i ne ) , end= ' ' ) ret crnd . Crnd . pa r s e l ine ( s e l f , l i ne ) p r i n t ( re t ) r e t u rn ret =

def one crnd ( s e l f , s ) : p r i n t ( ' onecrnd ( { ) ) ' . fo rrnat ( s ) ) re turn cmd . Crnd . one cmd ( s e l f , s ) de f ernp t y l ine ( se l f ) : p r i n t ( ' ernpt yl i ne ( ) ' ) return cmd . Cmd . ernpt yl i ne ( s e l f )

14.5. cmd: построчные комаtWtые процессоры

943

def de f a u l t ( s e l f , l i ne ) : p r i n t ( ' de fa u l t ( { } ) ' . fo rmat ( l i ne ) ) return cmd . Cmd . defaul t ( s e l f , l i ne ) def pre cmd ( s e l f , l i ne ) : pri n t ( ' p r e cmd ( { } ) . fo rmat ( l i ne ) ) re t u r n cmd . Cmd . pr e cmd ( s e l f , l i ne ) '

def pos t cmd ( s e l f , s t op , l i ne ) : p r i n t ( ' po s t cmd { { } , { } ) ' . forma t { s t o p , l i ne ) ) return cmd . Cmd . po s t cmd ( s e l f , s t o p , l i ne ) def do_g r ee t ( s e l f , l i ne ) : p r i n t ( ' he l l o , ' , l i ne ) d e f do EOF ( s e l f , l i ne ) : " E� i t " return T rue

if

name ma i n ' : I l lu s t r a t e ( ) . cmdl oop ( ' I l l u s t ra t i n g the met hods o f cmd . Cmd ' ) ==

'

Метод cmdl oop ( ) - это основной рабочий цикл интерпретатора. Как правило, его не приходится переопределять, поскольку имеются перехватчики pre loop ( ) и po s t loop ( ) . На каждой итерации цикла cmdloop ( ) вызывается метод onecmd ( ) , передаю­ щий команду соответствующему обработчику. Фактическая строка ввода анали зи­ руется методом par s e l ine ( ) , который возвращает кортеж, содержащий команду и оставшуюся часть строки. Если строка пуста, вызывается метод empt у 1 ine ( ) , и заданная по умолчани ю реализация вновь оьшолняет п редыдущую команду. Если строка содержит коман­ ду, то сначала вызывается метод precmd ( ) , а затем выполняется поиск и вызов со­ ответствую щего обработчика. Если обработчик команды не найден , вместо него вызывается метод de faul t ( ) . После этого вы зывается метод po s t cmd ( ) . Ниже представлен пример рабочего сеанса с добавленными инструкциями вы­ вода на печать. $ p ythonЗ cmd_i l l u s t r a t e_me t hods . py cmd l oop ( I l l u s t ra t i n g the me thods of cmd . Cmd ) pre l oop ( ) I l l u s t r a t i n g t he me thods o f cmd . Cmd ( Cmd ) greet ВоЬ pre cmd ( greet ВоЬ ) one cmd ( greet ВоЬ ) pars e l i n e ( g r e e t ВоЬ ) => ( ' g reet ' , ' ВоЬ ' , ' g r e e t ВоЬ ' ) hel l o , ВоЬ po s t cmd ( None , greet ВоЬ ) ( Cmd ) л Dpre cmd ( EO F ) one cmd ( EO F )

ГА888 14. C'lpotmмwtwe 6№tсм ПР""°*8НИЙ

944 pa r s e l i n e ( EO F ) => ( ' EOF ' , po s t cmd ( T rue , EOF ) po s t l oop ( )

' '

' EOF ' )

1 4. 5.б. Конфигурирование класса

Cmd

с помощью атрибутов

Для управления интерпретаторами команд можно использовать не только описанные ранее методы, но и ряд атрибутов. В атрибуте prompt можно задать строку, которая будет выводиться в каждом приглашении ко вводу очередной ко­ манды . Атрибут intro содержит текст приветствия, выводимого в начале рабо­ ты программы. Если необходимо изменить этот текст, то можн о передать методу cmdloop ( ) н о вый вариант текста в качестве аргумента или задать его непосред­ ственно в классе. Для форматирования тексrа выв одимой справки используются атрибуты doc_heade r , m i s c_he ade r , undoc_heade r и r u l e r . Листинг 1 4.49. cmd_attributes . ру impo rt cmd

c l a s s He l l oWorld ( cmd . Cmd ) : p rompt = ' prompt : ' intro " S imp l e command pro c e s s o r exampl e . " =

do c heade r ' do c_he ade r ' mi s c-heade r ' mi s c-heade r ' undo c header ' undoc_he ade r ' =

=

rul e r = ' - ' de f do_p rompt ( s e l f , l i ne ) : ''Изме нить интера кти в ную подсказку" s el f . prompt l i ne + ' · ' =

def do_EOF ( se l f , l i ne ) : return T rue

if

narne ' rna i n ' · He l l oWo r l d ( ) . crndl oop ( ) ==

В этом примере демонстрируется обработчик команд, предоставляющий поль­ зователю возможность управления подсказкой в интеракти вных сеансах.

$

python З crnd_a t t r ibut e s . py

S irnpl e comma nd proce s s o r exarnp l e . prompt : prornpt he l l o hel l o : h e l p doc h e a d e r

945

he lp

prornpt

undo c heade r EOF hel l o :

1 4.5.7. Выполнение команд оболочки Класс Cmd до п олняет стандартную обработку команд двумя специал ьными пре­ фиксами. Вопросительный знак ( ? ) эквивалентен встроенной команде help и мо­ жет исполЬ3оваться наравне с ней . Восклицательны й зн ак ( ! ) соответствует ме­ тоду do_ shel 1 ( ) и предназначен для выполнения других команд, как показано в следующем примере. Л истинr 1 4. 50. cmd_do_shell . py impo rt crnd impo r t s ubproce s s

c l a s s She l l E naЬl e d ( cmd . Cmd ) : l a s t_output = ' ' d e f do_s he l l ( s e l f , l i ne ) : " Run а s he l l command " p r i n t ( " runn ing she l l command : " , l i ne ) s ub_cmd s ubpr oce s s . Popen ( l i n e , s h e l l=True , s t dout = s ubpr o ce s s . PI PE ) output sub_cmd . commun i cate ( ) [ О ] . de code ( ' ut f-8 ' ) p r i n t ( output ) s e l f . l a s t_output output =

=

de f do_e cho ( s e l f , l i ne ) : " " " Pr i n t the i nput , r e p l a c i n g ' $ out ' w i t h the output o f the l a s t s he l l command ll Jl lr

# Явно н енадежный вариа нт p r i n t ( l i n e . replace ( ' $ out ' , s e l f . l a s t_output ) ) d e f do_EOF ( s e l f , l i ne ) : ret urn T rue if

narne ' ma i n ' · She l lEnaЫed ( ) . cmd l oop ( ) ==

В этой реализации команды echo строка $ out заменяется выводом предыду­ щей команды оболочки.

ГАВва И. С1роитеАьные 6Аокм nрМАс.ений $ python3 cmd_do_she l l . py ( Cmd ) ? Docurne n t e d commands ( t ype h e l p < t o p i c> ) : e cho

help

s he l l

Undocumented commands : EOF ( Cmd ) Run а ( Cmd ) Print

? she l l she l l command ? e cho the i nput , rep l a c i n g ' $ out ' w i t h the output of the l a s t s h e l l comma nd ( Cmd ) she l l pwd running shel l command : pwd . . . /pymotw- 3 / s o u r ce / cmd ( Cmd ) ! pwd running she l l c ommand : pwd . . . /pymotw- 3 / s o u r ce / cmd ( Cmd ) e cho $ ou t . . . /pymotw- 3 / s o u r c e / cmd

1 4. 5.8.

Альтернативные варианты ввода

По умолчанию объект Cmd взаимодействует с пользователем посредством мо­ дуля readline ( раздел 14.3) , но также существует возможность пер едачи последо­ вательности команд в стандартный поток ввода с помощью стандартных средств перснаправлен ия ввода оболочки Uнix. $ e cho help 1 pytho n 3 cmd_do_h e l p . py ( Cmd ) Do cumented commands ( t ype h e l p < t opi c> ) : greet help Undocumented c onunands : ========-=---=-= =-==-===

EOF ( Cmd )

Чтоб ы обеспечить чтение ввода непосредственно из файла, в сценарий необ­ ходимо внести некоторые изме н ения. Поскольку модуль readline ( раздел 14.3) взаимодействует с терминалом, а не со стандартным потоком ввода, при чтении

847

ввода из файла его следует отключить. Кроме того, •1тобы избавиться от лишних подсказок, загроможда1ощих экран , можно задать в ка•1естве подсказки пустую строку. В приведенной ниже видоизмене н ной версии примера HeUoWorld показа­ но, как открыть файл и передать его содержимое сценарию в качестве ввода. Листинг 1 4. 5 1 . omc:l_file . py irnpo rt cmd

c l a s s Hel l oWo r l d ( cmd . Cmd ) : # Откпючи ть испол ь зов ание модул я rawi nput u s e_rawi nput Fa l s e =

# Н е отобража ть подсказку после ч те ни я каждой команды prompt = ' ' def do_g reet ( s e l f , l i ne ) : p r i nt ( " hel l o , " , l i ne ) def do_EOF ( s e l f , return T rue

l i ne ) :

if - name' ma i n ' · irnpo r t s ys with open ( s ys . a rgv [ l ] , ' rt ' I as i nput : He l l oWo r l d ( s t di n•i nput ) . cmd l o op ( ) •=

Установив для переменной use_rawinput значение Fal se , а для подсказки пустую строку, можно вызвать сценарий для работы с входным файлом, содержа­ щ им по одной команде в каждой строке. Листинг 1 4.52. omc:l_file . txt greet greet Al i ce a nd ВоЬ

Выполнение примера с этими входными данными дает следую щ ий вывод. $ pytho n З cnd_f i l e . py cmd f i l e . t x t hel l o , he l l o , A l i c e a nd ВоЬ

1 4. 5.9. Извлечение команд иэ переменной

sys . arqv

Команды могут передаваться программе не только из консольного ввода или файла, 110 и в виде аргументов командной строки. Это можно сделать путем непо­ средственного вызова метода onecmd ( ) , как показан о в следующем примере.

948 Листинг 1 4.53. cmd_argv . py import cmd

c l a s s I n t e ract i veOrCommandL i n e ( cmd . Cmd ) : " " " Принимае т кома нды чере з о бычную и нтерактивн ую подск азку или и з командно й строки . nnn

def do_g reet ( s e l f , l i ne ) : p r i n t ( ' he l l o , ' , l i ne ) d e f do_EOF ( s e l f , l i ne ) : return T rue 1 . i f -name -ma i nimport s ys i f l e n ( s ys . a rgv ) > 1 : I n t e ract i veOrCommandLi ne ( ) . one cmd ( ' ' . j o i n ( s ys . a rgv [ l : ] ) ) e l se : I n t e ra c t i veOrCommandLi ne ( ) . cmdloop ( ) ==

1

Поскольку методу onecmd ( ) передается единственный аргумент в виде строки. то перед передаче й аргуме нтов пр01·рамме их необходимо конкатенировать. $ pyt honЗ cmd_a r g v . py greet Command- L i n e U s e r h e l l o , Comma nd- L i n e U s e r $ pythonЗ cmd_a r gv . py ( Cmd ) g r e e t I nt e r a c t i ve U s e r he l l o , I n t e r a ct i ve U s e r ( Cmd ) Дополнительные ссылки • • •





Раздел документации стандартной библиотеки, посвященный модулю cmct 1 1 • cmct 2 12 . Улучшенная версия модуля cmd, предлагающая дополнительные возможности. GNU Readline1 3 . Библиотека функций, обеспечиваю щих возможность редактирования входных строк в процессе их ввода. rea d l i n e (раздел 14.3). И нтерфейс стандартной библиотеки Python, предназначенный для работы с библиотекой GNU Readline. subproce s s (раздел 1 0. 1 ). Модуль, используемый для управления другими процессами и их выводом.

12

11

https : / /docs . python . o r g / 3 . 5 / l i b r a r y / cmd . html h t t p : / /pypi . pyt hon . o r g /pypi / cmd2

13

h t t p : / / t i swww . ca s e . edu /php / che t / readl i n e / r l top . h tml

14.8. shlex: А81ССМ'18С1СМЙ 8Н8111а СМН18Кl:МСО8 8 СТМА8 КОМ8НАНОЙ обо№чlСИ Unlx

1 4. б . shlex: лексический анализ синтаксисов в стиле командной оболочки Unix Модуль sh lex реализует класс , предназначенный для анализа простых видов синтаксиса в стиле оболочки U11ix. Е1·0 можно ис11ол ьзовать для написания с11еци­ ализированных языков или для анализа строк, содержащих кавычки (задача не настолько 11ростая , как может показаться на первый взгляд) .

1 4.6. 1 . Анализ строк, содержащих кавычки При обработке входного ·1-екста часто возникает проблема идентификации по­ следовательности слов, заключенной в кавычки , как единого целого. Разбиение текста на основе кавычек не всегда рабо"·ает 'l"dK, как ожидается , о repr ( ) : ' t B � 9®e '

1 4.7.З. Доступ к конфигурационным параметрам Класс Conf igParser включает методы , предназначенные для обработки струк­ туры содержимого конфигурационного файла, в том числе для получения списка разделов и параметров и их значений. Ниже приведен конфигурационный файл, который содержит два раздела, соответствующих двум разным веб-службам.

981 [ bug_ tracke r ] url ht tp : / / l o c a lhos t : 8 0 8 0 /bug s / u s e rname dhe l lmann p a s s wo rd S ECRET =

=

[wiki ] url ht tp : / / l o c a lhos t : 8 0 8 0 / wi k i / u s e rname dhel lmann p a s s wo rd S ECRET =

=

В следующем примере демонстрируется исследование конфи гурационных дан­ ных с помощью методов s e c t i ons ( ) , opt i ons ( ) и i tems ( ) . Лисrинr 1 4.66. configparser_s tructure . ру from con f i gp a r s e r import Con f i g Pa r s e r parser Con f i g Pa r se r ( ) p a r s e r . read ( ' mu l t i s e c t i on . i n i ' ) =

for s e c t i on_name i n pa r s e r . s e c t i ons ( ) : p r in t ( ' S e ct i on : ' , s e c t i o n_name ) print ( ' Opt i o n s : ' , par s e r . opt i ons ( s e c t i on_name ) ) for name , v a l ue in p a r se r . i t ems ( s e c t i on_name ) : {} { } ' . fo rmat ( name , va l ue ) ) p r i nt ( ' print ( ) =

Каждый из методов s e ct i on s ( ) и op t i on s ( ) возвращает список строк, тог­ да как метод i tems ( ) возвращает список кортежей , содержащих пары " им я значение " . ­

S e c t i on : bug_t racker Opt i on s : [ ' ur l ' , ' us e rn ame • , ' pa s swo rd ' ] url h t t p : / / l o calhost : 8 0 8 0 /bug s / dhel lmann u s e rname p a s s word SECRET =

=

S e c t i on : wi ki Opt i ons : [ ' u r l ' , ' u s e rn ame ' , ' pa s sword ' ] url http : / / l ocalhost : 8 0 8 0 /wi k i / u s e rname dhe l lmann pas swo rd SECRET =

=

Кроме того, класс ConfigParser поддерживает тот же ЛРI привязок, что и тип dict, действуя как один общий словарь, содержащий отдельные словари для каж­ дого раздела. Листинг 1 4.67. configparser_s tructure_di ct . py f rom con f i gpa r s e r import Con f i g Pa r s e r parser Con f i g P a r s e r ( ) p a r s e r . read ( ' mu l t i s e c t i on . i n i ' ) =

Гмва 14. Стромтем.н ые б№ки прможени й

962

for s e c t i on_name in p a r s e r : p r i n t ( ' S e c t i o n : ' , s e c t i on_name ) s e ct i on pa r s e r [ s e c t i o n name ] print ( ' Opt i ons : ' , l i s t ( s e c t i o n . keys ( ) ) ) f o r name i n s e c t i o n : {} { } ' . f o rmat ( name , s e c t i on [ name ] ) ) print ( ' print ( ) =

=

Использование API привязок для доступа к тому же конфигурационному файлу дает те же результаты. S e ct i on : DE FAULT Opt i o n s : [ ] S e c t i on : bug_t racker Opt i on s : [ ' u r l ' , ' us e rname ' , ' pa s sword ' ] url h t tp : / / l o c a l ho s t : 8 0 8 0 /bug s / u s e r n ame dhe l lmann pa s sword SECRET =

=

S e c t i o n : wi ki Opt i o n s : [ ' ur l ' , ' us e rname ' , ' pa s s word ' ] url h t tp : / / l o ca lho s t : 8 0 8 0 /wi ki / use rname dhe l lmann pas sword SEC RET =

=

1 4. 7 .3 . 1 . Тестирование на наличие значений

Чтобы проверить существование раздела, следует испольаовать метод has s e c t i on ( ) , передав ему имя раздела в качестве аргумента.

_

Лисrинr 1 4.68. con.figparser has_section . ру _

f rom c o n f i gpa r s e r imp o r t C o n f i g Pa r s e r parser Con f i g Pa r s e r ( ) p a r s e r . read ( ' mu l t i s e c t i on . i n i ' ) =

f o r candidate in [ ' wi k i ' , ' bug_t r a c ke r ' , ' dvcs ' ] : p r i n t ( ' { : < 1 2 } : { } ' . fo rmat ( candida t e , pa r s e r . has_s e c t i on ( candidate ) ) )

П роверка существования раздела до вызова метода get ( ) может предотвра­ тить возникновение исключений, обусловленных отсутствием данных. $ pythonЗ c o n f i gpa r s e r_ha s_s e ct i on . py wiki bug_t r a c k e r dvc s

True T rue Fa l s e

Для проверки существования параметра внуrри раздела следует использовать метод ha s_opt i on ( ) .

14.7. confl8p8rser: ре6ота с �нымм файммм Листинг 1 4.69. configparser_has_op tion . py f rom conf i gpa r s e r import Conf i g Pa r s e r pa r s e r C on f i g P a r s e r ( ) pa r s e r . read ( ' mu l t i s ec t i on . i n i ' ) =

SEC T I ONS OPT I ONS

= =

[ ' wi ki ' , ' none ' ] [ ' u s e rname ' , ' pa s sword ' ,

' ur l ' ,

' de s c r ipt i on ' ]

f o r s e ct i on i n SECT I ONS : has_s e ct ion pars e r . has_s e c t i on ( se ct i on ) pr i nt ( ' { } s e ct i on e x i s t s : { } ' . fo rma t ( s e c t i o n , has_s e c t i on ) ) fo r candidate i n OPT I ONS : ha s opt i on p a rs e r . ha s opt ion ( s e c t i o n , candidat e ) p r i nt ( ' { } . { : < 1 2 } : { } · � format ( s e ct ion , candida t e , ha s_opt i o n ) ) print ( ) =

=

Если указанного раздела не существует, метод ha s opt i on ( ) возвращает значе­ ние Fa l s e . _

$ pythonЗ c on f i gpa r s e r_ha s_opt i on . py w i k i s e c t i on e x i s t s : w i k i . u s e rname w i k i . pa s s word wiki . url w i k i . de s c r ipt i on

True T rue T ru e T ru e Fa l s e

none s e c t ion ex i s t s : none . u s e rname none . pa s s wo rd none . ur l none . de s c r i p t i on

Fa l s e Fal s e Fa l s e Fa l s e Fa l s e

1 4.7.3.2. Типы значений

Имена всех разделов и параметров обрабатываются как строки, тогда как зна­ чения параметров могут быть строками, целыми числами, числами с плавающей точкой и булевыми значениями. Некоторые строковые значения могут быть ис­ пользованы для представлен ия булевых значений в конфюурациошюм файле; при обращении к ним они преобразуются в True или Fa l se . Следующий файл включает примеры с числовыми типами, а также примеры со всеми значениями, которые распоз н аются анализатором как булевы значения. Листинг 1 4.70. type s . ini [ ints ] p o s i t i ve = 1 negat i ve -5 [ f l oat s ]

po s i t ive negat ive

0.2 -3 . 14



[ bool e ans ] numЬe r t rue = 1 numЬe r f a l s e О yn t rue ye s yn fa l s e no t rue t f t rue tf fa l s e fa l s e onoff t rue on onoff f a l s e fa l s e -

=

=



=

=

=

=

Класс Con f i g Pa r s e r пе предпринимает никаких попыток определить тип па­ раметра. Ожидается, что приложение будет использовать корректные методы для извлечения значений желаемого типа. Метод get ( ) всегда возвращает строку. Для извлечен ия целочисленных значений следует использовать метод getint ( ) , значений с плавающей точкой - метод get floa t ( ) , а булевых значений - метод getboolean ( ) . Листинг 1 4.71 . configparser_value_types . py f rom conf i gpa r s e r imp o r t Con f i g Pa r s e r pa r s e r Con f i g P a r s e r ( ) pa r s e r . read ( ' t ype s . i n i ' ) =

p r i n t ( ' I n t e ge r s : ' ) for name i n pa r s e r . opt i ons ( ' i nt s ' ) : s t r i ng_va lue pa rs e r . g e t ( ' i n t s ' , name ) va l ue = par s e r . g e t i nt { ' i n t s ' , name ) print ( ' { : < 1 2 } : { ! r : < 7 } - > { } ' . format ( name , s t r i ng_va lue , va l ue ) ) =

p r i n t ( ' \ n Floa t s : ' ) for name i n pa rs e r . opt i on s ( ' f l o a t s ' ) : s t r i ng_va lue pa r s e r . get ( ' f l oa t s ' , name ) value pa rs e r . g e t f l o a t ( ' f l o a t s ' , name ) print ( ' { : < 1 2 } : { ! r : < 7 } -> ( : 0 . 2 f } ' . fo rma t { name , s t r i ng_va lue , va lue ) ) =

=

p r i nt { ' \nBooleans : ' ) f o r name i n pa r s e r . opt i ons { ' boo l e a n s ' ) : s t r i ng_va lue pa r s e r . get ( ' bo o l e ans ' , name ) va l ue par s e r . ge tboo lean ( ' bo o l ea ns ' , name ) print ( ' { : < 1 2 } : { ! r : < 7 } -> { } ' . fo rma t { name , s t r i ng_va lue , va l ue ) ) =

=

Выполнение этой программы дает следующие результаты. Integers : pos i t i ve negat ive

'1' ' -5 '

-> 1

-> - 5

14.7. conf\gp&l'88r: ре6отв с конфиrуреционными файмми Floa t s : po s i t i v e nega t ive

-> 0 . 2 0 '0.2' ' -3 . 1 4 ' -> -3 . 1 4

Booleans : nurnЬe r t rue numЬ e r fa l se yn_t rue yn_fa l s e t f t rue tf fa l s e o n o f f t rue ono f f fa l s e

'1' 10' ' ye s ' ' no ' ' t rue ' ' fa l s e ' ' on ' ' fa l s e '

-> -> -> -> -> -> -> ->

T rue Fal s e T rue Fal s e T rue Fal s e T rue Fal s e

С помощью аргумента conve r t e r s можно передать конструк·юру Config Pa r s e r дополнительные функции преобразования. Каждый конвертер получает единственное входное значение, которое он преобразует в соответствующий воз­ вращаемый тип. Листинг 1 4.72. configparser_cus tom_type s . py f rom con f i gpa r s e r import C o n f i g Pa r s e r impo rt datet ime def pa r s e i s o da t e t ime ( s ) : p r i n t ( ' pa r s e_i so_da t e t ime ( { ! r } ) ' . fo rma t ( s ) ) r e t u r n datet ime . da t e t ime . st rpt ime ( s , ' % Y- % m- % dT % H : %M : % S . % f ' ) pa r s e r Con f i g P a rs e r ( conve r t e r s = { ' da t e t ime ' : pa rse_ i s o_da t e t ime , =

pa r s e r . read ( ' cus t om_t ype s . i n i ' ) s t ri n g_value pa r s e r [ ' dat e t ime s ' ] [ ' du e_dat e ' ] va lue pa r s e r . g e t da t e t ime ( ' dat e t ime s ' , ' due_dat e ' ) p r i n t ( ' due_date : { ! r } - > { ! r } ' . fo rmat ( s t r i n g_va l ue , va l ue ) ) =

=

Добавление конвертера приводит к тому, что клас dat e t ime . da t e t ime ( 2 0 1 5 , 1 1 , 8, 11, 30, 5, 905898 )

П реобразующие методы можно также добавлять не п осредственно в подклассы Conf i gPa rser.

1 4 . 7 . 3 . 3 . Параметры, используемые как флаги

Обычно анализатор требует задания явных значений для каждого параметра. Но если конструктору Con f igPa r s e r передан apгyмeнт a l low_no_value со значе­ нием True, то во входном файле допускается задание параметров без значений, и такие параметры можно использовать в качестве флагов. Листинг 1 4.73. configparser _allow_no_value . py , import c o n f i gpa r s e r # Тре буе т с я задание з начени й параме тров try: pa r s e r = con f i gpars e r . Con f i g Pa r s e r ( ) par s e r . read ( ' a l l ow_no_val u e . i n i ' ) except confi gpar s e r . Pa r s i ngError a s e r r : p r i n t ( ' Could not par s e : ' , e rr ) # Разрешить и спол ь з о ва ние параметров бе з з начений p r i n t ( ' \nTrying a g a i n w i t h a l l ow no va l ue=T rue ' ) pa r s e r = conf igpa r s e r . Con f i g P a r s e r ( a l l ow no-val ue=True ) pa r s e r . read ( ' a l l ow_ no _va lue . i n i ' ) for f l a g in [ ' turn f e a t u r e on ' , ' turn other fea t u r e on ' ] : p r i n t ( ' \ n ' , f l ag ) ex i s t s = pa r s e r . has_op t i on ( ' fl a g s ' , flag ) print ( ' has _opt ion : ' , ex i s t s ) i f exist s : print ( ' ge t : ' , pa rs e r . ge t ( ' f l a g s ' , f l ag ) )

Если для какого-либо параметра, имеющегося во входном файле, значение не задано явно, то метод ha s_opt i on ( ) сообщит о том, что параметр существует, а метод get ( ) вернет для такоl'о параметра значение None.

$

pythonЗ c o n f i gpa r s e r_a l l ow_no_va l ue . py

Coul d not pa r s e : S o u r c e conta i n s pa r s i ng e r r o r s : ' a l l ow no value . i n i ' [ l in e 2 ] : ' turn_featu re_on \ n ' T r ying a g a i n wi th a l l ow no va lue=True turn feature on has_opt ion : T rue g e t : None t u rn- o t h e r- feature- on ha s_opt ion : Fa l s e

1 4.7.3.4. Многострочные строки

Строковые значения мо1уг занимать несколько строк, если последующие стро­ ки выделены отступом.

14.7. conflgparser: pa6cna с конфмrурацмоннымм фаААамм

Н7

[ examp l e ] rne s s a g e Th i s i s а rnul t i l i n e s t ring . Wi th two p a r a g raphs . =

They a r e s ep a r a t ed Ьу а comp l e t e l y emp t y l i ne .

В пределах многострочных значений, выделенных отступами, пустые строки интерпретируются как часть значения и , f i l e i nput g r ep . py : 2 3 : l i neno= f i l e i nput . f i l e l ineno( ) ,

=

Текст также можно читать из стандартного ввода. $ cat * . ру 1 python f i l e i nput_g rep . py f i l e input l O : irnport f i l e i npu t 1 7 : f o r l i ne i n f i l e i nput . input ( f i l e s , inplace=T ru e ) : 2 9 : irnport f i l e i nput 3 7 : f o r l i ne in f i l e input . i nput ( f i l e s , inplace=True } :

187 38 : i f f i l e i nput . i s f i r s t l i ne ( ) : f i l e i nput . f i l ename ( ) ) ) 40: 5 4 : " " " Examp l e f o r f i l e i nput modul e . 5 8 : imp o r t f i l e i nput 7 8 : fo r l i ne in f i l e i nput . i nput ( s ys . a rgv [ l : ] ) : l O l : imp o r t fi l e i nput 1 0 7 : fo r l i ne in f i l e i nput . i nput ( s ys . a r gv [ 2 : ] ) : i f f i l e i nput . i s s t d i n ( ) : 109: 113: p r i n t ( fmt . format ( f i l e n ame= f i l e i nput . f i l ename ( ) , 114 : l i neno= f i l e i nput . f i l e l i neno ( ) ,

1 4.9.3 . Фильтрация на месте Другой распространенной операцией, выполняемой по отношени ю к файлам, является изменение содержимого существующего файла, а не создание нового с измененным содержимым. Например, на платформах Unix иногда требуется из­ менить содержимое файла hosts для обновления диапазона IР-адресов подсети. Листинг 1 4.93. etc_hosts.txt до модификаций 11 11

11 H o s t Da t abase

11

11 l o c a l ho s t i s used t o c o n f i gure t he l oopback i n t e rface

11 when t h e s y s t em i s boo t i ng . 11 11

Do not ch ange t h i s ent ry .

127 . 0 . 0 . 1 l ocalho s t 2 5 5 . 2 5 5 . 2 5 5 . 2 5 5 broadca s t h o s t : :1 localhost fe 8 0 : : 1 % l o 0 l oc a l h o s t 1 0 . 1 6 . 1 7 7 . 1 2 8 hubert hube rt . he l l f l y . n e t 1 0 . 1 6 . 1 7 7 . 1 3 2 cubert cube rt . he l l f l y . net 1 0 . 1 6 . 1 7 7 . 1 3 6 z o i dberg z o idbe rg . he l l f l y . n e t

Безопасным способом автомати ч еского внесения подобных изменений явля­ ется создание нового файла на основе ввода с последующей заменой оригинала его измененной копией. Модуль f i l e input поддерживает такой подход посред­ ством использования параметра inp lace. Листинг 1 4.94. fileinput change suЬnet . ру _

impo r t f i l e input impo rt sys from_b a s e s ys . argv [ l ] to_base s ys . a rgv [ 2 ) f i l e s = s ys . a rgv [ 3 : ] =

=

f o r l i n e in f i l e i nput . i nput ( f i l e s , i np l a ce=T rue ) : line l i ne . r s t r i p ( ) . repl ace ( f rom_bas e , t o_ba s e ) pr i n t ( l i n e ) =

-

Несмотря на то что в сценарии используется функция print ( ) , результаты не отображаются на экране, поскольку модуль f i l e i nput перенаправляет стандарт­ ный поток вывода в изменяемый файл. $ pytho n 3 f i l e i nput_change_subne t . py 1 0 . 1 6 1 0 . 1 7 e t c hos t s . txt

Обновленная версия файла содержит измененные IР-адреса всех серверов в сети 1 0. 1 6.0.0/ 16. Листинг 1 4.95. etc_hosts.txt после модицикаций 11 11

11 H o s t Database

11 11 l ocalhos t i s used t o c o n f i g u r e t h e l oopba c k i n t e rface

11 when the s y s t em i s b o o t i n g . 11 11

Do not change t h i s e n t ry .

l o c a l ho s t 127 . 0 . 0 . 1 2 5 5 . 2 5 5 . 2 5 5 . 2 5 5 broadc a s tho s t : :1 localhost fe8 0 : : 1 % lo0 localhost 1 0 . 1 7 . 1 7 7 . 1 2 8 hub e r t hubert . he l l f l y . n e t 1 0 . 1 7 . 1 7 7 . 1 3 2 cube rt cubert . he l l f l y . net 1 0 . 1 7 . 1 7 7 . 1 3 6 z o i dberg zoidberg . he l l f l y . net

Прежде чем начнется обработка файла, создается его резервная копия с тем

же именем, но с добавленным расширением

.bak.

Листинг 1 4.96. fileinput_change_suЬnet_noi sy . py impo rt f i l e i nput impo rt g l ob impo rt s y s f rom_ba s e sys . a rgv [ l ] t o_b a s e s y s . argv [ 2 ] files s y s . a rgv [ 3 : ] =

=

=

f o r l i n e i n f i l e i nput . i nput ( f i l e s , i np l a ce=T rue ) : i f f i l e i nput . i s f i r s t l ine ( ) : sys . stde r r . wr i t e ( ' S t a r t e d p r o ce s s i n g { } \ n ' . format ( f i l e i nput . f i lename ( ) ) ) sys . s t d e r r . w r i t e ( ' D i r e c t o r y con t a i n s : { } \ n ' . fo rmat ( g l ob . g l ob ( ' e t c_ho s t s . t x t * ' ) ) ) l ine l i ne . r s t r i p ( ) . repl a ce ( f rom_ba s e , t o_ba s e ) p r i n t ( l i ne ) =

s y s . s t d e r r . w r i t e ( ' F i n i shed proce s s i ng \ n ' ) s y s . s td e r r . w r i t e ( ' Di rect o ry contai n s : { } \n ' . format ( g l ob . g l ob ( ' e t c_hos t s . txt * ' ) ) )

Резервный фа йл удаляется сразу же после закрытия входного файла.

14.10. atexlt: llbl30ll функцмii МН18PflP8'fBfOPOМ nрм •верwенмм pa6cnw nроrраммы

889

$ pythonЗ f i l e i nput_change_ s uЬne t_no i s y . py 1 0 . 1 6 . 1 0 . 1 7 . e t c hos t s . t xt Started pro ce s s ing e t c_hos t s . txt Directory cont a i n s : [ ' e t c_ho s t s . txt ' , ' e t c_ho s t s . txt . bak ' ] Fini shed proce s s ing Di rectory c o n t a i n s : [ ' e t c_ho s t s . t xt ' ) Дополнительные ссылки

Раздел документации стандартной библиотеки, посвященный модулю f i l e i nput 21 • 22 m3ut o r s s • Сценарий для преобразования МЗu-файлов, содержащих списки файлов МРЗ, в файлы RSS, пригодные для использования в качестве каналов подкастов. xml . e t ree. Более подРобное описание использования класса E l ement T re e для полу­ чения ХМL-файлов.

• •



1 4. 1 О.

вызов функций интерпретатором при завершении работы программы

а texi t :

Модуль а texi t предоставляет интерфейс для регистрации функций заверше­ ния, которые должны вызываться интерпретатором перед нормальным заверше­ нием работы программы.

1 4. 1 0. 1 . Регистрация функций завершения В следующем примере выполняется я вная регистрация функ ц ии обратного вызова с помощью функции reg i s ter ( ) . Листинr 1 4.97.

а

texi t s imple . ру _

import atex i t

d e f a l l _done ( ) : p r i n t ( ' al l_done ( ) ' ) p r i n t ( ' Reg i s t e r i ng ' ) a t e x i t . re g i s t e r ( al l_done ) p r i nt ( ' Reg i s t ered ' )

Поскольку программа не выполняет никаких других действий, функ��ия a l l _ done ( ) вызывается немедленно. $ pythonЗ atexi t_s impl e . py Reg i s t e r ing Reg i s t e red al l_done ( ) 2

1

22

https : / /do c s . python . o rg / 3 . 5 / l ibrary/ f i l e i nput . html https : / /pypi . python . o rg /pypi /mЗu t o r s s

990

Также допускается регистрация нескольких функций с передачей им аргумен­ тов. Эту возможность удобно использовать, в частности, для отклю•1ения от баз данных или удаления временных файлов при завершении работы программы. Вместо того чтобы поддерживать список ресурсов, которые требуется освобо­ ждать, можно зарегистрировать для каждого ресурса соответствующую функци ю очистки. Листинг 1 4.98. atexi t_mul tiple . py impo r t a t ex i t de f my_cl eanup ( name ) : p r i n t ( ' my_c l eanup ( { } ) ' . format ( name ) ) a t ex i t . reg i s t e r ( my_c l eanup , a t ex i t . reg i s t e r ( my_c l eanup , a t ex i t . reg i s t e r ( my_c l e anup ,

' fi rst ' ) ' s econd ' ) ' th i rd ' )

Очередность выполнения функций очистки обратна очередности их регистра­ ции. Благодаря этому операции по освобождению ресурсов, связанных с отдель­ ными модулями, могуг выполняться в порядке, обратном порядку импорта моду­ лей (а значит, и вызова их функций a t e x i t ) , •1то уменьшает вероятность возник­ новения конфликтов между зависимостями. $ pythonЗ a t e x i t_mul t i ple . py my_cl ea nup ( th i r d ) my_c l eanup ( s econd ) my_c l eanup ( f i r s t )

1 4. 1 0.2. Синтаксис декораторов Функции, не требующие задания аргументов, можно регистрировать, исполь­ зуя функцию regi s t e r ( ) в качестве декоратора. Этот альтернативный синтаксис удобно применять в случае функций очистки ресурсов, воздействующих на гло­ бальные данные уровня модуля. Листинг 1 4.99. atexi t_decorator . py import a t e x i t @ a t ex i t . re g i s t e r d e f a l l_done ( ) : p r i n t ( ' a l l_done ( ) ' ) p r i n t ( ' s t a r t i n g ma i n program ' )

Поскольку подобные функция регистрируется во время их определения, важ­ но быть уверенным в том , что они работают корректно, даже если модуль не вы­ полняет никакой другой работы. Если освобождаемые ресурсы не инициализиро­ вались, то вызов функции обратного вызова, запускаемой при выходе из програм­ мы, не должен приводить к ошибке.

14.10. atexlt: выаов функци й иtnepnpe1a1opoм при заверwении ра6сnъ1 nроrраммы

991

$ pythonЗ a t e x i t_de corator . py s t a rt i ng ma i n program a l l_done ( )

1 4. 1 0.3. Отмена регистрации функци й обратного вызова Чтобы отменить регистрацию функций обратного вызова, которые долж­ ны вызываться при выходе и з программы, следует использовать функцию un regi s t e r ( ) . Листинг 1 4. 1 00. atexi t_unregi ster . py impo rt atex i t

d e f my_cl e anup ( name ) : p r i n t ( ' my_c l e anup ( { } ) ' . forma t ( name ) ) a t e x i t . re g i s t e r ( my_cl eanup , atex i t . regi s t e r ( my_cl eanup , a t e x i t . reg i s t e r ( my_c l e anup ,

' fi rst ' ) ' s e cond ' ) ' t h i rd ' )

atex i t . unreg i s t e r ( my_c l e anup )

При этом отме н яются все вызовы одной и той же функции, н езависимо от того, сколько раа она была зарегистрирована. $ pytho n З a t e xi t_un regi s t e r . py

Отмена ре1·истрации функции обратного вызова, которая не была зарегистри· рована, не сч итается ошибкой. Л истинг 1 4. 1 01 . atexi t_unregi ster_not_regi s tered . py import atex i t

d e f my_c l e anup ( name ) : p r i n t ( ' my_c l eanup ( { } ) ' . format ( name ) ) i f Fal s e : atex i t . re g i s t e r ( my_c l e anup ,

' neve r regi s t e red ' )

atex i t . unreg i s t e r ( my_c l e anup )

Поскольку фун кция unregi s t e r ( ) игнорирует неизвестные функции обратно­ го вызова, се можно использовать даже в тех случаях, когда 11оследователыюсть регистрации этих функций неизвестна. $ pythonЗ a t e x i t_un regi s t e r_not_reg i s t e r ed . py

992

1 4. 1 0.4. Случаи, когда функции обратного вызова модуля а texi t не вызываются Функции обратного вызова, зарегистрированные с помощью модуля atexi t, не вызываются в случае наступления следующих событий: •

• •

выполнение программы преждевременно прекращается в результате посту­ пления сигнала; явно вызвана функция o s . exi t ( ) ; возникла фатальная ошибка в интерпретаторе . _

Чтобы продемонстрировать, что имен н о происходит в случае преждевре­ менного прекращения выполнения программы в результате поступления сиг­ нала, можно обновить пример, который использовался при описании модуля subproce s s (раздел 1 0. 1 ) . В этом примере участвуют два файла, содержащие ро­ дительскую и дочернюю программы соответственно. Родительская программа за­ пускает дочернюю, затем выдерживает паузу, после чего прекращает выполнение дочерней программы. Листинг 1 4. 1 02. a texi t_siqnal_parent . py import import import import

os s i gn a l s ubp roce s s t ime

proc s ubp roce s s . Pop e n ( ' . / a t ex i t s i gn a l chi l d . py ' ) p r i nt ( ' PARENT : Pau s i ng b e f o r e s e ndi ng s i gnal . . . ' ) time . s l e ep ( l ) p r i n t ( ' PARENT : S i g n a l i ng ch i ld ' ) os . k i l l ( proc . p i d , s i gnal . S I GTERМ ) =

Дочерняя программа усnшавливает функцию обратного вызова, срабатываю­ щую при нормальном завершении программы, а затем "засыпает" до получения сиг н ала. Листинг 1 4. 1 03.

а

texi t siqnal_child . ру _

import atex i t import t ime import s ys def not_ca l l ed ( ) : p r i nt ( ' CH I LD : a t ex i t handl e r shou l d not have been cal l e d ' ) p r i nt ( ' CH I L D : Reg i s t e r ing a t ex i t handl e r ' ) sys . s t dout . f l ush ( ) a t ex i t . reg i s t e r ( not_c a l l e d ) print ( ' CHI L D : Pau s i n g t o wa i t f o r s ig n a l ' ) sys . s t dout . f lush ( ) t ime . s l eep ( S )

2.4.10. 8t8Xlt: BW308 функций ИН18рnре1а1ором nри эвнрwенми ре6о1ы nроrраммы

993

Выполнение этого сценария дает следующие результаты. $ pythonЗ a t ex i t_s i gna l_pa rent . py CHILD : CHILD :

Reg i s t e r ing a t e x i t hand l e r Paus i ng t o wai t f o r s i gn a l PARENT : Paus i ng b e f o r e s e ndi ng s i gna l . . . PARENT : S i g na l i ng c h i ld

Дочерняя программа не выводит сообщение, встроенное в функцию not_ cal led ( ) . Используя вызов o s . exi t ( ) , программист может избежать вызова функции обратного вызова при завершении работы программы. _

Листинг 1 4. 1 04. atexit_os_exi t . py irnp o r t a t ex i t irnport o s

d e f not_ca l l ed ( ) : p r int ( ' Th i s s h o u l d not Ье c a l l ed ' ) p r i n t ( ' Re g i s t e ri n g ' ) a t ex i t . regi s t e r ( not_c a l l e d ) p r i n t ( ' Reg i s t e re d ' ) p r i n t ( ' Ex i t i ng . . . ' ) o s . _e x i t ( O )

Поскольку в данном примере не используется обычный путь выхода из про­ граммы, интерпретатор не вызывает функцию завершения. Кроме того, функция p r int в ней не будет выполнена, и сброс буфера для нес не потребуется, поэтому пример выполняется без использования параметра командной строки - u , активи­ зирующего буферизованный ввод-вывод. $ pythonЗ -u atex i t_os_ex i t . py Reg i s t e r i ng Re g i s t e re d Ex i t i ng . . .

Чтобы гарантировать выполнение функций обратного вызова, следует предо­ ставить программе возможность выполнить все предусмотренные в ней инструк­ ции или вызвать функцию sys . exi t ( ) .

Листинг 1 4. 1 05 . atexi t_sys_exi t . py irnport a t ex i t irnport s y s de f a l l_done ( ) : p r i nt { ' al l_done ( ) ' )

ГА888 1.4. Сrромtмыt ые 6мжм nрможенмй

994 p r i n t ( ' Reg i s t e r i ng ' ) a t e x i t . re g i s t e r ( a l l_done ) p r i nt ( ' Re g i s t e red ' ) p r i nt ( ' Ex i t i ng . . . ' ) s y s . ex i t ( )

В этом примере вызывается функция sys . exi t ( ) , которая обеспечивает вы­ полнение зарегистрированных функций обратного вызова при выходе из про­ граммы. $ pytho n З a t e x i t _sys_ex i t . py Reg i s t e r i n g Re g i s t e red E x i t i ng . . . a l l _done ( )

1 4. 1 О.5. Обработка исключений Трассировочная информация о б исключениях, возбужденных о функциях об­ ратного вызова модуля atexit, выводится на консоль. Последнее из исключений возбуждается повторно и и с пользуется в качестве итогового сообщения об ошиб­ ке, возникшей в программе. Листинг 1 4. 1 06 . a texi t_exception . py import a t e x i t d e f exi t_w i t h_except i on (me s sage ) : r a i s e Runt imeE rror ( me s s a g e ) a t e x i t . re g i s t e r ( e x i t_wi th_except i o n , a t ex i t . re g i s t e r ( e x i t_w i t h_except i o n ,

' Re g i s tered f i r s t ' ) ' Re g i s t e red s econd ' )

Порядок регистрации функций обратного вызова определяет порядок их вы­ полнения. Если ошибка в одной из этих функций вызывает ошибку в другой (за­ регистрированной ранее, но выполненной позже) , то итоговое сообщение об ошибке может п е быть наиболее полезным для отображения пользователю. $ pythonЗ a t e x i t_except ion . py E r r o r in a t ex i t . run e x i t funcs : T r a ceba c k ( mo s t r e cent ca l l l a s t ) : Fi l e " a t e x i t_ex cept i on . py " , l i ne 1 1 , in e x i t_wi th_e x c e p t i o n r a i s e Run t ime E r ro r ( me s s age ) Run t imeError : Re g i s t e red s e cond Error in a t e x i t . _run_ex i t funcs : Tra ceba c k (mo s t recent ca l l l a s t ) : Fi l e " a t e x i t_except i on . py " , l i ne 1 1 , i n exi t_w i t h_exception ra i s e Run t imeE rro r ( me s s age ) Run t ime E r r o r : Reg i s t e red f i r s t

995

Как правило, лучше всего обрабатывать и протоколировать все исключения, возникающие в функциях очистки, без вывода соответствующих сообщений на консоль: отображение ошибок после аварийного завершения работы программы придает результирующему выводу неаккуратный вид. Дополн ительные ссыпки • • •

Раздел документации стандартной библиотеки, посвященный модулю a t e x i t 23• Раздел 1 7.2.4. Глобальная обработка неперехваченных исключений. Замечания относительно портирования программ из Python 2 в Python 3, касающиеся модуля a t ex i t (раздел А.6.5).

1 4. 1 1 .

s ched:

планирование запуска событий

Модуль sched реализует обобщенный планировщик событий, обеспечиваю­ щий запуск задач в определенное время. Класс schedu l e r использует функцию t ime ( ) для определения текущего времени и функцию delay ( ) для задания паузы определенной длительности. Фактические единицы времени неважны, поэтому интерфейс достаточно гибок для того, чтобы его можно было использовать для многих целей. Функция t ime ( ) вызывается без аргументов и должна возвращать число, пред­ ставляющее текущее время. Функция delay ( ) вызывается с единственным цело­ численным аргументом, используя ту же шкалу, что и функция t ime ( ) , и должна выждать в течение заданного промежугка времени, прежде чем вернуть резуль­ тат. По умолча нию используются функции mono tonic ( ) и s leep ( ) из модуля t ime ( раздел 4. 1 ) , но в приведенных в этом разделе примерах используется фун к­ ция t ime . t ime ( ) , которая также удовлетворяет необходимым требованиям, по­ скольку упрощает чтение вывода. Для поддержки многопоточных приложений функция delay ( ) должна вызы­ ваться с аргументом О после генерации каждого события, чтобы гарантировать возможность запуска каждого потока.

1 4. 1 1 . 1 . Запуск событий с задержкой Можно планировать запуск событий с задержкой или на определенное время . Чтобы запланировать запуск события с задержкой , следует использовать метод ente r ( ) , который имеет четыре аргумента: • • • •

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

В этом примере запуск двух различных событий назначается через 2 и 3 секун­ ды соответственно. Когда наступает время запуска события, вызывается функция print _ event ( ) , которая выводит текущее время и имя аргумента, переданного событию. 23

h t tp s : / /doc s . python . o r g / 3 . 5 / l ib r a r y / a t e xi t . html

Листинг 1 4. 1 07. sched_basic . ру import s ched import t ime s chedu l e r = s ched . s c hedul e r ( t ime . t ime , t ime . s l e ep )

de f p r i nt_even t ( name , s t a rt ) : now = t i me . t ime ( ) e l apsed = i n t ( now - s t a r t ) print ( ' EVENT : { } e l aps ed= { } name= ( } ' . format ( t ime . ct ime ( now ) , e l ap s e d , name ) ) s t a rt = t ime . t ime ( ) p r i n t ( ' START : , t ime . ct ime ( s t art ) ) s chedul e r . ent e r ( 2 , 1 , p r i n t_even t , s chedu l e r . en t e r ( З , 1 , p r i n t_event , '

( ' fi r s t ' , s t a r t ) ) ( ' s e cond ' , s t a rt ) )

s chedu l e r . run ( )

Выполнение :iтой программы дает следую щие резуш,таты. $ pythonЗ s ched_ba s i c . py START : Sun Sep 4 1 6 : 2 1 : 0 1 2 0 1 6 EVENT : Sun Sep 4 1 6 : 2 1 : 0 3 2 0 1 6 e l aps ed=2 name= f i r s t EVENT : Sun S ep 4 1 6 : 2 1 : 0 4 2 0 1 6 e l apsed=З name= s e cond

Время , выводимое для перво1·0 события , составляет 2 секунды, а время, выве­ денное для второго события, - 3 секунды пос.ле :�апуска программы.

1 4. 1 1 .2. Перекрывающиеся события Вызов метода run ( ) блокируется до тех нор. пока не будут обработаны все со­ бытия. Каждое событие выполняется в одном и том же потоке, и поэтому в тех случаях, когда выполнение события занимает больше времени , чем промежуток между событиями, наступает перекрытие событий. Это перекрытие разрешается переносом последующих событий на более позднее время. Таким образом, собы· тия не теряются , однако некоторые из них могут быть вызваны позже запланиро­ ванного момента времени. В следующем примере функция long_event ( ) просто засыпает на 2 секунды , но точно так же эта задержка могла бы быть обусловлена вьпюлнением интенсивных вычислений или блокирующими операциями вво­ да-вывода. Листинг 1 4. 1 08. sched_overlap . py import s ched import t ime s chedu l e r

=

s ched . s chedu l e r ( t ime . t ime , t ime . s l eep )

14.11. IChed: манмроанме запуска с:о6wrмй

997

d e f l ong_event ( name ) : p r i n t ( ' BEG I N EVENT : ' , t ime . ct ime ( t ime . t ime ( ) ) , name ) t ime . s l e ep ( 2 ) p r i n t ( ' F I N I S H EVENT : ' , t ime . ct ime ( t ime . t ime ( ) ) , name ) p r i n t ( ' START : ' , t ime . ct ime ( t ime . t ime ( ) ) ) s chedul e r . e n t e r ( 2 , 1 , l ong_event , ( ' fi r s t ' , ) ) s chedu l e r . en t e r ( З , 1 , l ong_event , ( ' s e cond ' , ) ) s chedu l e r . run ( )

В результате этого второе событие запускается сразу же после завершения пер­ во1·0, поскольку п рохождение первого события занимает достаточно много вре­ мени для того, чтобы перенести время запуска второго события . $ pythonЗ s ched_ove r l ap . py START : Sun S e p 4 1 6 : 2 1 : 0 4 2 0 1 6 BEG I N EVENT : Sun Sep 4 1 6 : 2 1 : 0 6 F I N I S H EVENT : Sun Sep 4 1 6 : 2 1 : 0 8 BEG I N EVENT : Sun Sep 4 1 6 : 2 1 : 0 8 F I N I SH EVENT : Sun Sep 4 1 6 : 2 1 : 1 0

2016 2016 2016 2016

first first s e cond s e cond

1 4. 1 1 .3. Приоритеты событий Ее.ли несколько событий запланированы па одно и то же время , то для опреде­ ления очередности их запуска используются значения приоритета. Листинг 1 4. 1 09. schedyriori ty . py irnp o r t s ched irnport t irne s chedu l e r

=

s ched . s chedu l e r ( t irne . t irne , t irne . s l ee p )

d e f p r i n t_event ( narne ) : p r i n t ( ' EVENT : ' , t irne . ct irne ( t irne . t irne ( ) ) , narne ) now t irne . t irne ( ) p r i n t ( ' START : ' , t irne . c t irne ( now ) ) s chedul e r . e n t e rabs ( now + 2 , 2 , p r i n t_eve n t , s chedul e r . e n t e rabs ( now + 2 , 1 , p r i n t_eve n t , =

( ' fi r s t ' , ) ) ( ' s e cond ' , ) )

s chedul e r . run ( )

В этом примере необходимо обеспечить запуск событий ст�ю1·0 в одно и ·го же время, поэтому вместо метода ent e r ( ) используется метод enterabs ( ) , аргумен­ том ко·rо1юго является время, а не задержка времени запуска события. $ pythonЗ s ched_p r i o r i ty . py START : Sun S e p 4 1 6 : 2 1 : 1 0 2 0 1 6

998 EVENT : S uп Sep 4 1 6 : 2 1 : 1 2 2 0 1 6 s e cond EVENT : S un S e p 4 1 6 : 2 1 : 1 2 2 0 1 6 f i r s t

1 4. 1 1 .4. Отмена событий Обе функции, enter ( ) и ente rabs ( ) , возвращают ссылку на событие, которая впоследствии может быть использована для его отмены. Поскольку метод run ( ) блокируется, для отмены события необходимо использовать другой поток. В дан­ ном примере планировщик запускается в дополнительном потоке, а ос новной по­ ток используется для отмены события. Листинr 1 4. 1 1 0. sched_cancel . ру irnp o r t s ched irnpo rt threading irnport t irne s chedu l e r = s ched . s chedul e r ( t irne . t irne , t irne . s l e ep ) # Задать глобаль ную переме нную , значение которой будет # и змен ять с я потоками coun t e r О =

d e f i n c rernen t_coun t e r ( narne ) : g l obal count e r p r i n t ( ' EVENT : ' , t irne . c t irne ( t irne . t irne ( ) ) , narne ) count e r += 1 p r i n t ( ' NOW : ' , coun t e r ) p r i n t ( ' START : ' , t irne . c t irne ( t i rne . t irne ( ) ) ) el s chedul e r . e n t e r ( 2 , 1 , i n c rernent_coun t e r , е2 s chedul e r . e n t e r ( З , 1 , i n c rerne n t_count e r , =

( ' El ' , ) ) ( ' Е2 ' , ) )

# За пустить поток для выполнени я событий t = threading . Thread ( t a rg e t = s ch e dul e r . run ) t . s tart ( ) # Отме нить в основн ом потоке первое и з запла нированных с обытий s chedul e r . cance l ( e l ) # Выжда ть , пока не заверши тс я выполнение планировщика в п о т о ке t . j oin ( ) pr i n t ( ' FI NAL : ' , c ounte r )

Были запланированы два события, однако впоследствии первое и з них было отменено. Выполняется лишь второе событие, поэтому переменная c ounter ин­ крементируется только один раз. $ pythonЗ s ched cancel . py START : Sun Sep 4 1 6 : 2 1 : 1 3 2 0 1 6 EVENT : Sun Sep 4 1 6 : 2 1 : 1 6 2 0 1 6 Е2

14.U. sched: манмрованме эапусма co61onиi NOW : 1 FINAL : 1 Дополнительные ссылки • •

24

Раздел документации стандартной библиотеки, посвященный модулю t irne (раздел 4.1 ). Описание модуля t irne .

ht tps : / /docs . python . o rg / 3 . 5 / l ib r a r y / s ched . htrnl

s ched24 •

Глава 15 Интернационализация и локализация приложений Python поставляется с двумя модулями, позволяющими подготовить приложение к использованию нескольких языков и региональных настроек. Модуль gettext (раздел 15.1) обеспечивает отображение командных подсказок и сообщений об ошибках на языке, понятном пользователю, за счет создания каталогов с сообщениями, переведенными на различные языки. Модуль locale (раздел 15.2) изменяет форматирование чисел, денежных единиц, значений даты и времени с учетом таких региональных стандартов, как обозначение отрицательных значений или символ местной денежной единицы. Оба модуля взаимодействуют с  другими инструментами и рабочей средой, обеспечивая согласованность приложения на языке Python с остальными программами, установленными в данной системе.

15.1. gettext: каталоги сообщений

Модуль gettext предоставляет совместимую с библиотекой GNU gettext реализацию, распознающую только код на языке Python, с помощью которой можно управлять сообщениями, переведенными на другие языки, и каталогами, в которых они содержатся. Инструменты, доступные в исходном дистрибутиве Python, позволяют извлекать сообщения из набора исходных файлов, создавать каталоги, содержащие переводы сообщений, и использовать их на этапе выполнения для отображения информации на языке, понятном пользователю. Каталоги сообщений можно также использовать для настройки других параметров, в том числе для создания интерфейсов-оберток. Примечание

Несмотря на то что в документации стандартной библиотеки утверждается, что Python включает все необходимые инструменты, программа pygettext.py неспособна извлекать сообщения, обернутые вызовом функции ngettext(), даже с использованием соответствующих параметров командной строки. В приведенных в этом разделе примерах вместо программы pygettext.py используется утилита xgettext из набора инструментов GNU gettext.

15.1.1. Обзор процесса перевода сообщений Процесс настройки и использования сообщений, переведенных на различные языки, включает следующие пять этапов. 1. Идентификация и маркирование в исходном коде тех литеральных строк, которые содержат сообщения, подлежащие переводу на другие языки.

Глава 15. Интернационализация и локализация приложений

1002

Начните с идентификации в исходном коде сообщений, которые должны быть переведены на другой язык, и пометьте соответствующие литеральные строки, чтобы программа, предназначенная для их извлечения, могла их найти. 2. Извлечение сообщений. Идентифицировав подлежащие переводу строки, используйте программу xgettext для их извлечения и создайте шаблон перевода в виде .pot-файла. Шаблон перевода — это текстовый файл, содержащий копии всех идентифицированных строк и поля, в которые должны быть вставлены переводы сообщений. 3. Перевод сообщений Передайте переводчику копию .pot-файла, изменив его расширение на .po. Файл с расширением .po — это редактируемый файл, используемый в качестве входного на этапе компиляции. Переводчик должен обновить текст заголовка в файле и предоставить перевод для каждой строки. 4. Компиляция каталога переведенных сообщений. Получив от переводчика готовый текстовый .po-файл, скомпилируйте его в двоичный формат каталога с помощью функции msgfmt. Двоичный формат используется при поиске подстановочных значений в каталоге во время выполнения. 5. Загрузка и активизация подходящего каталога сообщений во время выполнения. Окончательным шагом является добавление в программу нескольких строк, обеспечивающих конфигурирование и загрузку каталога сообщений, а также настройку функции translation(). Это можно сделать несколькими способами, однако все они требуют компромиссных решений. В оставшейся части раздела мы подробно рассмотрим каждый из этих этапов по отдельности, начав с внесения в код необходимых изменений.

15.1.2. Создание каталога сообщений на основе исходного кода Работа модуля gettext заключается в поиске литеральных строк, содержащихся в базе данных, и извлечении соответствующего перевода для каждой строки. Обычным приемом является связывание поисковой функции с именем _ (одиночный символ подчеркивания), позволяющим избавиться от загромождения кода множеством вызовов функций с длинными именами. Программа для извлечения сообщений, xgettext, ищет сообщения, которые встроены в вызовы функций, выполняющих поиск в каталоге. Она распознает различные исходные языки и использует для каждого из них соответствующий парсер. При наличии поисковых функций, имеющих псевдонимы (а также в случае добавления новых функций), необходимо передать программе xgettext имена дополнительных символов, которые следует учитывать при извлечении сообщений.

15.1. gettext: каталоги сообщений

1003

В представленном ниже сценарии имеется одно сообщение, подготовленное к переводу. Листинг 15.1. gettext_example.py t = gettext.translation( 'example_domain', 'locale', fallback=True, ) _ = t.gettext print(_('This message is in the script.'))

Текст "This message is in the script." — это сообщение, которое должно быть заменено соответствующим переводом из каталога сообщений. Включение резервного режима (fallback) обеспечивает вывод встроенного сообщения, если при выполнении сценария каталог сообщений окажется недоступным. $ python3 gettext_example.py This message is in the script.

Следующим шагом является извлечение сообщения и создание .pot-файла с помощью программы pygettext.py или xgettext. $ xgettext -o example.pot gettext_example.py

Содержимое результирующего файла приведено ниже. Листинг 15.2. example.pot # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-07-10 10:45-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: gettext_example.py:19 msgid "This message is in the script." msgstr ""

1004

Глава 15. Интернационализация и локализация приложений

Каталоги сообщений создаются в каталогах, структурированных по признакам домена и языка. Домен предоставляется приложением или библиотекой, и обычно ему соответствует какое-либо уникальное значение, например имя приложения. В сценарии gettext_example.py домен определяется значением example_domain. Значение параметра, определяющего язык, предоставляется пользовательской средой времени выполнения через одну из переменных среды LANGUAGE, LC_ALL, LC_MESSAGES или LANG, в зависимости от конфигурации и платформы. Все примеры, приведенные в этом разделе, выполнялись с использованием значения en_US для параметра языка. Следующим после подготовки шаблона шагом является создание требуемой структуры каталогов и копирование шаблона в соответствующее расположение. Во всех приведенных ниже примерах в качестве корневого каталога сообщений используется каталог locale, принадлежащий дереву каталогов PyMOTW, но, как правило, для этих целей лучше использовать каталог, к которому можно обращаться из любого расположения в системе, чтобы доступ к нему имели все пользователи. Полный путь к исходному файлу каталога сообщений имеет следующий вид: $localedir/$language/LC_MESSAGES/$domain.po, а фактическим каталогом сообщений является файл с расширением .mo. Чтобы создать каталог, следует скопировать файл шаблона example.pot в расположение locale/en_US/LC_MESSAGES/example.po и внести в него соответствующие исправления, изменив значения в заголовке и задав альтернативные варианты сообщений (листинг 15.3). Листинг 15.3. locale/en_US/LC_MESSAGES/example.po # Messages from gettext_example.py. # Copyright (C) 2009 Doug Hellmann # Doug Hellmann , 2016. # msgid "" msgstr "" "Project-Id-Version: PyMOTW-3\n" "Report-Msgid-Bugs-To: Doug Hellmann \n" "POT-Creation-Date: 2016-01-24 13:04-0500\n" "PO-Revision-Date: 2016-01-24 13:04-0500\n" "Last-Translator: Doug Hellmann \n" "Language-Team: US English \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: gettext_example.py:16 msgid "This message is in the script." msgstr "This message is in the en_US catalog."

Каталог создается на основе .po-файла с помощью утилиты msgfmt: $ cd locale/en_US/LC_MESSAGES; msgfmt -o example.mo example.po

15.1. gettext: каталоги сообщений

1005

В качестве домена в сценарии gettext_example.py используется example_ domain, однако файл называется example.pot. Чтобы модуль gettext мог найти соответствующий файл с переводом сообщения, эти имена должны совпадать. Листинг 15.4. gettext_example_corrected.py t = gettext.translation( 'example', 'locale', fallback=True, )

Теперь при запуске сценария будет выводиться не встроенная строка, а сообщение из каталога. $ python3 gettext_example_corrected.py This message is in the en_US catalog.

15.1.3. Поиск каталогов сообщений во время выполнения Как уже отмечалось, каталог локалей, содержащий каталоги сообщений, организуется на основе поддерживаемых языков, тогда как имена каталогов сообщений формируются на основе доменов программ. Различные операционные системы определяют собственные значения по умолчанию, но модулю gettext ничего неизвестно обо всех этих умолчаниях. Он использует заданный по умолчанию каталог локалей sys.prefix + '/share/locale', но в большинстве случаев безопаснее явно задавать значение каталога locale, чем надеяться на то, что значение по умолчанию всегда является корректным. Функция find() позволяет находить нужный каталог сообщений во время выполнения. Листинг 15.5. gettext_find.py import gettext catalogs = gettext.find('example', 'locale', all=True) print('Catalogs:', catalogs)

Часть пути, ассоциированная с языком, берется из одной из нескольких переменных среды, пригодных для конфигурирования средств локализации (LANGUAGE, LC_ALL, LC_MESSAGES и LANG). Фактически используется первая найденная переменная из числа указанных. Можно выбрать несколько языков, разделив идентифицирующие их значения символами двоеточия (:). Чтобы продемонстрировать, как это работает, ниже в качестве примера приведены результаты нескольких запусков сценария gettext_find.py. $ cd locale/en_CA/LC_MESSAGES; msgfmt -o example.mo example.po $ cd ../../.. $ python3 gettext_find.py Catalogs: ['locale/en_US/LC_MESSAGES/example.mo'] $ LANGUAGE=en_CA python3 gettext_find.py

1006

Глава 15. Интернационализация и локализация приложений

Catalogs: ['locale/en_CA/LC_MESSAGES/example.mo'] $ LANGUAGE=en_CA:en_US python3 gettext_find.py Catalogs: ['locale/en_CA/LC_MESSAGES/example.mo', 'locale/en_US/LC_MESSAGES/example.mo'] $ LANGUAGE=en_US:en_CA python3 gettext_find.py Catalogs: ['locale/en_US/LC_MESSAGES/example.mo', 'locale/en_CA/LC_MESSAGES/example.mo']

Несмотря на то что функция find() отображает полный список каталогов, лишь первый из них в этой последовательности фактически загружается для поиска сообщений. $ python3 gettext_example_corrected.py This message is in the en_US catalog. $ LANGUAGE=en_CA python3 gettext_example_corrected.py This message is in the en_CA catalog. $ LANGUAGE=en_CA:en_US python3 gettext_example_corrected.py This message is in the en_CA catalog. $ LANGUAGE=en_US:en_CA python3 gettext_example_corrected.py This message is in the en_US catalog.

15.1.4. Грамматические формы для множественного числа В то время как простая подстановка сообщений позволяет справиться с большинством проблем их перевода, встречающиеся в сообщениях формы множественного числа имен существительных обрабатывается модулем gettext как специальные случаи. В зависимости от языка различия между формами единственного и множественного числа могут требовать изменения не только окончаний отдельных членов предложения, но и всей структуры сообщения. Кроме того, формы множественного числа в сообщениях также могут зависеть от включаемой в сообщение величины числовой характеристики. Чтобы упростить (а в некоторых случаях сделать вообще возможным) управление множественными формами сообщений, для их запроса предоставляется отдельный набор функций. Листинг 15.6. gettext_plural.py from gettext import translation import sys t = translation('plural', 'locale', fallback=False) num = int(sys.argv[1])

15.1. gettext: каталоги сообщений

1007

msg = t.ngettext('{num} means singular.', '{num} means plural.', num) # Все еще требуется самостоятельно добавлять значения в сообщение print(msg.format(num=num))

Для доступа к нескольким подстановочным сообщениям следует использовать функцию ngettext(), которая получает в качестве аргументов сообщения, требующие перевода, и счетчик элементов. $ xgettext -L Python -o plural.pot gettext_plural.py

Ввиду существования альтернативных форм сообщения, требующих перевода, подстановочные значения возвращаются в виде массива. Использование массива облегчает перевод в случае языков с несколькими формами множественного числа (например, в польском языке существуют различные формы для указания относительного количества). Листинг 15.7. plural.pot # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-07-10 10:45-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: gettext_plural.py:15 #, python-brace-format msgid "{num} means singular." msgid_plural "{num} means plural." msgstr[0] "" msgstr[1] ""

Кроме предоставления строк перевода необходимо уведомить библиотеку о способе формирования грамматических форм множественного числа, чтобы ей было известно, как индексировать массив для любого заданного значения счетчика. Строка "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" включает два значения, которые должны быть заменены вручную: nplurals  — целое

1008

Глава 15. Интернационализация и локализация приложений

число, указывающее на размер массива (количество используемых переводов), и plural — выражение на языке C, преобразующее указанное количество в индекс массива при поиске перевода. Литеральная строка n заменяется количественной величиной, переданной функции ungettext(). Можно привести пример из английского языка, включающий две формы числа. Количество 0 трактуется как множественное (“0 bananas”). Строка PluralForms в этом случае приобретает следующий вид: Plural-Forms: nplurals=2; plural=n != 1;

Перевод, соответствующий единственному числу, должен помещаться в позицию 0, а перевод, соответствующий множественному числу, — в позицию 1. Листинг 15.8. locale/en_US/LC_MESSAGES/plural.po # Messages from gettext_plural.py # Copyright (C) 2009 Doug Hellmann # This file is distributed under the same license # as the PyMOTW package. # Doug Hellmann , 2016. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PyMOTW-3\n" "Report-Msgid-Bugs-To: Doug Hellmann \n" "POT-Creation-Date: 2016-01-24 13:04-0500\n" "PO-Revision-Date: 2016-01-24 13:04-0500\n" "Last-Translator: Doug Hellmann \n" "Language-Team: en_US \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;" #: gettext_plural.py:15 #, python-format msgid "{num} means singular." msgid_plural "{num} means plural." msgstr[0] "In en_US, {num} is singular." msgstr[1] "In en_US, {num} is plural."

Предварительно скомпилировав каталог, выполним тестовый сценарий несколько раз, чтобы продемонстрировать, как различные значения N преобразуются в индексы строк перевода. $ cd locale/en_US/LC_MESSAGES/; msgfmt -o plural.mo plural.po $ cd ../../.. $ python3 gettext_plural.py 0 In en_US, 0 is plural. $ python3 gettext_plural.py 1

15.1. gettext: каталоги сообщений

1009

In en_US, 1 is singular. $ python3 gettext_plural.py 2 In en_US, 2 is plural.

15.1.5. Локализация приложений и модулей Цели, которые преследуются при переводе текста, определяют способ установки и использования модуля gettext в теле кода.

15.1.5.1. Локализация приложения В случае переводов, действие которых распространяется на все приложение, автор программы может прибегнуть к глобальной установке такой, например, функции, как ngettext(), используя пространство имен __builtins__, поскольку он самостоятельно контролирует высокоуровневый код и ему понятен весь набор соответствующих требований, которые должны быть удовлетворены. Листинг 15.9. gettext_app_builtin.py import gettext gettext.install( 'example', 'locale', names=['ngettext'], ) print(_('This message is in the script.'))

Функция install() связывает функцию gettext() с именем _() в пространстве имен __builtins__. Она также добавляет функцию ngettext() и другие функции, перечисленные в аргументе names.

15.1.5.2. Локализация модуля Изменение пространства имен __builtins__ библиотеки или отдельного модуля — не совсем хорошая идея, поскольку это может породить конфликты с глобальными значениями приложения. Вместо этого лучше импортировать или повторно связать функции перевода вручную на верхнем уровне модуля. Листинг 15.10. gettext_module_global.py import gettext t = gettext.translation( 'example', 'locale', fallback=False, ) _ = t.gettext ngettext = t.ngettext print(_('This message is in the script.'))

1010

Глава 15. Интернационализация и локализация приложений

15.1.6. Переключение вариантов перевода Во всех предыдущих примерах на протяжении всего времени выполнения программы использовался один и тот же каталог сообщений. Однако в некоторых ситуациях, особенно в случае веб-приложений, в разное время должны использоваться разные каталоги сообщений, причем это должно осуществляться без выхода из приложения или переустановки переменных среды. В подобных случаях удобно использовать API на основе классов, предоставляемых модулем gettext. Вызовы этого API остаются в основном теми же, что и глобальные вызовы, описанные в данном разделе, однако предоставление объекта каталога сообщений, которым можно манипулировать непосредственно, обеспечивает возможность использования нескольких каталогов сообщений. Дополнительные ссылки ƒƒ Раздел документации стандартной библиотеки, посвященный модулю gettext1. ƒƒ locale (раздел 15.2). Модуль, содержащий другие инструменты локализации.

ƒƒ GNU gettext2. Все форматы каталога сообщений, API и другие вспомогательные средства для этого модуля основаны на оригинальном пакете GNU gettext. С этим пакетом совместимы форматы файлов каталогов, а сценарии командной строки имеют аналогичные (если не идентичные) параметры. В руководстве GNU gettext utilities3 содержится подробное описание форматов файлов и GNU-версий инструментов для работы с ними. ƒƒ Формы множественного числа4. Обработка форм множественного числа в словах и предложениях на различных языках. ƒƒ Internationalization and Nationalization (Martin von Löwis)5. Статья с описанием методик интернационализации приложений Python. ƒƒ Поддержка интернационализации приложений в Django6. Неплохой источник информации относительно использования модуля gettext, включающий примеры из реальных приложений.

15.2. locale: API локализации

Модуль locale является частью библиотеки Python, обеспечивающей поддержку интернационализации и локализации приложений. Она предоставляет стандартный способ выполнения операций, которые могут зависеть от языка общения или местоположения пользователя. В число таких операций входят, в частности, форматирование денежных значений, сравнение строк с целью их сортировки и работа с датами. Библиотека не поддерживает перевод текста с одного языка на другой (см. описание модуля gettext в разделе 15.1) и преобразование текста в кодировку Unicode (см. описание модуля codecs в разделе 6.10). 1 

https://docs.python.org/3.5/library/gettext.html www.gnu.org/software/gettext/ 3  www.gnu.org/software/gettext/manual/gettext.html 4  www.gnu.org/software/gettext/manual/gettext.html#Plural-forms 5  http://legacy.python.org/workshops/1997-10/proceedings/loewis.html 6  https://docs.djangoproject.com/en/dev/topics/i18n/ 2 

15.2. locale: API локализации

1011

Примечание Изменение локали может влиять на все приложение в целом, поэтому рекомендуется не изменять ее в библиотеке, а позволить приложению самостоятельно устанавливать ее в случае необходимости. В приведенных в этом разделе примерах локаль изменяется несколько раз в небольшой программе, чтобы продемонстрировать различия в настройке разнообразных локалей. Вероятно, в случае реальных приложений целесообразно устанавливать локаль лишь один раз при запуске приложения или получении веб-запроса, а не изменять ее многократно.

В этом разделе обсуждаются некоторые высокоуровневые функции, входящие в модуль locale. Другие функции — низкоуровневые (format_string()) или связанные с управлением локалью приложения (resetlocale()).

15.2.1. Проверка региональных настроек Наиболее распространенный способ дать пользователю возможность изменять настройки локали приложения — использовать переменную среды (LC_ALL, LC_CTYPE, LANG или LANGUAGE, в зависимости от платформы). Это позволяет вызывать функцию setlocale() без жестко заданных значений. Листинг 15.11. locale_env.py import locale import os import pprint # Значения по умолчанию, заданные в пользовательской среде locale.setlocale(locale.LC_ALL, '') print('Environment settings:') for env_name in ['LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE']: print(' {} = {}'.format( env_name, os.environ.get(env_name, '')) ) # Что собой представляет локаль? print('\nLocale from environment:', locale.getlocale()) template = """ Numeric formatting: Decimal point : "{decimal_point}" Grouping positions : {grouping} Thousands separator: "{thousands_sep}" Monetary formatting: International currency symbol Local currency symbol Symbol precedes positive value Symbol precedes negative value Decimal point

: : : : :

"{int_curr_symbol!r}" {currency_symbol!r} {p_cs_precedes} {n_cs_precedes} "{mon_decimal_point}"

1012

Глава 15. Интернационализация и локализация приложений

Digits in fractional values Digits in fractional values, international Grouping positions Thousands separator Positive sign Positive sign position Negative sign Negative sign position

: {frac_digits} : : : : : : :

{int_frac_digits} {mon_grouping} "{mon_thousands_sep}" "{positive_sign}" {p_sign_posn} "{negative_sign}" {n_sign_posn}

""" sign_positions = { 0: 'Surrounded by parentheses', 1: 'Before value and symbol', 2: 'After value and symbol', 3: 'Before value', 4: 'After value', locale.CHAR_MAX: 'Unspecified', } info = {} info.update(locale.localeconv()) info['p_sign_posn'] = sign_positions[info['p_sign_posn']] info['n_sign_posn'] = sign_positions[info['n_sign_posn']] print(template.format(**info))

Метод localeconv() возвращает словарь, содержащий правила данной локали. С полным списком названий и определений можно ознакомиться в документации стандартной библиотеки. На компьютере Mac, работающем под управлением OS X 10.11.6, выполнение этого сценария без присваивания переменным среды каких-либо значений дало следующие результаты: $ export LANG=; export LC_CTYPE=; python3 locale_env.py Environment settings: LC_ALL = LC_CTYPE = LANG = LANGUAGE = Locale from environment: ('None', 'None') Numeric formatting: Decimal point : "." Grouping positions : [] Thousands separator: "" Monetary formatting:

15.2. locale: API локализации International currency symbol Local currency symbol Symbol precedes positive value Symbol precedes negative value Decimal point Digits in fractional values Digits in fractional values, international Grouping positions Thousands separator Positive sign Positive sign position Negative sign Negative sign position

1013 : : : : : :

"''" '$' 127 127 "" 127

: : : : : : :

127 [] "" "" Unspecified "" Unspecified

Выполнив этот же сценарий с установленной переменной LANG, можно увидеть, как при этом изменится локаль и установленная по умолчанию кодировка символов. США (en_US) $ LANG=en_US LC_CTYPE=en_US LC_ALL=en_US python3 locale_env.py Environment settings: LC_ALL = en_US LC_CTYPE = en_US LANG = en_US LANGUAGE = Locale from environment: ('en_US', 'ISO8859-1') Numeric formatting: Decimal point : "." Grouping positions : [3, 3, 0] Thousands separator: "," Monetary formatting: International currency symbol Local currency symbol Symbol precedes positive value Symbol precedes negative value Decimal point Digits in fractional values Digits in fractional values, international Grouping positions Thousands separator Positive sign Positive sign position Negative sign Negative sign position

: : : : : :

"'USD'" '$' 1 1 "." 2

: : : : : : :

2 [3, 3, 0] "," "" Before value and symbol "-" Before value and symbol

1014

Глава 15. Интернационализация и локализация приложений

Франция (fr_FR) $ LANG=fr_FR LC_CTYPE=fr_FR LC_ALL=fr_FR python3 locale_env.py Environment settings: LC_ALL = fr_FR LC_CTYPE = fr_FR LANG = fr_FR LANGUAGE = Locale from environment: ('fr_FR', 'ISO8859-1') Numeric formatting: Decimal point : "," Grouping positions : [127] Thousands separator: "" Monetary formatting: International currency symbol Local currency symbol Symbol precedes positive value Symbol precedes negative value Decimal point Digits in fractional values Digits in fractional values, international Grouping positions Thousands separator Positive sign Positive sign position Negative sign Negative sign position

: : : : : :

"'EUR'" 'Eu' 0 0 "," 2

: : : : : : :

2 [3, 3, 0] " " "" Before value and symbol "-" After value and symbol

Испания (es_ES) $ LANG=es_ES LC_CTYPE=es_ES LC_ALL=es_ES python3 locale_env.py Environment settings: LC_ALL = es_ES LC_CTYPE = es_ES LANG = es_ES LANGUAGE = Locale from environment: ('es_ES', 'ISO8859-1') Numeric formatting: Decimal point : "," Grouping positions : [127] Thousands separator: ""

15.2. locale: API локализации

1015

Monetary formatting: International currency symbol Local currency symbol Symbol precedes positive value Symbol precedes negative value Decimal point Digits in fractional values Digits in fractional values, international Grouping positions Thousands separator Positive sign Positive sign position Negative sign Negative sign position

: : : : : :

"'EUR'" 'Eu' 0 0 "," 2

: : : : : : :

2 [3, 3, 0] " " "" Before value and symbol "-" Before value and symbol

Португалия (pt_PT) $ LANG=pt_PT LC_CTYPE=pt_PT LC_ALL=pt_PT python3 locale_env.py Environment settings: LC_ALL = pt_PT LC_CTYPE = pt_PT LANG = pt_PT LANGUAGE = Locale from environment: ('pt_PT', 'ISO8859-1') Numeric formatting: Decimal point : "," Grouping positions : [] Thousands separator: " " Monetary formatting: International currency symbol Local currency symbol Symbol precedes positive value Symbol precedes negative value Decimal point Digits in fractional values Digits in fractional values, international Grouping positions Thousands separator Positive sign Positive sign position Negative sign Negative sign position

: : : : : :

"'EUR'" 'Eu' 0 0 "." 2

: : : : : : :

2 [3, 3, 0] " " "" Before value and symbol "-" Before value and symbol

1016

Глава 15. Интернационализация и локализация приложений

Польша (pl_PL) $ LANG=pl_PL LC_CTYPE=pl_PL LC_ALL=pl_PL python3 locale_env.py Environment settings: LC_ALL = pl_PL LC_CTYPE = pl_PL LANG = pl_PL LANGUAGE = Locale from environment: ('pl_PL', 'ISO8859-2') Numeric formatting: Decimal point : "," Grouping positions : [3, 3, 0] Thousands separator: " " Monetary formatting: International currency symbol Local currency symbol Symbol precedes positive value Symbol precedes negative value Decimal point Digits in fractional values Digits in fractional values, international Grouping positions Thousands separator Positive sign Positive sign position Negative sign Negative sign position

: : : : : :

"'PLN'" 'z' 1 1 "," 2

: : : : : : :

2 [3, 3, 0] " " "" After value and symbol "-" After value and symbol

15.2.2. Денежные единицы Результаты предыдущего примера демонстрируют, что изменение локали приводит к соответствующему изменению символа валюты и символа, отделяющего дробную часть числа от целой. В следующем примере выводятся положительные и отрицательные денежные значения для нескольких локалей, перебираемых в цикле. Листинг 15.12. locale_currency.py import locale sample_locales = [ ('USA', 'en_US'), ('France', 'fr_FR'), ('Spain', 'es_ES'), ('Portugal', 'pt_PT'), ('Poland', 'pl_PL'), ]

15.2. locale: API локализации

1017

for name, loc in sample_locales: locale.setlocale(locale.LC_ALL, loc) print('{:>10}: {:>10} {:>10}'.format( name, locale.currency(1234.56), locale.currency(-1234.56), ))

Результаты приведены ниже. $ python3 locale_currency.py USA: France: Spain: Portugal: Poland:

$1234.56 -$1234.56 1234,56 Eu 1234,56 Eu1234,56 Eu -1234,56 Eu 1234.56 Eu -1234.56 Eu łz 1234,56 ł z 1234,56-

15.2.3. Форматирование чисел Числа, не связанные с денежными значениями, также могут форматироваться по-разному в зависимости от локали. В частности, это относится к символу grouping, разделяющему группы разрядов в больших числах. Листинг 15.13. locale_grouping.py import locale sample_locales = [ ('USA', 'en_US'), ('France', 'fr_FR'), ('Spain', 'es_ES'), ('Portugal', 'pt_PT'), ('Poland', 'pl_PL'), ] print('{:>10} {:>10} {:>15}'.format( 'Locale', 'Integer', 'Float') ) for name, loc in sample_locales: locale.setlocale(locale.LC_ALL, loc) print('{:>10}'.format(name), end=' ') print(locale.format('%10d', 123456, grouping=True), end=' ') print(locale.format('%15.2f', 123456.78, grouping=True))

Для форматирования чисел без символа валюты следует использовать не метод currency(), а метод format(). $ python3 locale_grouping.py Locale USA

Integer 123,456

Float 123,456.78

Глава 15. Интернационализация и локализация приложений

1018 France Spain Portugal Poland

123456 123456 123456 123 456

123456,78 123456,78 123456,78 123 456,78

Чтобы преобразовать числа, отформатированные в соответствии с определенной локалью, в нормализованные числа, формат которых не связан с локалью, следует использовать функцию delocalize(). Листинг 15.14. locale_delocalize.py import locale sample_locales = [ ('USA', 'en_US'), ('France', 'fr_FR'), ('Spain', 'es_ES'), ('Portugal', 'pt_PT'), ('Poland', 'pl_PL'), ] for name, loc in sample_locales: locale.setlocale(locale.LC_ALL, loc) localized = locale.format('%0.2f', 123456.78, grouping=True) delocalized = locale.delocalize(localized) print('{:>10}: {:>10} {:>10}'.format( name, localized, delocalized, ))

Разделители групп разрядов удаляются, а разделитель целой и десятичной части чисел преобразуется в символ точки (.). $ python3 locale_delocalize.py USA: 123,456.78 France: 123456,78 Spain: 123456,78 Portugal: 123456,78 Poland: 123 456,78

123456.78 123456.78 123456.78 123456.78 123456.78

15.2.4. Анализ чисел Кроме генерации вывода в различных форматах модуль locale может оказаться полезным для синтаксического анализа вводимых чисел. Он включает функции atoi() и atof(), преобразующие строки в целочисленные значения и значения с плавающей точкой на основании соглашений о форматировании чисел, принятых для заданной локали.

15.2. locale: API локализации

1019

Листинг 15.15. locale_atof.py import locale sample_data = [ ('USA', 'en_US', '1,234.56'), ('France', 'fr_FR', '1234,56'), ('Spain', 'es_ES', '1234,56'), ('Portugal', 'pt_PT', '1234.56'), ('Poland', 'pl_PL', '1 234,56'), ] for name, loc, a in sample_data: locale.setlocale(locale.LC_ALL, loc) print('{:>10}: {:>9} => {:f}'.format( name, a, locale.atof(a), ))

Парсер распознает символы разделителя групп разрядов и десятичной точки текущей локали. $ python3 locale_atof.py USA: 1,234.56 => France: 1234,56 => Spain: 1234,56 => Portugal: 1234.56 => Poland: 1 234,56 =>

1234.560000 1234.560000 1234.560000 1234.560000 1234.560000

15.2.5. Дата и время Другим важным аспектом локализации является форматирование значений даты и времени. Листинг 15.16. locale_date.py import locale import time sample_locales = [ ('USA', 'en_US'), ('France', 'fr_FR'), ('Spain', 'es_ES'), ('Portugal', 'pt_PT'), ('Poland', 'pl_PL'), ] for name, loc in sample_locales: locale.setlocale(locale.LC_ALL, loc) format = locale.nl_langinfo(locale.D_T_FMT) print('{:>10}: {}'.format(name, time.strftime(format)))

Глава 15. Интернационализация и локализация приложений

1020

В этом примере для вывода значений даты и времени используется строка форматирования, соответствующая текущей локали. $ python3 locale_date.py USA: France: Spain: Portugal: Poland:

Fri Aug 5 Ven 5 aoû vie 5 ago Sex 5 Ago ptk 5 sie

17:33:31 17:33:31 17:33:31 17:33:31 17:33:31

2016 2016 2016 2016 2016

Дополнительные ссылки ƒƒ Раздел документации стандартной библиотеки, посвященный модулю locale7. ƒƒ Замечания относительно портирования программ из Python 2 в Python 3, касающиеся модуля locale (раздел А.6.24).

ƒƒ gettext (раздел 15.1). Каталоги сообщений с вариантами перевода на другие языки.

7 

https://docs.python.org/3.5/library/locale.html

Глава 16 Инструменты разработки За время существования Pytl1011 постепенно сформировалась обширная экоси­ стема модулей, которая облегчает жизнь разработчикам, избавляя их от необхо­ димости создавать все с нуля. Та же самая философия применяется и к инструмен­ там, лежащим в основе деятельности разработчика, даже если они не используют­ ся в окончательной версии программы. В этой главе обсуждаются модули Pytlюn, упрощающие решение таких повседневных задач разработки, как тестирование, отладка и профилирование приложений. Простейшая форма оказания помощи разработ•1ику- предоставление ему до­ кументации используемого кода. Модуль pydoc (раздел 16.1) генерирует формати­ рованную справочную информацию из строк документирования, которыми дол­ жен снабжаться исходный код любого важного модуля. Pytlю n включает два фреймворка автоматизированного тестирования, обеспе­ чивающих автоматическую проверку работы кода и контроль корректности его поведения. Модуль doc test (раздел 16.2) извлекает тестовые сценарии из вклю­ ченных в документацию примеров, которые могут либо встраиваться в код, либо предоставляться в виде независимых файлов. Модуль uni t t e s t (раздел 16.3) это полноценный фреймворк автоматизированного тестирования с поддержкой фикстур (fixtures), предопределенных наборов тестов и обнаружения тестов. Модуль t race (раздел 16.4) обеспечивает мониторинг выполнения программ на языке Pytl1011 , создавая отчет, в котором отображаются данные о том, сколько раз выполнялась каждая строка кода. Эту информацию можно использовать для поиска путей выполнения, не охваченных набором автоматизированных тестов, и исследования 1·рафа вызовов функций с целью определения зависимостей меж­ ду модулями. Написание и выполнение тестов позволяет обнаруживать проблемы в боль­ шинстве программ. Pythoн упрощает процесс отладки, поскольку необработанные ошибки обычно выводятся на консоль в виде трассировочной информации. В тех случаях, когда программа выполняется не в среде текстовой консоли, для под­ готовки аналогичного вывода в файл журнала или диалоговое окно сообщений можно использовать модуль t raceback (раздел 16.5). Если стандартных трассиро­ вочных данных оказывается недостаточно, то для получения такой более подроб­ ной информации, как значения локальных переменных на каждом уровне стека, и просмотра контекста исходного кода следует использовать модуль cgi tb (раз­ дел 16.6). Для вывода сообщений об ошибках в веб-приложениях модуль cgi tb обеспечивает форматирование трассировочной информации в формате HTML. Как только локализован участок кода, в котором возникла проблема, даль­ нейший способ ее разрешения заключается в пошаговом выполнении кода с по­ мощью интерактивного отладчика, предлагаемого модулем pdb (раздел 16.7), ко­ торый позволяет быстро исследовать маршрут выполнения кода, приводящий к возникновению проблемной ситуации. Кроме того, модуль рdЬ облегчает прове-

ГАВва 16. Инcrpyмetmi1 реэрабсmое

1 022

дение экспериментов с использованием активных объектов, что позволяет значи­ тельно уменьшить количество итераций, необходимых для внесения в код соот­ ветствующих изменений и устранения ошибки. Следующим шагом после того, как программа отлажена и работает корректно, является улучшение ее производительности. Используя модули pro file (раз­ дел 16.8) и t ime i t (раздел 1 6.9), разработчик может измерить скорость выпол­ нения программы и выявить те ее части, которые работают медленнее всего и нуждаются в улучшении. В языках, подобных Python, в которых пробелы являются элементом синтак­ сиса, важно строго придерживаться правил создания отступов в коде. Модуль tabnanny (раздел 16. 10) предоставляет средство, позволяющее обнаруживать слу­ чаи некорректного использования отступов. Это средство можно применять в те­ стах для того, чтобы гарантировать соответствие кода минимальным стандартам до его проверки в репозитории исходного кода. Программы на языке Руtlюн выполняются посредством их предоставления ин· терпретатору в виде байт-компилированной версии исходного кода программы. Байт-компилированные версии могут создаваться либо на лету, либо однократно во время помещения программы в пакет. Модуль comp i l e a l l (раздел 1 6.1 1 } пре­ доставляет интерфейс, используемый программами-установщиками и менеджера­ ми пакетов для создания файлов, содержащих байт-код модуля. Его также можно использовать в среде разработки для того, чтобы гарантировать отсутствие син­ таксических ошибок и создать байт-компилированные файлы для включения в пакеты при выпуске программ. Что касается уровня исходного кода, то модуль pyclbr (раздел 16. 1 2) предо­ ставляет обозреватель классов, который может использоваться текстовым редак­ тором или иной программой для просмотра исходного кода программы на языке Python с целью исследования таких элементов, как функции или классы. Эти дей­ ствия осуществляются без импортирования кода и благодаря этому не сопрово­ ждаются отрицательными побочными эффектами. Виртуальные окружения Pythoп, управляемые модулем venv (раздел 16. 13), определяют изолированные среды, предназначенные для установки и выполне­ ния программ. Они упрощают тестирование одной и той же программы с раалич­ ными версиями зависимостей и установку различных программ на одном компью­ тере без риска возникновения конфликтов между ними. Чтобы воспользоваться всеми преимуществами обширной экосистемы моду­ лей, фреймворков и утилит, доступных через каталог пакетов PPI ( Pythoп Package Index), необходимо использовать установщик пакетов. Программа установки па· кетов Руtlюн, pip, не распространяется вместе с интерпретатором ввиду того, что новые версии языка выпускаются значительно реже по сравнению с жела­ тельной частотой обновления данного инструмента. Для установки последней версии программы pip можно использовать модуль ensurepip (раздел 1 6. 14 ) .

16.1.

pydoc:

оперативная справка для модуле й

Модуль pydoc импортирует модуль Руtlюн и использует его содержимое для ге­ нерации текста справки во время выполнения. Выходная информация включает строки документирования всех элементов, входящих в состав модуля, таких как классы, методы и функции.

16.1. pydoc: оnер8'1М8Н8Я сnраВIСВ /Jlifl МодуАей

1023

1 6.1.1 . Получение справки в формате простого текста Если вызвать pydoc как утилиту командной строки, задав имя модуля Pytlюn в качестве аргумента, то она выведет на консоль справку к указанному модулю и его содержимое, используя терминальный пейджер, если таковой сконфигурирован. Нанример, чтобы получить текст справки 110 модулю atexit (раздел 14.10), следу­ ет вы1юлнить команду pydoc atexi t . $ pydoc atexit Help on bu i l t - i n modu l e atex i t : NАМЕ a t e x i t - a l l ow programme r t o def i n e mult i ple e x i t func t i ons t o Ье executed upon norma l prog ram t e rminat i on . DESCRI PT I ON Two puЫ i c funct i ons , regi s t e r and unreg i s t e r , are de f i ned . FUNCT IONS reg i s t e r ( . . . ) regi s t e r ( func , * a rgs , * * kwa rgs ) -> func Re g i s t e r а funct i on t o Ье executed upon norma l prog ram t e rmi n a t i on func - funct i o n to Ье ca l l ed at e x i t args - opti onal a rguments to pa s s t o func kwa rgs - opt i onal keyword a rgument s t o pa s s t o func func is returned to faci l i t a t e usage as а decora t o r . unreg i s t e r ( . . . ) unreg i s t e r ( func )

-> None

Unreg i s t e r a n exit funct i o n whi ch wa s prev i ou s l y reg i s t e red u s i ng a t e x i t . re g i s t e r func - func t i o n t o Ье unreg i s t e red F I LE ( bu i l t - i n )

1 6 . 1 .2. Справка в формате HTML Утилита pydoc также способна генерировать справочную информацию в фор­ мате HTML, записывая ее в статический файл, расположенный в локальном ката­ логе, или запуская веб-сервер, позволяющий просматривать текст справки в он­ лайн-режиме. $

pydoc -w a t e x i t

1024

Предыдущая команда создает файл atexit.lttml в текущем каталоге, тогда как следующая команда запускает веб-сервер, прослушивающий запросы по адресу http://localhost:5000/. $ pydoc -р 5 0 0 0 S e rver ready at http : / / l oca lhost:5 0 0 0 / S e rver commands : [ b ) rows e r , [ q ) u i t s e rver> q S e rver s topped

Сервер генерирует информацию на лету в процессе ее просмотра. Команда Ь автоматически открывает окно браузера, а команда q останавливает сервер.

16.1.3. Интерактивная справка Модуль pydoc добавляет в список _builtins_ функцию help (),поэтому та же информация доступна в интерактивной оболочке интерпретатора Руtlюп. $ python Python 3 . 5 . 2 ( v 3 . 5 . 2 : 4de f 2 a 2 9 01a 5 , Jun 2 6 2 0 1 6 , 1 0:4 7 : 2 5 ) [ GCC 4 . 2 . 1 ( Apple I n c . bu i l d 5 6 6 6 ) ( dot 3 ) ) on darwin Т уре " help " , " copyri ght " , " cred i t s " o r " l i cense " for more i n forma t ion . > > > help ( ' a t ex i t ' ) He lp on modu l e a t e x i t : NАМЕ a t ex i t - a l l ow prog rammer to de fine mu l t i p l e e x it func t i ons t o Ье executed upon norma l prog ram t e rmina t i on .

Дополнительные ссылки •

Раздел документации стандартной библиотеки, посвященный модулю pydo c 1•



i n spect (раздел 1 8.4). Модуль i n spect может использоваться для программного из­ влечения строк документирования объектов.

1 6.2.

doctest:

тестирование доку ментации

Модуль doctest тестирует исходный код путем выполнения примеров, встро­ енных в документацию, и проверки того, что они воспроизводят ожидаемые ре­ зультаты. Данный модуль просматривает строки документирования с целью по­ иска примеров, выполняет примеры и сравнивает результирующий текст с ожи­ даемым значением. Многим разработчикам проще работать с модулем doctest, чем с модулем unittest (раздел 16.3), поскольку простейшие случаи его исполь­ зования не требуют изучения API. Однако по мере усложнениях примеров отсут1

https : / /docs . python . o rg / 3 . 5 / l i brary/pydoc . html

1025

16.2. doc:test тecmtp088HM8 АQКУМ8К'11ЦММ

ствие средств управления фикстурами (конфигурациями тестирования) в модуле doctest приводит к более 1·ромоздким тестам по сравнению с тестами, подготов­ ленными с помощью модуля unittest.

1 6. 2. 1 . Начало работы Прежде чем приступить к работе с модулем doctests, необходимо создать те­ стовые примеры с помощью интерактивного интерпретатора и включить их в строки документирования модуля путем копирования и вставки. В приведенном ниже листинге предоставляются два примера для функции my function () . _

Листинг 16.1. clocte s t simple. ру _

def my_func t i on ( a , Ь ) : """

> > > my_funct i on ( 2 , 3 )

6

> > > my_func t i on ( ' a ' , 3 )

' ааа '

"""

return а * Ь

Чтобы выполнить тесты, следует использовать модуль doctest в качестве ос­ новной программы, указав параметр -m при его запуске. Как правило, во время выполнения тестов не выводится никакая выходная информация, поэтому ниже испольаустся параметр -v, обеспечивающий получение более информативного вывода. $ python3 -m doctest

-v

doct e s t_ s impl e . py

Trying : my_funct ion ( 2 , 3 ) Expec t ing : 6

ok T r ying : my_funct i on ( ' а' , 3 ) Expec t ing :

' ааа '

ok 1 i t ems h a d no t e s t s : doct e s t_s imp l e 1 i t ems p a s s ed a l l t e s t s : 2 t es t s in doct e s t_simp l e . my_funct i o n 2 t e s t s i n 2 i t ems . 2 pa s s ed and О fa i l ed . T e s t passed .

Как правило, сами по себе примеры не могут объяснить работу функций, по­ этому модуль doctest обеспечивает использование дополнительных текстовых описаний. Он ищет строки, начинающиеся с подсказки интерпретатора (>>>), которые указывают на начало набора тестовых данных, а признаком конца те-

1028

стового набора служит пустая строка или следующая подсказка интерпретатора. Перемежающийся с тестовыми данными текст игнорируется и может выводиться в любом формате при условии, что он отличается от формата тестовых данных. Листинг 1 6.2. doctes t simple wi th docs . ру _

_

_

def my_funct i on ( a , Ь ) : " " " Возвраща ет а * Ь . Работает с чи слами : > >> my_funct i on ( 2 , 3 )

6

и строками : >>> my_ funct i on ( ' a ' , 3 ) ' а аа ' """ return а * Ь

Внедрение дополнительного текста в строку документирования делает ее бо­ лее информативной для читателя. Поскольку модуль doctest игнорирует этот текст, результаты остаются прежними. $ python3 -m doct e s t

-v

doct e s t_s imp l e_w i t h_docs . py

T r y i ng : my_funct i on ( 2 , 3 ) Expect i n g :

6

ok Trying : my_funct i on ( ' a ' , 3 ) Expect ing : ' ааа ' ok 1 i t ems h a d no t e s t s : doct e s t s impl e-wit h docs 1 i t ems pass ed a l l t e s t s : 2 t e s t s in doc t e s t_s imp l e_wi t h_doc s . my_funct i on 2 t e s t s i n 2 it ems. 2 passed and О fa iled . T e s t passed .

1 6. 2.2. Обработка непредсказуемого вывода Иногда точный вывод невозможно предсказать, но он все равно должен оста­ ваться тестируемым. Например, значения текущей даты и времени или иденти­ фикатор объекта могут меняться от одного прогона теста к другому, используемая по умолчанию точность представления чисел с плавающей точкой зависит от параметров компилятора, а строковое представление контейнерных объектов,

1 027

таких как словари, может быть недетерминированным. Несмотря на то что эти условия невозможно контролировать, существуют подходы, позволяющие справ­ ляться с подобными ситуациями. Например, в CPythoп идентификаторы объектов основаны на адресах струк­ тур данных, хранящих объекты. Листинr 1 6.3. doctes t_unpredictaЬle . py

c l a s s MyC l a s s : pass

def unpredi ctaЬl e ( obj ) : " " " Возвращае т новый список, содержащий объект . > > > unpredi ctaЬle (MyC l a s s ( ) ) [ ] 1 1 " "

return [ obj ]

Значения идентификаторов меняются при каждом запуске программы, по­ скольку она загружается в разные области памяти. $ python З -m doct e s t

-v

doc t e s t_unpred i ctaЫe . py

T r ying : unpred i ctaЬle ( MyC la s s ( ) ) Expect ing : [ J ****************************************************************** F i l e " . . . / do c t e s t _unpredi ctaЫ e . py" , l i ne 1 7 , i n doctes t_unpred i c t aЫe . unpred i c t a Ы e Fa i l e d examp l e : unpredi ctaЬle ( MyCl a s s ( ) ) Expe c t ed : [ < doc t e s t_unpredi ctaЫ e . MyC l a s s obj ect at Ox l 0 0 5 5a 2 d0 > ] Got : [ < doc t e s t_unpredi ctaЫ e . MyC l a s s obj ect at О х 1 0 1 6а 4 1 6 0 > ] 2 i t ems h a d n o t e s t s : doc t e s t_unpred i ct a Ы e do ctes t_unpredi ctaЫe . MyCl a s s ****************************************************************** 1 i t ems had f a i l ures : 1 of 1 in doc t e s t_unpre d i c t aЫ e . unpred i c t a Ы e 1 t e s t s in З i t ems . О pa s s ed and 1 fai l ed . * * * T e s t Fa i l ed * * * 1 fa i l ures .

Если тесты включают значения, которые могут изменяться непредвиденным образом, и если фактические значения несущественны для результатов теста, то следует использовать опцию ELL I P S I S , указывающую на то, что при верифика­ ции значения некоторые его части можно игнорировать.

1 028 Листинг 16.4. doctes t_ellip s i s . ру c l a s s MyCl a s s : pass def unpredictaЬl e ( obj ) : " " " В о звращает новый список, содержащий объект . > > > unpred i c t aЬ l e ( MyC l a s s ( ) ) #docte s t : +ELLI PS I S [ ) "" return [ obj ) "

Наличие комментария # doct est : + E LL I P S I S после вызова функции unpredictaЫ e ( ) указывает модулю doctest на то, что для данного теста вклю­ чена опция ELLI P S I S . Часть идентификатора, соответствующая адресу объекта в памяти, заменяется символом м1ю1·оточия ( . . . ), так что эта часть ожидаемого значения игнорируется. Фактический вывод совпадает с ожидаемым, и тест счи­ тается успешно пройденным. $ python З -m doct est

-

v doct e s t e l l i p s i s . py _

T rying : unpred i c t aЫ e ( MyC l a s s ( ) ) # doct e s t : +ELL I PS I S Expe c t i n g : [ < doc t e s t _e l l ips i s . MyC l a s s obj ect a t Ох >) ok 2 i t ems had n o t e st s : doct e s t_el l i ps i s doct e s t_e l l ips i s . MyC l a s s 1 i t ems passed a l l t e s t s : 1 t e s t s i n doct e s t _e l l i p s i s . unpredi c t aЫ e 1 t e s t s i n З i t ems . 1 passed and О fa i l ed . T e s t pas sed . •

.



Однако иногда непредсказуемое значение нельзя игнорировать,поскольку это сделает тест неполным или неточным. Например, простые тесты быстро услож­ няюТ > > douЫ e_space ( [ ' L ine one . ' , L i n e one .

L i n e two .

' L i n e two . ' J )

fl l l ll

f o r 1 in l i ne s : p r i nt ( l ) p r i nt ( )

В этом примере модуль doctest заменяет фактические пустые строки тем же литералом, прежде чем выполнить сравнение. Теперь фактические и ожидаемые значения совпадают, и тест проходит. $

pythonЗ -m d o c t e s t

-

v doc t e s t_Ы an k l i n e . py

T r y i ng : douЫ e_space ( [ ' L i n e one . ' , ' L i n e t wo . ' J ) Expect i ng : L i ne one . < BLANKLINE > L i n e two .

ok 1 i t ems h a d no t e s t s : do c t e s t Ы a n k l ine 1 i t ems pa s sed all t e s t s : 1 t e s t s in do c t e s t_Ы a n kl i n e . douЫ e_s p a c e 1 t e s t s i n 2 i t ems . 1 p a s s ed and О f a i l e d . T e s t pa s s ed .

Пробелы в пределах. строки также могут порождать проблемы при тестирова­ нии. В следующем примере за цифрой 6 следует лишний пробел. Листинг 1 6. 1 1 . doctest_extra_space . py d e f my_func t i on ( a , tl l l ,1

Ь) :

>>> my_func t i on ( 2 ,

3)

6 > > > my_fu nct i on ( ' a ' ,

' аа а '

"""

return а * Ь

3)

Г118ва

1034

16. ИнсrрумеН1Ы раэра6о1км

Лишние пробелы могут появляться в коде вследствие редактирования текста методом копирования и вставки. Если такие пробелы оказываются в конце стро­ ки, то они могут остаться незамеченными в исходном файле и будут такими же невидимыми в отчете о непрохождении теста. $ python3 -m doctest

doct e s t_ext ra_space . py

-v

T rying : my_funct ion ( 2 , 3 ) Expe c t i ng :

6 ********** ********************************************************

Fi l e " . . . / doct e s t_ext ra_spa c e . py " , l i n е 1 5 , in doctes t_ext ra_spa ce . my_funct i on Fa i l ed example : my_funct ion ( 2 , 3 ) Expe cted : 6

Got :

6

T rying : my_func t i on ( ' а ' , 3 ) Expec t i ng : ' ааа '

ok 1 i t ems had n o t e st s :

doc t e s t_ext ra_space ****************************************************************** 1 i t ems had fa i l u re s : 1 of 2 in doct e s t_extra_spa +ce . my_funct i on

2 t e s t s in 2 i t ems . 1 passed and 1 f a i l ed . * * * T e s t Fa i l ed * * * 1 fa i l ure s .

Использование одной из таких опций вывода отчета, как RE PORT_NDI FF, по­ зволяет отображ ать различия между фактическими и ожидаемыми значениями с большей степенью детализации, при этом лишние пробелы становятся видимы­ ми. Листинг 1 6 . 1 2 . doctes t_ndiff . ру de f my_func t i o n ( a , Ь ) : " " "

> > > my_funct i on ( 2 , 6

3 ) #doctest : +REPORT N D I FF

> > > my_func t i on ( ' a ' , ' ааа '

3)

ll fl lf

return а * Ь

Также доступны опции вывода унифицированных (RE PORT_UDI FF) и контекст­ ных (REPORT_CDI FF) различий.

1 035 $ python3 -m doct e s t

-v

doct es t_ndi f f . py

Tryi ng : my_func t i on ( 2 , 3 ) idoct e s t : +REPORT N D I F F Expect ing : 6 * * * * * * * * * * * * * * * * * * * * ** * * * * * * * * * * * * * * * * * * * * * * * * * * ** * * * * * * * * * * * * * * * * \ doc t e s t ndi ff . py " , l i ne 1 6 , i n doct e s t ndi f f . my func t i on Fi l e Fa i l ed ex ampl e : my_func t i on ( 2 , 3 ) i doct e s t : +REPORT_N D I FF D i f ferences ( ndi f f with -exp e c t ed +a c t u a l ) : 6 " .

.



-

-

-

?

+ 6 T r y i ng : my_func t i on ( ' a ' , 3 ) Expect ing : ' аа а ' ok 1 i t ems h a d no t e s t s : doct e s t ndi f f ********************************************************** ******** 1 i t ems had f a i lures : 1 of 2 i n doct e s t_ndi f f . my_funct i on 2 t e s t s in 2 i t ems . 1 p a s s ed and 1 fa i l ed . * * * T e s t Fa i l ed* * * 1 fai lure s .

Иногда желательно добавить дополнительные пробелы в образце вывода для теста, но таким образом, чтобы модуль doctest игнорировал их. Например, чи­ тать структуры данных проще, если они распределены по нескольким строкам, даже если их представление вполне могло бы умес1·иться в одной строке. def my_funct i on ( a , Ь ) : " " " Возвраща е т а * Ь . > > > my_func t i on ( [ ' A ' ,

[ 'А' , 'А' , 'А' ,

' В ' ) , 3 ) idoctes t : +NORМAL I Z E WHITES PACE

'В' , 'В' , ' В' )

Э тот в ариан т не подходит из-за доnолнитель ного пробела nосле [ в сnиске . >>> my_funct i on ( [ ' A ' , ' В ' ] , 2 ) i d o c t e s t : +NORМAL I Z E WHIT E S PACE [ 'А' , ' В' , 'А' , 'В' , ) n r1 lf

return а * Ь

При включенной опции NORМALI ZE WHI T ES PAC E любой пробел в фактическом и ожидаемом значениях считается совпадением. Пробел не может добавляться в ожидаемое значение там, где он отсутствует в выводе, но длина последовательно­ стей пробелов в ожидаемом и фактическом значениях не обязана быть одинако_

Гмва 16. Инаrруменn.1 pupa6oncи

1 036

вой. Первый тестовый пример соответствует этому требованию, и его тест про­ ходит, да ж е если входные данные содержат дополнительные пробелы и си мволы новой строки. Второй пример включает по дополнительному пробе лу по сле сим­ вола [ и перед символом ] и поэтому тест не проходи т. ,

$ pytho n 3 -m doctest

-v

doc t e s t_norma l i z e_whi t e space . py

Trying : my_func t i on ( [ ' A ' , ' В ' ] , 3 ) # do c t e s t : +NORМAL I Z E WHI TES PACE Expect i ng : [ 'А' , ' В ' , 'А' , ' В ' , 'А' , ' В' , ] *************************** * * ************************************* F i l e " doctes t_no rma l i ze_whi t espace . py " , l i ne 1 3 , i n doct e s t norma l i z e_whi t e space . my_func t i on Fa i l ed examp l e : my_func t i on ( [ ' A ' , ' В ' ] , 3 ) #doct e s t : +NORМAL I ZE WHI T E S PACE Expected : [ 'А' , ' В ' , 1А1 , ' В ' , 'А' , ' В ' ] Got : [ 'А' , ' В ' , 'А' , ' В ' , 'А' , ' В ' ] T rying : my_func t ion ( [ ' A ' , ' В ' ] , 2 ) #doctes t : +NORМAL I Z E WH I T E S PACE Exp e c t i ng : [ 'А' , 'В' , 'А' , 'В' , * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ** * * * * * * * * * * * * * * * * * * * Fi l e " do c t e s t_normal i z e_whi t e space . py " , l i ne 2 1 , i n do c t e s t norma l i z e_whi t e space . my_fun c t i on Fa i led examp l e : my_funct i on ( [ ' A ' , ' В ' ] , 2 ) #doct e s t : +NORМAL I Z E WH I T E S PACE Expected : [ 'А' , 'В' , 'А' , ' В' , Got : [ 'А' , ' В ' , ' А' , ' В ' ] 1 i t ems had no t e s t s : doct e s t_no rma l i z e_wh i t espace * * * * * ** * * * * * ** * ******** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** 1 i t ems had fa i l u res : 2 of 2 in doctes t_norma l i z e_wh i t e space . my_funct i on 2 t e s t s in 2 i t ems . О pas sed and 2 fai led . * * *Test Fa i l ed* * * 2 fai lures .

16.2.5. Местонахождение тестов До сих пор во всех примерах тесты записывали сь в строка х документирования к которым они относятся. Такой подход удобен дл я пользов ателей, изункций, ф

16.2. doctelt: ТIС1Мро88НМ8 АQКУМ8НТ8ЦММ

1037

влекающих справочную информацию из строк документирования с помощью той или иной служебной функции, например pydoc (раздел 1 6. 1 ) , но модуль d oc test позволяет искать тесты и в других расположениях. Наиболее очевидными распо­ ложениями для поиска дополнительных тестов являются строки документирова­ ния , включенные в модуль на любом уровне. Листинг 1 6. 1 3. doctest docstrings . ру " " " Тес ты могут появлять с я в любой строке документирования в модуле . Тесты уровня модул я пересекают границы классов и функций . »> А ( ' а ' ) Fa l s e

==

В ( 'Ь' )

11 1 1 11

class А: " " " Простой кла с с . > > > A ( ' i n s t ance_name ' ) . name

' i ns t a n ce name '

11 11 '8

def

i ni t ( s e l f , name ) : s e l f . name name =

d e f me thod ( s e l f ) : " " " Воз вращает необычное значени е . »> А ( ' name ' ) . method ( )

' eman '

1 1 11 14

return ' ' . j oi n ( reversed ( s e l f . name ) )

class В (А) : " " " Еще один простой кла с с . > > > B ( ' di f f e rent_name ' ) . name

' di f fe rent name ' """

Тесты могут включаться в строки документирования на уровне модуля , класса или функции. $ pythonЗ -m doctest -v d o c t e s t_docst r i ngs . py T rying : А( 'а' ) Expec t i ng : Fa l s e ok

В( 'Ь' )

Гмва 16. ИНСfРУМ8Н1Ы раре6сmси

1038 Trying : A ( ' i n s t ance_name ' ) . name Expe ct i ng : ' i n s t ance name ' ok Trying : А ( ' name ' ) . method ( ) Expe c t i n g : ' ema n ' ok T ryi ng : B ( ' d i f fe rent_name ' ) . name Expe ct i n g : ' d i f ferent name ' ok 1 i t ems h a d no t e s t s : doct e s t_docst r i ngs . A . _i n i t� 4 it ems pas s e d a l l t e s t s : 1 t e s t s in doctes t_do c s t rings 1 tests i n doct e s t_docs t rings . A 1 t e s t s i n doct es t_do c s t rings . A . me thod 1 t e s t s in doct es t_do c s t rings . B 4 tes t s in 5 i t ems . 4 passed and О fa i l ed . Te s t p a s s e d .

Иногда тесты должны включаться вместе с исходным кодом, но не в случае текста справки для модуля. В этом случае их следует располагать вне строк доку­ ментирования. Одним из способов, используемых модулем doctes t для опреде­ ления местонахождения других тестов, является поиск переменной _test_ на уровне модуля . Эначением _tе s t_ должен быть словарь, сопоставляющий име­ на (заданные в виде строк) со строками, модулями, классами или функциями. Листинr 1 6. 1 4. doctes t_yrivate_ tests . py impo r t doct e s t_private_t e s t s_external test = ( ' numЬers ' : " " " > > > my_func t i on ( 2 , 3 ) 6 > > > my_fun c t i on ( 2 . 0 , 3 ) 6.0 """' ' s t r ings ' : '' " " > > > my_func t i on ( ' a ' , 3 ) ' а аа ' > > > my_fun ct i on ( 3 , • а аа ' """'

'a'J

16.2. doctest: 1еС1Ированме докумеНТВцин

1 030

' ext ernal ' : doctes t_p r i va t e_t e s t s_externa l ,

def my_func t i on ( a , Ь ) : " " " Возвращает а * Ь " " 11

return а * Ь

Если значением, ассоциированным с ключом, является строка, то она интер­ претируется как строка документирования и проверяется на наличие тестов. Если значением является класс или функция, то модуль doctes t выполняет рекур­ сивный поиск строк документирования, пытаясь найти в них тесты. В следующем примере строка документирования модуля doctes t_private_tes ts_ext ernal содержит один тест. Листинг 1 6. 1 5. doc tes t_;:>rivate_tes ts_external . ру

" " " Внешние те сты, а с с оциированные с doctest_p r i v a t e_t e s t s . py . > >>

my_funct i on ( [ ' A ' , ' В ' , ' С ' ] , 2 ) [ 'А' , ' В ' , 'С' , 'А' , ' В ' , ' С ' ]

l l ll JI

Просмотрев файл примера, модуль doctes t находит в общей сложности пять тестов, подлежащих выполнению. $ python3 -m doct e s t -v doctest_p r i v a t e_t e s t s . py Trying : my_funct i on ( [ ' A ' , ' В ' , ' С ' ] , 2 ) Expect i n g : [ 'А' , ' В ' , 'С' , 'А' , ' В ' , ' С ' ] ok Trying : my_funct i on ( 2 , 3 ) Expect i n g : 6 ok T rying : my_funct i on ( 2 . 0 , 3 ) Expect i n g : 6.0 ok Trying : my_funct i on ( ' a ' , 3 ) Expe ct i ng : ' ааа ' ok Trying : my_funct i on ( 3 , ' а ' ) Expec t i ng : ' ааа ' ok

1 040

ГАава 16. Инсrруменn.� рара6сmси

2 i t ems had n o t e s t s : doctest_p r i va t e_t e s t s doctest_p r i vat e_t est s . my_funct i on 3 i t ems passed a l l t e s t s : 1 t e s t s in doct e s t_p r i vate_t e s t s . �t e s t� . external 2 t e s t s i n doct e s t_priva t e_t e st s . �t est� . numЬe rs 2 tests i n docte s t _priva t e_t e st s . �t e s t� . s t r ings 5 tests in 5 i t ems . 5 pa s s ed and О fa i l ed . T est pas sed .

1 6.2.б. Внешняя документация Смешивание тестов с обычным кодом - не единственный способ исполь· зования модуля d octest. Точно так же можно использовать примеры. встро· енные в файлы документации внешних проектов, такие как файлы ReST (reStrusturedText). Листинг 1 6 . 1 6 . docte s t_in_help . py

def my_funct ion ( a , Ь ) : " " " Возвращает а * Ь """ return а * Ь

Справочная информация для этог о пробного модуля сохранена в отдельном файле dortest_in_help. txt. Примеры использования модуля включены в справочный текст, и для их нахождения и выполнения можно использовать модуль d oc tes t. Листинг 1 6. 1 7. doc te s t_in_help . txt

How t o Use doc t e s t_in_help . py

Thi s l ibrary i s ve ry s imp l e , s i nce it on l y has one funct ion ca l l ed ' ' my funct ion ( ) ' ' . _ NumЬe rs

' ' ту- funct i o n ( ) ' ' return s the p r odu ct o f i t s a r gument s . that value i s equ i v a l ent t o us i ng the ' ' * ' ' operat o r .

> > > from doct e s t_ i n_he l p import my_funct i o n > > > my_funct ion ( 2 , 3 ) 6 I t a l s o works with f l oa t i ng-po i n t values .

> > > my_func t i o n ( 2 . 0 , 3 )

Fo r numЬe r s ,

16.2. doctest: тесntр088НМ8 АОКJМ8НТВЦМ М

1 041

6.0 Non-NumЬe rs Because ' ' * ' ' i s also defined оп da t a t ypes other than numЬer s , ' ' my_funct i on ( ) ' ' wo rks j u s t as wel l i f one o f t h e a rgwne n t s i s а s t r i ng , а l i s t , or а tupl e .

> > > my_funct i on ( ' a ' , ' а аа '

3)

> > > my_func t i on ( [ ' А ' , ' В ' , ' С ' ] , 2 ) [ ' А' , ' В ' , ' С ' , ' А' , ' В ' , ' С ' ]

Тесты, включенные в текстовый файл, можно запускать из командной строки точно так же, как и модули Pythoв. $ python3 -m doctest

-v

doctes t_i n_he l p . txt

Trying : f rom doctest_in_help import my_funct i on Expec t ing nothing ok T rying : my_funct i on ( 2 , 3 ) Expect ing : 6 ok T rying : my_func t i on ( 2 . 0 , 3 ) Expe ct ing : 6.0 ok T r y i ng : my_fun c t i on ( ' a ' , 3 ) Expe ct ing : ' ааа ' ok T rying : my_funct i on ( [ ' A ' , ' В ' , ' С ' ] , 2 ) Expect ing : [ 'А' , ' В ' , ' С ' , 'А' , ' В' , ' С ' ] ok 1 i t ems p a s s e d a l l t e s t s : 5 t e s t s in doctest i n_help . txt _ 5 tests i n 1 i t ems . 5 p a s s ed and О f a i l ed . T e s t pas s e d .

Обычно модуль настраивает тестовую среду выполнения так, чтобы она вклю­ чала элементы тестируемого модуля, поэтому тестам необязательно импортиро-

Гмва 16. Инструменn.1 рааработкм

1042

вать его явно. Однако в данном случае тесты определены пе в модуле Руtlюп , и модуль doctest не знает,как настроить глобальное пространство имен. Как след­ ствие, примеры должны самостоятельно импортировать нужный модуль. Все те­ сты в данном файле разделяют один и тот же контекст, поэто му достаточно им­ портировать модуль в самом начале файла.

1 6.2.7. Выполнение тестов Во всех предыдущих примерах использовалась про грамма запуска тестов из командной строки, встроенная в модуль doctest. Этот подход прост и удобен в случае тестирования одиночного модуля, но с ростом пакета тестируемых файлов его трудоемкость быстро возрастает. Для подобных случаев рааработаны альтер­ нативные подходы,доказавшие на практике свою :-эффективность.

1 6.2. 7 . 1 . Тестирование модулей Инструкции для запуска модуля doctest по отношению к определенному ис­ ходному коду могут помещаться в конце соответствующих модулей. Листинг 1 6. 1 8. doc tes t testmod . ру _

def my_f unct ion ( a , Ь ) : lf rf lf

> > > my_funct i on ( 2 , 3 ) 6 > > > my_fu nct i on ( ' a ' , 3 ) ' а аа ' " " "

return а * Ь if

name == ' ma in import doct e s t doct e s t . t e s tmod ( )

' ·

Вызов функции testmod ( ) лишь в том случае, если именем текущего модуля является ma in гарантирует, что тесты будут выполняться только тогда, когда модуль вызывается как основная программа. _

_,

$ pythonЗ doct est_t e s tmod . py -v Trying : my_funct ion ( 2 , 3 ) Expect ing : 6 ok T r y i ng : my_funct i on ( ' a ' , 3 ) Expect ing : ' аа а ' ok 1 i t ems h a d no t e s t s : ma i n

1043

1 i t ems pas s ed a l l tes t s : 2 t e s t s i n �ma i n� . my funct i on _ 2 t e s t s i n 2 i t ems . 2 pas s ed and О fai l ed . T e s t pas s ed .

Первым аргументом функции t e s tmod ( ) является модуль, содержащий код, который должен быть проверен на наличие тестов. Отдельный сценарий тести­ рования может использовать эту возможность для импорта реального кода и поо­ чередного выполнения тестов, содержащихся в каждом модуле. Листинr 1 6. 1 9. doctest_tes tmod_other_module . ру

import doct est_s imple if

name ' mai n ' · impo rtdoc t e s t doctest . t e s tmod ( do c t e s t_simpl e ) ==

Тестовый набор для проекта можно создать, импортируя каждый модуль и вы­ полняя его тесты. $ python3 doct e st_t es tmod_othe r_modu l e . py

-v

Trying : my_func t i on ( 2 , 3 ) Expe c t i ng : 6 ok Trying : my_funct ion ( ' a ' , 3 ) Expect ing : ' а аа ' ok 1 i t ems had no t e st s : doct e s t_simp l e 1 i t ems pas sed a l l t e s t s : 2 t e s t s in docte st_s imp l e . my_funct i on 2 t e s t s in 2 i t ems . 2 p a s s e d and О fa i l ed . T e s t passed .

1 6.2.7.2. Тестирование файлов Функция t e s t f i l e () работает аналогично функции testmod (), обеспечивая возможность явного вызова тестов, содержащихся во внешнем файле, из про­ граммы тестирования. Листинr 1 6. 20. docte s t_te s tfile . py

import doct e s t if

name ma i n doct e s t . t e s t f i l e ( ' doct es t_i n_he l p . txt ' ) '

·

Гмва 18. ИнсrрумеН1111 реара6откм

1044

Обе функции, te s tmod ( ) и t e s t f i l e ( ) , включают необязательные параме­ тры, rюзволяющие управлять поведением тестов посредством опций модуля doc t e s t . Более подробное описание этих возможностей можно найти в докумен­ тации стандартной библиотеки , однако следует отметить, что в большинстве слу­ чаев необходимости в использовании указанных опций не возникает. $ python3 doct e s t _t e s t f i l e . py

-v

Trying : from doct e s t_in_h e l p import my_funct i on Expec t i n g not h ing ok Trying : my_funct i on ( 2 , 3 ) Expect ing : 6 ok T rying : my_funct ion ( 2 . 0 , 3 ) Expect ing : 6.0 ok Trying : my_fun c t i o n ( ' а ' , 3 ) Expec t i n g : ' аа а ' ok Trying : my_funct i on ( [ ' A ' , ' В ' , ' С ' ] , 2 ) Expect ing : [ ' А' , ' В ' , ' С ' , ' А' , ' В ' , ' С ' ] ok 1 i t ems p a s s e d a l l t e st s : 5 t e s t s i n doct est_in_he lp . txt 5 t e s t s i n 1 i t ems . 5 passed and О f a i l e d . Test passed .

1 6.2 .7.3. Набор тестов unitte s t Если для тестирования одного и того же кода 1' различных си1уациях привле­ каются модули un i t t e s t (раздел 16.3) и do ctest, то для совместного выполнения всех тестов можно использовать интеграцию этих модулей. Для создания набо­ ров тестов, совместимых с API программы запуска тестов модуля u ni t t e s t , мож­ но использовать два класса: DocTe s t Su i te и DocFi leSui te. Листинг 1 6. 2 1 . docte s t_unittest . py

import doctest import uni t t e s t import doctes t_simp l e

16.2. doctest: '8с:111ро118НМ8 АQКУМ8Н18ЦИ М

1045

suite uni t t e s t . Te s t Sui t e ( ) sui t e . addTe s t ( doctest . DocT e s t Sui t e ( doct e s t_simp l e ) ) sui t e . addTe s t ( docte s t . DocFi l e Sui t e ( ' doc t e s t_i n_help . t xt ' ) ) =

runner uni t t e s t . TextTe s t Runne r ( verbos i t y= 2 ) runner . run ( s u i t e ) =

Вместо создания отчетов для отделы1ых тестов результаты тестов, независимо от их источников,сводятся в единый отчет. $ pythonЗ doctes t_uni t t e s t . py my_funct i on ( do c t e s t_simp l e ) Doct e s t : doc t e s t_simp l e . my_funct i o n . . . o k doct e s t_ i n_he lp . txt Doct e s t : doc t e s t_i n_help . txt . . . ok

Ran 2 t e s t s in 0 . 0 1 8 s ок

1 6.2.8. Контекст тестирования Контекст выполнения, создаваемый модулем doctest 1 ю время тестирования , содержит копии глобальных значений уровня модуля. Каждый источник тестов (например, функция, класс, модуль) имеет собственный набор глобальных значе­ ний, что в определенной степени изолирует тесты и снижает вероятность их вли­ яния друг на друга. Листинr 1 6.22. doctest_tes t_globals . ру

c l a s s TestGl oba l s : def one ( s e l f ) : fl ll tl

> > > var ' va l ue ' > > > ' va r ' in gl oba l s ( ) T rue =

"""

def two ( s e l f ) : """

> > > ' va r ' i n g l oba l s ( ) Fa l s e 11 11 11

Класс T e s t Global s обладает двумя методами: one ( ) и two ( ) . Тесты в строке докум ентирования метода one ( ) устанавливают глобальную переменную, тогда как тест для метода two ( ) ищет эту переменную (но не рассчитывает найти се ) .

1048 $ pythonЗ -m doct e s t -v doctest_t est_g l oba l s . py Trying : var ' value ' Expect ing nothing ok Tryi n g : ' var ' in g l oba l s ( ) Expect ing : T rue ok Trying : ' va r ' in g l oba l s ( ) Expe c t i ng : Fa l se ok 2 i t ems had n o t e s t s : doctest_t e s t_g l oba l s doct e s t_t e s t_gl oba l s . TestGl obal s 2 i t ems p a s sed a l l t e s t s : 2 t e s t s in doct e s t_t e s t_g l oba l s . T e s t G l oba l s . one 1 t e s t s i n doct e s t_ t e st_g l oba l s . T e s t G l obal s . two З t e s t s i n 4 i t ems . 3 p a s s ed and О fa i l ed . Te s t pa s s e d .

Однако это вовсе не означает, что тесты не моrут взаимодействовать между собой, если у них есть возможность изменять содержимое общих переменных, определенных в модуле. Листинг 1 6. 23. doctest_mutaЫe_global s . ру

module data

=

{}

cl a s s T e s t G l oba l s : def one ( s el f ) : 11 11 11

> > > T e s t G l obal s ( ) . one ( ) > > > ' va r ' i n modu l e dat a T rue """

modul e_data [ ' va r ' J

' value '

def two ( s el f ) : """

> > > ' va r ' i n Fa l s e

modul e data

11 11 11

Переменная _module_data модуля изменяется тестами для метода one ( ) , в ре­ зул1;гате чего тест для модуля two ( ) не проходит.

16.2. doctest: 18Сnlро&аНМ8 АОКУll8НТ8ЦМ М

1047

$ pythonЗ -m do c t e s t -v do c t e s t _mutaЫ e_g l o ba l s . py Trying : T e s t G l obal s ( ) . one ( ) Expe c t i n g nothing ok T rying : ' var ' in modu l e data Expect ing : T rue ok Trying : ' va r ' in modul e data Expe ct ing : Fal s e ******************************** ********************************** F i l e " . . . /doct e s t_mutaЫ e_g l oba l s . py " , l i ne 2 5 , i n doct e s t mu t a Ы e _g l obal s . TestGl oba l s . two Fa i l e d examp l e : ' va r ' i n modu l e data Expe c t ed : Fa l s e Got : T rue 2 i t ems had no t e s t s : doct e s t_mut aЫe_g l oba l s doct est_mutaЫe_g l oba l s . T e s t G l oba l s 1 i t ems passed a l l t e s t s : 2 t e s t s i n doct e s t_mutaЫe_gl oba l s . T e s t G l oba l s . one **************** ***************************** *********** ********** 1 i t ems had f a i lures : 1 of 1 i n doct e s t -mu t a Ы e g l obal s . T e s tGl obal s . two 3 t e s t s in 4 i t ems . 2 pa s s ed and 1 fai l ed . * * *T e s t Fa i l ed * * * 1 f a i l u re s . -

Если тестам необходимо использовать глобальные значения (например, для параметризации среды выполнения) , их можно передать функциям testmod ( ) и t estfil e ( ) для настройки контекста с использованием данных, контролируемых вызывающим кодом. Дополнительные ссылки •

Раздел документации стандартной библиотеки, посвященный модулю doct e s t 2 .



The Mighty Dictionary (Brandon Rhodes)3• Презентация материала, посвященного вну­ тренним механизмам работы словарей, на конференции PyCon 201 О.



d i f f l i b (раздел 1 .4). Библиотека Python содержит инструменты для вычисления раз­ личий между последовательностями и, в частности, для сравнения текста.

2 3

https : / / doc s . python . org / 3 . 5 / l ibrary/doct e s t . html www . yout ube . com/wa t c h ? v=C 4 K c B x z cA 6 8

1048 •

Sphinx4 • Генератор документации стандартной библиотеки Python, который в силу

простоты его использования и способности генерировать аккуратный вывод в не­ скольких цифровых и печатных форматах был адаптирован для использования в неза­ висимых проектах. Sphinx вклю чает расширение для выполнения тестов при обработ­ ке исходных файлов документации. •





ру . t e s t 5 . Независимая программа для выполнения тестов с поддержкой модуля doct e s t . nose2 6 • Независимая программа дл я выполнения тестов с поддержкой модуля doc t e s t . мanue l 7 • Независимая программа для выполнения тестов н а основе документации

с дополнительными возможностями извлечения тестовых примеров и интеграции с ге­ нератором документации Sphinx.

1 6.3.

фреймворк автоматизированного тестирования uni tte s t:

Фреймворк автоматизированного тестирования из модуля uni t t e s t основан на архитектуре XU11it , разработанной Кентом Беком и Эрихом lаммой. Та же идея повторена во многих других языках программирования , включая С, Pcrl, Java и Sшallt alk. Фреймворк, реализованный в модуле uni t t e s t , обеспечивает ав­ томатизацию тестирования за счет поддержки тестовых конфи гу раций, наборов тестов и средств для их выполнения.

1 6.3. 1 . Базовая структура тестов Тесты , определяемые модулем uni tte s t , формируются из двух составляющих:

фикстур (fixtures) - кода, управляющего зависимостями тестов, и собственно

те­

стов. Отдельные тесты формируются посредством создания подклассов Tes tCase и переопределения или добавления соответствующих методов. В следующем при­ мере класс S impl i s t i cTe s t содержит единственный метод test ( ) для выполне­ н ия теста, который пе должен проходить, если а не равно Ь. Листинг 1 6.24. uni ttest_s imple . py

import uni t t e s t

c l a s s S imp l i s t icTest ( un i t t e s t . T e s tCa s e ) : def t e s t ( se l f ) : а = 'а' Ь 'а' s e l f . a s s e r t Equa l ( a , Ь ) =

4 www . 5 6 7

sph i nx -doc . org ht tp : / /doc . p yt e s t . o r g / en / l a t e s t / https : / / n o s e 2 . readthedoc s . i o / en / l a t e s t / ht tps : / /pyt honhos t e d . o r g /manu e l /

16.3. unlttest: фреймеорк а1ПОМ81М3И рованноrо тес:nqюван ия

1048

1 6.3.2. Выполнение тестов Простейший способ выполнения тестов uni t tes t использование средства автоматического обнаружения тестов, доступного через интерфейс командной строки. -

$ pythonЗ -rn uni t t est uni t t e s t_sirnp l e . py

Ran 1 t e s t in O . O O O s

ок

Этот сокр ащенный вывод включает количество времени, которое потребова­ лось для выполнения тестов, и индикатор состояния для каждого теста (символ . в первой строке вывода говорит о том , что тест прошел). Для получения более подробных результатов тестирования следует использовать параметр - v . $ pythonЗ -rn uni t t es t -v uni t t e st_sirnp l e . py t e s t ( un i t t es t_s irnpl e . S irnp l i s t i cT e s t )

. . . ok

Ran 1 test in O . O O O s ок

1 6.3.3. Результаты тестов Возможны три исхода тестов (табл. 1 6. 1 ) . Не существует явных способов заста­ вить тест пройти, поэтому статус теста зависит от наличия ( или отсутствия) ис­ ключений. Таблица 1 6. 1 . Возможные исходы тестовых сценариев Исход

Описание

ok

Тест проходит

FAIL

Тест не проходит и возбуждает исключение

ERROR

Тест возбуждает исключение, отличное от As sertionError

Л истинг 1 6.25. uni tte s t_outcome s . py

irnport uni t t e s t c l a s s Out cornesTes t ( un i t t e s t . T e s tCa se ) : def t e s t Pa s s ( s e l f ) : return def t e s t Fa i l ( s e l f ) : s e l f . a s s e r t Fa l s e ( T rue )

Asserti onError

ГАава 16.

1050

ИнструмеН1Ы раэра6аnсм

def t e s tError ( se l f ) : ra i s e RuntimeErro r ( ' T e s t e r r or ! ' )

Если тест не проходит или генерирует ошибку, то в результаты включается трассировочная информация. $ pythonЗ -m un i t t est un i t t e s t_outcome s . py EF . ERROR : t e s t Error ( un i t t e s t_out come s . OutcomesTest ) T raceback ( mos t recent c a l l l a s t ) : Fi l e " . . . / un i t t e st_outcome s . py " , l i ne 1 8 , i n t e s t E rror raise Runt imeE rror ( ' Te s t error ! ' ) Runti meE r r o r : T e s t e r r o r !

FAI L : t e s t Fa i l

( un i t t e s t_ou t c omes . Out come sT e s t )

T ra c eback ( mo s t recent c a l l l a s t ) : Fi l e " . . . /uni t t e st_out come s . py " , l i ne 1 5 , s e l f . a s s er t Fa l se ( T rue ) As s e r t i onError : T rue i s not f a l s e

in t e s t Fa i l

R a n 3 t e s t s in O . O O l s FAI LED ( fa i lures= l , errors= l )

В предыдущем примере метод t e s t Fa i l ( ) не проходит тестирование, и в сте­ ке вызовов отображается номер строки, по вине которой это произошло. Однако выяснение точной причины непрохождения теста зависит лишь от человека, оценивающего результаты тестирования, поскольку это требует изучен ия кода. Листинг 1 6 . 26. uni ttest_fai lwi thmes sage . py

import uni t t est

c l a s s Fa i l u reMe s s a geTe s t ( un i t t e s t . T e s t C a s e ) : def t e s t Fa i l ( s e l f ) : s e l f . as s e r t Fa l s e ( True ,

' fa i l u r e me s s age goes here ' )

Чтобы упростить выяснение причины непрохождения теста, можно передать методам fai l * ( ) и a s s e r t * ( ) аргумент msg, позволяющий вывести более инфор­ мативное сообщение об ошибке. $ pythonЗ -m un i t t e s t

-v

uni t t es t_fa i l wi t hme s sage . py

t e s t Fa i l ( un i t t e s t_fa i lwi thme s sage . Fa i l u reMe s sageTe s t )

. . . FAI L

16.3. unlttest: фреймеорк автомаnt3Ированноrо тесrмрованин

1051

FAI L : t e s t Fa i l ( un i t t e s t_fa i lwi thme s s age . Fa i l ureMe s sageT e s t ) Traceba ck ( mo s t recent ca l l l a s t ) : F i l e " . . . /uni t t e st_fai lwithme s s a g e . py " , l i ne 1 2 , in t es t Fa i l se l f . a s se r t Fa l se ( T rue , ' fa i lure me s s a g e goes here ' ) As s e r t i onE rro r : T rue i s not f a l s e : f a i l u re me s s a g e goes here Ran 1 t e s t i n O . O O Os FAI LED ( fa i l ures=l )

16.3.4. Подтверждение выполнения условия Большинство тестов проверяет истишюсть некоторых условий. Существуют два подхода к написанию таких тестов, и выбор одного из них зависит от намере­ ний автора тестов и ожидаемого результата работы тестируемого кода. Листинг 1 6.27. unitte s t_truth . py

import uni t t e s t

c l a s s T ruthTest ( un i t t e s t . TestCase ) : def t e s tAssertTrue ( se l f ) : s e l f . a s s ertTrue ( T rue ) def t e s tAs s e r t Fa l s e ( s e l f ) : s e l f . a s se r t Fa l s e ( Fa l s e )

Если код генерирует значение, эквивалентное значению True , то следует использовать метод a s sertTrue ( ) . Если же код генерирует значение, которое эквивалентно значению Fa l se, то целесообразнее использовать метод assert Fa l s e ( ) . $ pythonЗ -m uni t t e s t

-v

uni t t e st_t rut h . py

t e s tAs s e r t Fa l s e ( uni t t es t_t ru th . T ru t hT e s t ) . . . o k t e s t As s e r t T rue ( un i t t e s t_t ruth . T ru t hTe s t ) . . . o k

Ran 2 tests in O . OOOs ок

16.3.S. Тестирование равенства Модуль uni t test включает методы, предназначенные для проверки равенства двух значений.

1 052 Листинг 1 6.28. uni tte s t_equal i ty . py

import uni t t es t

c l a s s Equa l i t yT e s t ( un i t t e s t . T e s t C a s e ) : def t e s t ExpectEqual ( se l f ) : s e l f . a s s ert Equa l ( l , 3 - 2 ) def t e s t Expect Equa l Fa i l s ( s e l f ) : se l f . a s s ert Equa l ( 2 , 3 - 2 ) def t e s t ExpectNo tEqua l ( s e l f ) : se l f . a s ser tNotEqual ( 2 , 3 - 2 ) def tes tExpectNotEqua l Fa i l s ( s e l f ) : s e l f . a s s ertNot Equa l ( l , 3 - 2 )

В случае непрохождения тестов эти специалы1ые методы генерируют сообще­ ния об ошибках, идентифицирующие сравниваемые значения. $ pythonЗ -m uni t t e s t

-v

uni t t e s t_equa l i t y . py

t e s t Expe ct Equa l ( un i t t e s t_equa l i t y . Equa l i t yTest ) . . . o k t e s t ExpectEqua l Fa i l s ( uni t t e s t_equa l i t y . Equa l i t yT e s t ) . . . FAI L t e s t ExpectNotEqual ( uni t t e s t_equa l i t y . Equa l i tyT e s t ) . . . o k t e s t ExpectNotEqu a l Fa i l s ( un i t t es t_equa l i t y . Equa l i t yT e s t ) . . . FAIL

FAI L : t e s t Expect Equa l Fa i l s ( un i t t e s t_e qua l i t y . Equa l i t yTe st ) T r a c eba c k ( mo s t recent c a l l l a s t ) : Fi l e " . . . /un i t t es t_equal i t y . py " , l i ne 1 5 , i n t e s t ExpectEqua l Fa i l s s el f . a s s e rtEqua l ( 2 , 3 - 2 ) As s e r t i onE r ro r : 2 ! = 1

FAI L : t e s t ExpectNotEqua l Fa i l s ( uni t t e s t_equa l i t y . Equal i t yT e s t ) Traceba c k ( most recent ca l l l a s t ) : Fi l e " . . . /un i t t e s t _equa l i t y . py " , l i ne 2 1 , i n t e s t Expe ctNotEqua l Fa i l s s e l f . a s s ertNotEqua l ( l , 3 - 2 ) Assert i onError : 1 1 ==

Ran 4 t e s t s in O . O O l s FAI LE D ( fa i lures=2 )

16.З. unlttest: фреАмворк а1ПОМ81М3ироеанноrо 1'8С'1Мрованин

1053

1 6.3.6. Приблизительное равенство Кроме проверки на строгое равенство возможно тестирование приблизитель­ ного равенства ч исел с плавающей точкой с помощью методов as sertAlmo s t Equa l ( ) и a s s ertNotAlmo s tEqua l ( ) . Листинг 1 6.29. uni ttest_almostequal . py

import uni t t e s t

c l a s s Almo s t Equa lTe s t ( un i t t e s t . T e s t C a s e ) : d e f t e s t Equa l ( s e l f ) : se l f . a s s e r t Equa l ( l . 1 , 3 . 3 - 2 . 2 ) d e f t e s tAlmo s t Equa l ( s e l f ) : se l f . a s ser tAlmo s t Equa l ( l . 1 , 3 . 3 - 2 . 2 , p l a c e s = l ) d e f t e s tNotAlmo s t Equa l ( s el f ) : s e l f . a s s ertNotAlmost Equa l ( l . 1 , 3 . 3 - 2 . 0 , p l a ces= l )

Аргументами этих методов являются сравниваемые значения и количество де­ сятичных знаков после запятой, которые следует учитывать при сравнении. $ python3 -m uni t t e s t u n i t t e s t_a lmo s t equa l . py . F. z:za:= ==== =c:-=:a:aa:w

w

c_._•ш=••a=:=s•�:a-=zac:�=�•==-===c;- = �2

FAI L : t e s t Equa l ( un i t t e s t_almost equa l . Almos t Equa l T e s t ) T ra ceba c k ( mo s t recent c a l l l a s t ) : Fi l e " . . . /uni t t e s t_almo s t e qua l . py " , l i ne 1 2 , i n t e s tEqu a l s e l f . a s s e rtEqu a l ( l . 1 , 3 . 3 - 2 . 2 ) As s e r t i onErro r : 1 . 1 ! = 1 . 0 9 9 9 9 9 9 9 9 9 9 9 9 9 9 6

Ran 3 t e s t s i n O . O O l s FAI LED ( fa i lures= l )

1 6.3. 7. Контейнеры Кроме обоб щенных методов a s s e r tEqua l ( ) и as sertNo tEqual ( ) доступны специальные методы, предназнач енные для сравнения контейнерных объектов, таких как списки, словари и множества. Листинг 1 6. 30. uni t tes t_equali ty_con tainer . ру

import t extwrap irnport u n i t t e s t

1 054 c l a s s Cont a i ne rEqua l i t yTe s t ( un i t t e s t . T e s t C a s e ) : def t e s t Count ( s e l f ) : s e l f . a s sert Count Equa l ( [1, 2, 3, 2] , [1, 3, 2 , 3] ,

de f t e s t D i c t ( se l f ) : s e l f . a s s e r t D i ctEqual ( { ' а' : 1, 'Ь' : 2} , { ' а' : 1, 'Ь' : 3 ) ,

def t e s t L i s t ( s e l f ) : se l f . a s s e rt L i s tEqual ( [ 1, 2, 3] , [1, 3, 2] ,

d e f t e s tMu l t i L i n e S t r i ng ( se l f ) : s e l f . a s se rtMul t i L i neEqua l ( t e x twrap . dedent ( " " " Thi s s t r i ng has more than one l i ne . ff ff ff

)

,

t extwra p . dedent ( " " " Th i s s t ring has mo re than two l i nes . "" " ) ,

def t e s t S e quence ( s e l f ) : s e l f . a s s e r t S equenceEqua l ( [1, 2 , 3] , [1, 3, 2] ,

def t e s tSet ( s e l f ) : se l f . a s se r t S et Equa l ( set ( [ l , 2 , 3 ] ) , set ( [ 1 , 3 , 2 , 4 ] ) ,

def tes tTup le ( s e l f ) : se l f . a s s e r t Tup l eEqua l ( (1, 'а' ) , ( 1, 'Ь' ) ,

16.З.

unltteat: фреймворк ВВ'rОМ8'1М3Мрованноrо 18Сl'Ирования

1 055

Кажд ы й из эти х м етодов соо б щает о нераве нстве с помо щ ью ф о рмата , имею­ е щ го см ы сл для в ходного типа, ч то облег ч ает по н имание при ч ин непрохождения теста и внесение соответствующ их исправлений. $ python3 -m uni t t e s t uni t t e st_equa l i t y_cont a i ne r . py FFFFFFF FAI L : t e s t Count ( un i t t e s t_equ a l i t y_cont a i ne r . Cont a i ne rEqua l i t yT e st } T raceba c k (most recent ca l l l a s t } : Fi l e " . . . \ u n i t t e s t_equa l i t y_cont a i n e r . py " , l i ne 1 5 , ( 1 , 3, 2 , 3 ] , Assert i onE r ro r : E l ement c ount s were not equa l : Fi rst has 2 , Second has 1 : 2 F i r s t has 1 , Second has 2 : 3

in t e s t Count

FAI L : t e s t D i c t ( un i t t e s t_equ a l i t y_conta i n e r . Cont a i ne rEqua l i t yTe s t ) T ra c eba c k ( mo s t recent ca l l las t } : Fi l e " . . . uni t t e s t_equa l i t y_cont a i ner . py" , l i ne 2 1 , in t e s t D i c t { ' а ' : 1, ' Ь ' : 3 } , A s s e r t i onEr ro r : { ' а ' : 1 , ' Ь ' : 2 } ! = { ' а ' : 1 , ' Ь ' : 3 } - { 'а' : 1, 'Ь' : 2} ? + { 'а' : ?

1,

'Ь' : 31

FAI L : t e s t L i s t ( un i t t e s t_equ a l i t y_cont a i n e r . Cont a i ne rEqua l i t yTest } T raceba c k ( mo s t recent ca l l l a s t } : Fi l e " " . \uni t t e s t _equa l i t y_cont a i n e r . ру" , l i ne 2 7 , (1, 3, 2] , As s e r t i onErro r : Li s t s di f fe r : ( 1 , 2 , 3 ) ! = [ 1 , 3 , 2 ] F i r s t di f f e r i ng el eme nt 1 : 2 3 - ( 1, 2 , 3] (1, 3, 2]

+

FAIL : t e stMu l t iLine S t r i n g ( un i t t e s t_equa l i t y_cont a i ne r . Cont a i n e rEqua l i t yT e s t }

in t e s t L i s t

1056

l'мве 16. МНС1РJМ81nы реэрабоnси

Traceba c k ( mo s t recent c a l l l a s t ) : Fi l e " . . . \ uni t t e s t_equa l i t y_cont aine r . py " , l i ne 4 1 , i n t e s tMul t i L i ne S t r i ng """) , Assert i onErro r : ' \ nThi s s t r i ng \ nha s mo re than one \ nl ine . \ n ' ' \ nThi s s t r i ng has \nmo re than two \ n l i ne s . \ n '

!=

- Thi s s t ri ng + Thi s s t r i ng has ++++ ?

- has more t han one ? + mo re than two ? ++

- l i ne . + l i ne s . + ?

FAIL : t e s t S e que nce ( un i t t e s t_e qua l i t y_conta ine r . Cont a i ne rEqua l i t yT e s t ) T raceba c k ( most recent c a l l l a s t ) : Fi l e " . . . \uni t t e s t_equa l i t y_cont a i n e r . py " , l i ne 4 7 , i n t e s t S equence [ 1 , 3, 2 ] , A s s e r t i onError : Sequenc e s di ffe r : [ 1 , 2 , 3 ] ! = [ 1 , 3 , 2 ] F i r s t di f f e r i n g e l ement 1 :

2 3

- [ 1 , 2 , 3] + [1, 3, 2]

FAI L : t e s t S e t ( un i t t e s t_equa l i t y_cont a i ne r . Cont a ine rEqua l i t yT e s t ) T raceba c k (most recent c a l l l a s t ) : Fi l e " . . . /uni t t e s t_equa l i t y_cont a i n e r . py " , l i ne 5 3 , in t e s t S et s e t ( [ l , 3, 2 , 4 ] ) , As s e r t i onE r ro r : I t ems in the se cond s e t but not the f i rs t : 4

FAI L : t e s t Tupl e ( un i t t e s t_equa l i t y_cont a i ne r . Cont a i ne rEqua l i t yT e s t ) T raceba c k ( mo s t recent c a l l l a s t ) : Fi l e " . . . \un i t t e s t_equa l i t y_cont a i ne r . py " , l i ne 5 9 , i n t e s tTup l e (1, 'Ь' ) , Asse r t i onError : Tup l e s d i f fe r : ( 1 , ' а ' ) ! = ( 1 , ' Ь ' )

16.З. untttest: фреймворк 88ТОМ81'И3Ированноrо тесnqювания

1 057

F i r s t di f f e r i n g el ement 1 : 'а' 'Ь' - (1,

'а' J

(1,

'Ь' )

?

+

?

Ran 7 t e s t s i n 0 . 0 l O s FAILED ( f a i l ures=7 )

Для тестирования принадлежности контейнеру следует использовать метод as s ertln ( ) . Листинг 1 6. 3 1 . uni tte s t_in . py

import uni t t e s t

c l a s s Cont a i ne rMemЬershipTes t ( u n i t t e s t . TestCas e ) : def t e s t Di ct ( s el f ) : sel f . assert!n ( 4 ,

(1:

def t e s t L i s t ( s e l f } : sel f . assertin ( 4 ,

[1, 2, З] )

'а' , 2:

'Ь' , З:

'с' } )

de f t e s t S e t ( s e l f ) : self . as sert!n ( 4 , set ( [ 1 , 2 , З ] ) )

Метод a s s ertl n ( ) применим в отношении любого объекта, который поддер· живает оператор in или API контейнеров. $

pythonЗ -m un i t t e s t un i t t e s t_i n . py

FFF FAI L : test D i ct ( un i t t e s t_i n . Cont a i n e rMemЬe rshipT e s t ) Tra cebac k ( most recent c a l l l a s t ) : Fi l e " . . . /un i t t e s t_i n . py " , l i ne 1 2 , in t e s t D i ct sel f . assert ! n ( 4 , ( 1 : ' а ' , 2 : ' Ь ' , З : ' с ' } ) Assert ionError : 4 not found i n ( 1 : ' а ' , 2 : ' Ь ' , З :

'с' }

FAI L : t e s t L i s t ( un i t t e s t_i n . Conta inerMemЬershipT e s t ) T raceback ( most recent c a l l l a s t ) : Fi l e " . . . / uni t t e s t_in . py " , l i ne 1 5 , i n t e s t L i s t

Гмва 16. ИНС'IJJУменrы р83ра6откм

1 058 s e l f . a s s ert i n ( 4 , [ 1 , 2 , 3 ] } Assert i onErro r : 4 not found in ( 1 , 2 , 3 ]

FAI L : t e s t Set ( un i t t e s t_i n . Cont a i ne rMemЬe rshipT e s t ) T ra ceback (mo s t recent ca l l l a s t ) : F i l e " . . . /uni t t e s t_i n . p y " , l i ne 1 8 , i n t e s t Set se l f . a s s e rt i n ( 4 , se t ( [ l , 2 , 3 ] } ) As sert i onErro r : 4 not found i n ( 1 , 2 , 3 }

Ra n 3 t e s t s in O . O O l s FAILED ( fa i lures= 3 )

1 6.3.8. Тестирование исключений Как уже отмечалось, если тест возбуждает исJUJючение, отличное от As sertion Error, то оно трактуется как ошибка. Это поведение можно использовать для об­ наружения ошибок при изменении кода, уже покрытого тестами. Однако в неко­ торых ситуациях тест доJiжен проверять, что код действительно генерирует ис­ ключение. Например, если атрибуту объекта присваивается некорректное значе­ ние, то использование метода a s s e r tRa i ses ( ) приводит к бoJiee попятному коду, чем перехват исключения в тесте. Следую щий пример вJUJючает два теста, кото­ р ые можно сравнить между собой на основании этих соображений. Листинг 1 6.32. uni tte s t_exception . py

import un i t t e s t

d e f ra i s e s_error ( * a r g s , * * kwds ) : ra i s e Va lueError ( ' I nva l i d va lue :

' + s t r ( a r g s } + s t r ( kwds ) }

c l a s s Ex cept i onT e s t ( un i t t e s t . T e s t C a s e ) : de f te stT rapLoca l l y ( s el f ) : t ry : ra i s e s_e rror ( ' a ' , Ь= ' с ' ) except Va lueErro r : pass e l se : s e l f . fai l ( ' Di d not s e e ValueError ' ) def t e stAs s e r t Ra i s e s ( s e l f ) : s e l f . a s s e r t Ra i s e s ( Va lueErro r , r a i s e s_e rro r , 'а', Ь= ' с ' ,

16.З. unlttelt: фреАмворк 81ПО1181М3Мро88нноrо Т8С1Мро88НМR

1 059

Оба теста приводят к одинаковым результатам, но результаты второго теста, в котором используется метод as s ertRais es ( ) , более лаконичны. $ pythonЗ -m uni t t e s t

-v

uni t t e s t_except i on . py

t e s tAs sertRa i s e s ( un i t t e s t_except ion . Except i onTe s t ) . . . o k t e s t T rapLoca l l y ( un i t t e s t_except i on . Except i onTe s t ) . . . o k

Ran 2 t e s t s i n O . O O O s ок

1 6.3.9. Разделяемые контексты тестов Конфигурационные объекты (разделяемые контексты ) , или фикстуръt (fix­ tures) - это внешние ресурсы , или зависимости, необходимые тестам. Например, всем тестам одного класса может требоваться экземпляр другого класса, предо­ ставляющий конфигурационные параметры или другие разделяемые ресурсы. К ч ислу других фикстур относятся соединения с базами данных и временные фай­ лы. ( Многие будут утверждать, что использование внешних ресурсов лишает те­ сты статуса "блочных", но такие тесты по-прежнему остаются тестами и выполня­ ют свою полезную роль.) Модуль uni ttes t включает специальные перехватчики, предназначенные для конфигурирования и освобождения любых разделяемых контекстов, необходи­ мых тестам. Чтобы установить разделяемые контексты для каждого отдельного тестового сценария , следует переопределить метод s etU p ( ) класса Tes tCas e. Чтобы освободить ресурсы, занимаемые разделяемыми контекстами, в которых больше нет необходимости, следует использовать метод tearDown ( ) . Чтобы ис­ пользовать один набор разделяемых ресурсов для всех экземпляров тестового класса, необходимо переопределить методы s etUpClas s ( ) и tearDownCl as s ( ) класса TestCas e. Наконец, чтобы организовать управление особенно дорого­ с·rоящими операциями конфигурирования одновременно для всех тестов в прt.'­ делах модуля , следует использовать функции уровня модуля s etU pModu l e ( ) и tearDownModul e ( ) . Л истинг 1 6.33. uni t tes t_fixtures . ру

import random impo rt uni t t e s t d e f s etUpModu l e ( ) : print ( ' I n s e t UpModu l e ( ) ' ) def t ear DownModu l e ( ) : print ( ' I n t e a r DownModu l e ( ) ' ) c l a s s Fi x t u r e sTes t ( un i t t es t . T e s t Ca s e ) :

@ c l a s smethod def s etUpC l a s s ( c l s ) : p r i n t ( ' I n setUpC l a s s ( ) ' ) c l s . good_range range ( l , 1 0 ) =

@ c l a s smethod def tearDownC l a s s ( c l s ) : print ( ' I n t e a rDownC l a s s ( ) ' ) del c l s . good_range def s etUp ( s e l f ) : super ( ) . setUp ( ) print ( ' \ n l n s et Up ( ) ' ) # Выбрать число , заведомо принадл ежащее заданному # диап а з он у . Значение " s t op " не в ключае т с я в диапазон , # поэтому необходимо следи ть за тем, чтобы оно # не в ходило в набор допустимых значений дл я данного # варианта . s e l f . value random . randint ( s e l f . good_rang e . start , s e l f . good_range . s t op - 1 , =

def t e a r Down ( s e l f ) : print ( ' I n t ea rDown ( ) ' ) del se l f . va l ue super ( ) . t ea r Down ( ) de f t e s t l ( s e l f ) : print ( ' I n t es t l ( ) ) s e l f . a s s e rtl n ( з e l f . va l ue , '

s e l f . g o od_range )

def t e s t 2 ( s e l f ) : print ( ' I n test2 ( ) ' ) s e l f . a s s e rt i n ( s e l f . va lue , з e l f . g ood_range )

П р и выполнении это го теста очередность выполнения методов р азделяемого контекста и теста очевидна . $ pythonЗ -u



uni t t e s t -v un i t t e st_fixture s . py

In setUpModul e ( ) I n setUpC l as s ( ) t e s t l ( un i t t e s t_fixtures . FixturesTe s t ) I n setUp ( ) In testl ( ) I n t e a rDown ( ) ok t e s t 2 ( un i t t e s t_fixture s . Fixture s Te s t ) I n s e t Up ( ) In test2 ( ) I n t e a rDown ( ) ok

16.Э. unlttest: фреймворк а1ПОМ81Мэмрованноrо тестмрованИJI

1 08 1

In t e a rDownC l a s s ( ) I n t ea rDownModu l e ( )

Ran 2 t e s t s i n O . O O l s ок

Если в процессе очистки ресурсов разделя е мых контекстов возникают ошиб· ки, то, возможно, не все методы tearDown ( ) будуr вызваны. Для полной уверен· ности в том, что завершающие операции будут выполнены корректно, следует ис­ пользовать метод addCleanup ( ) . Листинг 1 6.34. uni tte s t_addcleanup . ру

import import import import

random shut i l t emp f i l e uni t t e s t

def remove_tmpdi r ( di rname ) : print ( ' I n remove_tmpdi r ( ) ' ) shut i l . rmt ree ( d i rname )

c l a s s FixturesTest ( un i t t e s t . T e stC ase ) : def setUp ( s e l f ) : super ( ) . s e tUp ( ) s el f . tmpdi r temp f i l e . mkdt emp ( ) s el f . addClea nup ( remove_tmpdi r , s e l f . tmpdi r ) =

def t est l ( s e l f ) : print ( ' \ ni n t e s t l ( ) ' ) def t e s t 2 ( s e l f ) : print ( ' \ n i n t e s t 2 ( ) ' )

В этом примере теста создается временный катало�; которь1й по завершении теста удаляется средствами модуля shu t i l ( см. раздел 6.7) . $ pythonЗ -u -m un i t t e st -v uni t t e s t_a ddcle anup . py t e s t l ( u n i t t e s t_addc leanup . Fi xt u resTes t ) I n testl ( ) I n remove_tmpdi r ( ) ok t e s t 2 ( un i t t e s t_addcleanup . FixturesT e s t ) In test2 ( ) I n remove_tmpd i r ( ) ok

1062

Ran 2 t e s t s in О . О О З s

ок

1 6.3. 1 О. Повторение тестов с различными входными данными Часто оказывается полезным выполнять одну и ту же логику теста с различны­ ми входными данными. В этом отношении популярен прием , в соответствии с ко­ торым вместо того, чтобы определять тестовые методы для каждого небольшого сценария по отдельности , следует создать один тестовый метод, содержащий не­ сколько родственных вызовов утверждений. Суть проблемы, связанной с таким подходом, заключается в том , что коль скоро одно утверждение не выполняется, т о все остальные пропускаются. Лучшим решением является использование мето­ да subTes t ( ) с целью создания контекста для теста в пределах тестового метода. Если после этого тест не проходит, то выводится соответствующее сообщение и продолжается выполнение оставшихся тестов. Листинг 1 6. 3 5 . uni tte s t_suЬte s t . py

import uni t t e s t

c l a s s SubTe s t ( un i t t e st . T e s t C a s e ) : de f t e s t_comЬ i ned ( s e l f ) : s e l f . a s s e r t Regex ( ' abc ' , s e l f . a s s e r t Regex ( ' аЬс ' , i Следующие утверждения s e l f . a s s e r t Regex ( ' abc ' , s e l f . a s s e r t Regex ( ' аЬс ' ,

'а' ) 'В' ) не пров еряются ! 'с' ) 'd• )

def t e s t_wi th_subt e s t ( s el f ) : for pat i n [ ' а ' , ' В ' , ' с ' , ' d • ] : wit h s e l f . subT e s t ( pa t t e r n=pat ) : s e l f . a s s e r t Regex ( ' abc ' , pat )

В этом примере утверждения для шаблонов ' с ' и ' d' в методе t e s t comЬ ined ( ) никогда н е будут выполнены. В то же время метод t e s t_wi th_ suЬ t e s t ( ) выполняется и корректно выводит сообщение о дополнительном случае непрохождения теста. Следует отметить, что программа, выполняющая тесты , считает, что существуют только два теста, хотя выводит три сообщения о том, что тесты не прошл и. $ pythonЗ -m un i t t e s t - v u n i t t e s t_subt e s t . py tes t_comЬi ned ( un i t t e s t_subt e s t . SubTe s t ) . . . FAI L t e s t_wi t h_subt est ( un i t t e s t_subte s t . S ubTe s t ) FAI L : t e s t_comЬi ned ( u n i t t e st_subt e s t . S ubT e st )

16.Э. unlttest: фрейМllQРК автом81И3Ированноrо 18С1Мрованин

1 063

T raceback (most recent c a l l l a s t ) : Fi l e " . . . \un i t t e s t_suЬt e s t . py " , l i ne 1 3 , in t e s t_comЬ i ned s e l f . a s s e r t Regex ( ' abc ' , ' В ' ) Assert i onErro r : Regex didn ' t ma t c h : ' В ' not found in ' аЬс '

FAI L : t e s t_w i t h_suЬt e s t ( un i t t e s t_suЬt e s t . SuЫe s t )

( pa t tern= ' B ' )

T raceback ( mo s t recent c a l l l a s t ) : Fi l e " . . . \ uni t t e s t_suЬtes t . py " , l i ne 2 1 , in t e s t_with_suЬt e s t s e l f . a s s e r t Regex ( ' abc ' , pat ) As s e r t i onErro r : Regex didn ' t ma t ch : ' В ' not found in ' аЬс '

FAI L : t e s t_wi t h_suЬ t e s t ( un i t t e s t_suЬt e s t . SuЫ e s t )

( pa t t ern= ' d ' )

Tra ceba c k ( mo s t recent c a l l l a s t ) : Fi l e " . . . \uni t t e s t_suЬt e s t . py " , l i ne 2 1 , in t e s t_wi th_suЬ t e s t s e l f . a s s e rtRegex ( ' abc ' , pat ) As s e r t i onErro r : Re gex didn ' t ma tch : ' d ' not found i n ' аЬс '

Ran 2 t e s t s in 0 . О О З s FAI LED ( fa i l u r e s= З )

1 6.3 . 1 1 . Пропуск тестов Иногда полезно иметь возможность пропускать тот или иной тест, если н е вы­ полняется некоторое внешнее условие. Например, если пишутся тесты , прове­ ряющие поведение библиотеки при работе с конкретной версией Pythoн, то не имеет смысла выполнять эти тесты в случае использования другой версии. Если требуется , чтобы тесты всегда пропускались, можно декорировать тестовые клас­ сы и методы декоратором s kip ( ) . Для проверки условия перед пропуском тестов можно использовать декораторы s kip ! f ( ) и s kipUn l e s s ( ) . Листинг 1 6.36. uni tte s t_skip . py

import sys import uni t t e s t c l a s s S k ippi ngT e s t ( un i t t e s t . T e s t C a s e ) : @ un i t t e s t . s kip ( ' a l ways s k ipped ' ) def tes t ( s e l f ) : s el f . a s s e r t T rue ( Fa l s e ) @un i t t e s t . s kip i f ( s ys . ve r s i on_i n fo ( O ] > 2 , ' onl y runs on python 2 ' ) de f tes t_python2_onl y ( s e l f ) : s el f . a s s e rt T rue ( Fa l s e )

1 064 @uni t t e s t . s kipUn l e s s ( s ys . p l a t form ' Da rwin ' , ' onl y runs on macOS ' ) def t e s t_ma cos_onl y ( s e l f ) : se l f . a s s e rtTrue ( T rue ) def t e st_ra i s e_s k i p t e s t ( s e l f ) : r a i s e un i t t e s t . S kipT e st ( ' skipping v i a excep t i on ' )

В случае сложных условий, которые трудно задать в виде одного выражения, передаваемого методу s kipl f ( ) или s kipUn l e s s ( ) тестовый сценарий может обеспечить пропуск теста, непосредственно возбудив исключен ие SkipT e s t . ,

$ pythonЗ - m uni t t e s t -v u n i t t e s t_s ki p . py t e s t ( uпi t t e s t_s kip . S kippi пgTe s t ) . . . s k ipped ' always s ki pped ' t e s t_ma cos_o n l y ( uп i t t e s t_skip . S kippingT e s t ) . . . s k i pped ' on l y ruпs оп ma cOS ' t e s t_python2_oпl y ( un i t t e s t_s kip . SkippiпgTe s t ) . . . s kipp e d ' оп l у runs о п python 2 ' t e s t_ra i s e_s kipt e s t ( un i t t est_s k i p . S k ipp iпgT e s t ) . . . s ki pped ' s kippiпg via except i oп '

Ran 4 t e s t s i n O . O O O s О К ( s ki pped= 4 )

1 6.3. 1 2 . Игнорирование неудачных тестов Вместо удаления тестов, которые постоянно не проходят, можно пометить их декоратором expectedFa i lure ( ) благодаря чему эти случаи ненрохождения тестов будут игнорироваться. ,

Листинг 1 6.37. uni ttes t_expectedfai lure . py

import un i t t e s t

c l a s s Test ( un i t t e s t . TestCa s e ) : @uni t t es t . expectedFa i l ure def t e s t_neve r_pa s se s ( s e l f ) : se l f . a s s e rtT rue ( Fa l s e ) @uni t t e s t . expectedFa i l ure def t e s t_a l ways_pa s s e s ( sel f ) : s e l f . a s s e rtTrue ( T rue )

Если ожидается , что тест не должен пройти, а на самом деле он проходит, то это условие трактуется как специальная разновидность непрохождения теста, и в таком случае выводится сообщен ие о непредвиденном прохождении теста.

16.4. tnюe: трассировма ВЫПОNtенмя проrраммы

1 085

$ python З -m un i t t es t -v uni t t e st_expectedfa i l ure . py t e s t_a l ways_p a s s e s ( u n i t te s t_expe c t e d f a i l u r e . T e s t ) . . . unexpe cted succe s s t e s t_never_passes ( un i t t e s t_expect edf a i l ure . Te s t ) . . . expected fa i l ure

Ran 2 t e s t s i n O . O O l s FAI LED ( expected f a i l u res= l , unexpect ed succe s s e s = l )

Дополнительные ссылки

8



Раздел документации стандартной библиотеки, посвященный модулю uni t t e s t .



doct est (раздел 1 6. 2). Альтернативное средство выполнения тестов, встроенных в строки документирования или содержащихся во внешних файлах документации.



9 nose . Сторонняя программа для выполнения тестов, предлагающая усовершенство­





ванные средства обнаружения тестов. 10 pyt e s t • Популярная сторонняя программа дл я выполнения тестов с подцержкой распределенного выполнения и альтернативной системой управления разделяемыми контекстами. 11 t e s t repo s i t o ry • Сторонняя программа для выполнения тестов, используемая в про­

екте OpeпStack, которая поддерживает параллельное выполнение и отслеживание непрошедших тестов.

1 6.4.

trace :

трасс и ровка выполнен ия программы

Модуль t race может быть полезным для понимания того, как работает про­ грамма. Он позволяет наблюдать :�а выполнением инструкций, создавать отчеты о покрытии кода и исследовать отношения между функциями, вызывающими одна другую.

1 6.4. 1 . Пример программы Представленная ниже программа будет использоваться во всех примерах, при­ веденных в оставшейся части данного раздела. Эта программа импортирует мо­ дуль recur se и выполняет содержащиеся в нем функции. Листинг 1 б.38. trace example/main . ру _

f r om recurse import recurse

def ma i n ( ) : 8

https : / / docs . python . o rg / 3 . 5 / l ib r a r y/ uni t t e s t . html https : / /nose . readthedocs . i o / e n / l a t e s t / 1 0 http : / / doc . pyt e s t . o r g / en / l at e s t / 1 1 htt p : / / t e s t repo s i t or y . readthedocs . i o / en / l atest /

9

1 088

ГАава 16. ИНСJРУМ8Н1Ы разр86сmсм

print ( ' Th i s i s the ma i n prog ram . ' ) recurse ( 2 ) if

-ma in- '

name ma i n ( )

·

Функция recurse ( ) вызывает саму себя до тех пор, пока аргумент level не до­ стигнет значения О . Л истинг 1 6.39. trace example/ recurse . py

_ def recurse ( l eve l ) : p r i nt ( ' recu r s e ( { } ) ' . forma t ( l evel ) ) i f l evel : recurse ( l evel - 1 )

def not_ca l l ed ( ) : p r i nt ( ' Th i s funct i o n is neve r ca l l ed . ' )

1 6.4.2. Трассировка выполнения Модуль trace можно вызывать непосредственно из командной строки. Пара­ метр - - t race задает отображение строк кода по мере их выполнения. Кроме того, в приведен ном ниже примере задан фильтр - - i gnore-di r , позволяющий избежать трассировки кода в модуле impo r t l ib ( раздел 1 9. 1 ) и других модулях стандартной библиотеки Руt lюп, что, возможно, представляло бы интерес в дру­ гих случаях, но в данном простом примере лишь загромождало бы вывод. $ pythonЗ -m t ra ce - - i gnore-di r= . . . / l i Ь /pythonЗ . 5 \ - - t race t r a ce_examp l e /ma i n . p y - - - modu lename : ma i n , funcname : ma in . ру ( 7 ) : ma i n . py ( l O ) : from recu r s e import recurse - - - modu lename : recur s e , funcname : recurs e . py ( 7 ) : " " " recurs e . py ( l l ) : d e f recurse ( l eve l ) : recurs e . py ( l 7 J : de f not_ca l l ed ( ) : ma i n . py ( l 3 ) : def ma i n ( ) : ma i n . ру ( 1 7 ) : i f name ' ma i n ' ma i n ( ) ma i n . py ( l 8 J : - - - modu l ename : ma i n , funcname : ma i n ma i n . py ( l 4 ) : print ( ' Thi s i s t h e ma in program . ' ) Thi s i s the ma i n prog ram . recur s e ( 2 ) ma i n . py ( l 5 ) : - - - modul ename : recu r s e , funcname : r e cu r s e p r i n t ( ' recurse ( { } ) ' . forma t ( level ) ) recurse . ру ( 1 2 ) : recurse ( 2 ) recurse . py ( l 3 ) : i f l eve l : recur s e ( l evel - 1 ) recurs e . py ( l 4 ) : - - - modu l ename : recu r s e , funcname : r e curse '""'

==

·

16.4. trace: tраССИровма вwпсwtеНия nporpaммw

1 087

p r i n t ( ' recurs e ( { } ) ' . format ( l evel ) ) recur se . ру ( 1 2 ) : recurse ( l ) i f l evel : recu r s e . py ( 1 3 ) : recu rse . ру ( 1 4 ) : recurse ( l evel - 1 ) - - - modul ename : recur s e , funcname : recurse p r i n t ( ' recurse ( { } ) ' . format ( l evel ) ) recurs e . ру ( 1 2 ) : recu r s e ( O ) i f l evel : recurse . ру ( 1 3 ) : u n s e t t race - - - modul ename : t race , funcname : sys . s et t race ( None ) t race . py ( 7 7 ) :

В нервой части вывода отображаются операции настройки, выполняемые мо­ дулем t race. Остальная часть вывода отображает вход в каждую функцию, вклю­ чая модул1,, в котором находится функция, и строки исходного файла по мере их выполнения. Как и следовало ожидать исходя из способа вызова функции recurse ( ) в функции ma i n ( ) , вход в нее выполнялся три раза.

1 6.4.3. Покрытие кода В результате запуска модуля t race из командной строки с указанием параме­ тра - -count создастся отчет о покрытии кода, содержащий подробную информа­ цию о том, какие строки выполняются, а какие пропускаются. Поскольку слож­ ные программы состоят, как правило, из нескольких файлов, для каждого из них создается отдельный отчет о покрытии кода. По умолчанию файлы отчетов запи­ сываются в тот же каталог, в котором находится модуль, и получают имя модуля , но с расширением . соvеr вместо .ру. $ python3 -m trace - - count t race_examp le /ma i n . py Th i s i s recurse recu rse recu rse

the ma i n p ro g ram . (2) (l) ( O)

В этом примере создаются два выходных файла, содержимое которых пред­ ставлено в приведенных ниже листингах. Листинг 1 6.40. trace_example /main . cover

1 : f rom recurse import recu rse 1 : def ma i n ( ) : 1: p r i n t ' Th i s i s the ma i n program . 1: recurse ( 2 ) 1: return

1 : if 1:

name ma i n ( )

ma i n

'

' .

Листинг 1 6.41 . trace_example/recurse . cover

1 : def recurse ( l evel ) : 3: print ' recu rse ( % s ) ' % l eve l

3: 2: 3:

i f l e ve l : recurse ( l eve l - 1 ) return

1 : def not_ca l l ed ( ) : p r i n t ' Th i s funct i on i s never c a l l e d . '

Примечание

Тот факт, что счетчик строки def recu r s e ( l eve l ) : равен 1, вовсе не говорит о том, что функция выполнялась только один раз. Это значение указывает лишь на то, сколь­ ко раз выполнялось определение функции. То же самое относится и к строке def n o t_ ca l l ed ( ) : , поскольку ее определение также обрабатывалось интерпретатором, даже если она ни разу не вызывалась.

Эту программу можно вы полн ить несколько раз, возможно, с другими пара· метрами, для сохранения общих данных о покрытии кода и получения объеди· пенного отчета. При первом запуске модуля trace с указанием выходного файла будет выведено сообщение об ошибке, возникающей при попытке загрузить су­ ществующие данные для объединения с новыми резуль1а1ами в условиях, когда выходной файл еще не создан. $ pythonЗ -m t race - - coverdi r c overdi r l - - c ount \ - - f i l e coverd i r l / coverage_report . da t t race_examp l e /ma i n . py T h i s is the ma i n program . recurse ( 2 ) recurse ( l ) recurse ( O ) S k ipping count s f i l e ' coverd i r l / cover a ge report . da t ' : [ E rrno 2 ] No such f i l e o r di rect o r y : ' coverdi r l / co� era ge_report . da t '

$ pythonЗ -m t race - - coverdi r cove rdi r l - - count \ - - f i l e coverdi r l / coverage_report . da t t race_examp l e /ma i n . py Thi s i s the ma i n prog ram . recurse ( 2 ) recurse ( l ) recurse ( O )

$ pythonЗ -m t race - - coverdi r coverdi r l - - count \ - - f i l e coverdi r l / coverage_repo rt . da t t race_examp l e /ma i n . py Thi s i s recurse recurse recurse

the ma i n program . (2) (l) (O)

$ l s coverdi r l coverage_report . da t

16.4. tnюe: 'lp8CCllpoвlCll

выпсwtен"'8 проrраммы

1 089

После получения и записи в файлы . cover достаточного объема информации о покрытии кода можно получить отчет, используя опцию -- repo r t . $ python3 - m t race - - cove rd i r cove rdi r l - - report - - summa ry \ - -mi s s ing - - f i le coverd i r l / coverage- report . dat \ t race_examp l e /ma i n . py cov% modu l e ( pa t h ) l i nes 537 0% t ra c e ( . . . / l i Ь /python3 . 5 / t race . py ) 7 100% t race examp l e . ma i n ( t ra ce_e xamp l e /ma i n . py ) 85% t race_examp l e . recu rse 7 ( t ra ce_examp l e / recu rs e . py )

Поскольку в дан ном случае программа выполнялась три раза, в отчете о по­ крытии кода выведены значения счетчиков строк, в три раза превышающие предыдущие. Опция - - sumrnary добавляет в результаты данные о покрытии кода в процентном выражении. Для модуля recurse этот показатель составил лишь 87%. В . саvеl"файле отражается тот факт, что тело функции not_called ( ) вообще нс выполнялось, на что указывает префикс >>>>> > . Листинг 1 6.42. coverdi r l / trace_example . recurse . cover 3 : def re curse ( l eve l ) : 9: p r i nt ( ' recurse ( { } ) ' . forma t ( l evel ) ) i f l evel : 9: 6: recu r s e ( level - 1 ) 3 : def no t_ca l l ed ( ) : p r i nt ( ' Th i s func t i on i s never ca l l ed . ' )

>>>»>

1 б.4.4. Взаимные вызовы функций В дополнение к информации о покрытии кода модуль trace может собирать данные и создавать отчет о взаимных вызовах функций . Чтобы получить простой список вызываемых функций, следует использовать опцию - - l i s t func s . $ python3 -m t race - - l i s t funcs t r a ce_e xamp l e /ma i n . py 1 \ g rep -v import l ib Thi s i s recurse recurs e recurse

the ma in prog ram . (2) (l) (O)

funct i ons f i l ename : funcname : f i l ename :

f i lename : mai n f i l ename :

ca l l ed : . . . / l i Ь /python3 . 5 / t race . py , modul ename : t r a c e , unsettrace t r a ce_examp le /ma i n . py , modu l ename : ma i n , funcname : t ra ce_examp l e /ma i n . py , modu l ename : ma i n , funcname : t race_example / recurse . py , modul ename : recu r s e ,

Гмва 16. МНСУруМеН1'Ы рара6сmсм

1 070

funcname : f i l ename : t race_examp l e / recurs e . py , modulename : recur s e , funcname : recurse

Для получения более подробной информации относительно того, откуда именно вызываются функции , следует использовать опцию - - t rac kcal l s .

$ pythonЗ - m t ra c e - - l i s t funcs - - t ra c kca l l s \ t race_examp l e /ma i n . py 1 grep -v import l ib This i s recu r s e recu r s e recu rs e

the ma i n prog ram . (2) (l) (O)

ca l l i ng re l a t i onships : * * * . . . / l i Ь /pythonЗ . 5 / t race . py * * * t race . T race . runctx - > t race . unse t t ra c e - - > t race_examp l e / ma i n . py t race . Trace . runctx -> ma i n . - - > t race_examp l e / recurse . py * * * t race exampl e /ma i n . py * * * ma i n . - > ma i n . ma i n - - > t race_examp l e / recu r s e . py ma i n . ma i n - > recur s e . recurse * * * t race_examp l e / recurs e . py * * * recu rs e . recu r s e - > recu rse . recurse П римечание

Поскольку фильтры - - i gnore-d i r и - - i gnore-modu l e не оказывают воздействия на оп­ ции - - l i s t funcs и - - t ra c k ca l l s, то для исключения ненужной части инф ормации из вы­ вода в этом примере используется команда g rep.

1 6.4. S. П рограммный интерфейс Вызов модуля t race из программного кода с помощью объекта T r ace предо­ ставляет более широкие возможности контроля над его интерфейсом. Объект Trace обеспечивает возможность настройки разделяемых контекстов и других зависимостей перед вы полнением отслеживаемой функции или команды Руtlюп. Листинг 1 6.43. trace run . ру _

i mport t race f rom t race_examp l e . recurse import recurse t racer t race . Trace ( count= Fa l s e , t ra c e=True ) t race r . run ( ' recurse ( 2 ) ) =

'

16.4. trace: трвс:сировке выnоNtенмн nроrраммы

1 071

Поскольку в данном примере трассируется лишь выполнение функции

r ecur s e ( ) , вывод не включает никакой информации относ ителыю модуля main . py. $ python3 t race_run . py - - - modu l ename : < s t r i ng > ( l ) : recu r s e . py ( l 2 ) : recu rse ( 2 ) recu r s e . py ( l 3 ) : recurs e . py ( l 4 ) : - - - modul ename : recu r s e . ру ( 1 2 ) : recurse ( l ) recurse . ру ( 1 3 ) : recurse . ру ( 1 4 ) : - - - modulename : recurse . ру ( 1 2 ) : recurse ( O ) recu r s e . ру ( 1 3 ) : - - - modul ename : t race . py ( 7 7 ) :

t race_run , funcname : modu l ename : recurse , funcname : recurse pr i nt ( ' recu r se ( { } ) ' . f ormat ( l evel ) ) i f l evel : recurse ( l evel - 1 ) recur se , funcname : recurse print ( ' recu r s e ( { ) ) ' . forma t ( l eve l ) ) i f l e ve l : recurse ( l evel - 1 ) recu r s e , funcname : recurse print ( ' recurse ( { ) ) ' . f ormat ( l eve l ) ) i f l evel : t ra c e , funcname : un s e t t race s y s . s e t t race ( None )

Аналогичный вывод можно получить с помощью метода runfunc ( ) . Листинг 1 б.44. trace runfunc . ру _

import t race f rom t race_examp l e . recurse i mport recu r s e t racer t race . T race ( count = Fa l s e , t race=T rue ) t race r . runfun c ( recur s e , 2 ) =

Метод runfunc ( ) получает произвольные позиционные и именованные аргу­ менты , которые передаются функции объектом трассировки при ее вызове. $ python3 t race_run func . py - - - modul ename : recurse , funcname : recurse recu r s e . ру ( 1 2 ) : print ( ' recurse ( { } ) ' . forma t ( l evel ) ) recurse ( 2 ) recurs e . ру ( 1 3 ) : i f l evel : recurse . ру ( 1 4 ) : recurse ( l evel - 1 ) - - - modul ename : recurs e , funcname : r e curse recu r s e . py ( l 2 ) : print ( ' recurse ( { ) ) ' . forma t ( l eve l ) ) recurse ( l ) i f l evel : recurs e . py ( l 3 ) : recurs e . py ( l 4 ) : recurs e ( l eve l - 1 ) - - - modul ename : recurse , funcname : recurse recurs e . py ( l 2 ) : p r i nt ( ' recu rse ( { } ) ' . forma t ( l evel ) ) recu r s e ( O ) i f l e ve l : recur s e . ру ( 1 3 ) :

1072

1 6.4.6. Сохранение результатов Как и при использовании интерфейса командной строки , информация о коли­ честве вызовов функций и покрытии кода может быть записана в файл. Данные можно сохранить явным образом, используя экземпляр CoverageRe sul ts из объ­ екта Trace. Листинг 1 б.45. trace CoverageResul ts . ру _

import t race f r om t ra ce_examp l e . recurse import recurse t racer = t race . Trace ( count=True , t r a ce=Fa l s e ) t racer . runfunc ( recurse , 2 ) resul t s = t racer . re s ul t s ( ) resul t s . wri t e_resu l t s ( cove rdir= ' coverdir2 ' )

В этом примере результаты сохраняются в каталоге coverd i r2 .

$

python3 t race_C overageRe s u l t s . py

recu r s e ( 2 ) recu r s e ( l ) recu r s e ( O )

$

f i nd coverd i r 2

cove r d i r 2 coverdi r2 / t race_exampl e . recurs e . cover

Выходной файл содержит следующую информацию. # ! / us r /bin/ env python # encodi ng : u t f - 8 # # Copyr i ght ( с ) 2 0 0 8 Dou g Hel lmann Al l r i gh t s r e s e rved . # >>>>>>

11 lf n

# end_pymotw_header > > > > > > def recurse ( l eve l ) : print ( ' recu r s e ( { ) ) ' . f o rma t ( l evel ) ) 3: i f l evel : 3: recu rse ( l evel - 1 ) 2:

> > > > > > def not_ca l l ed ( ) : p r i nt ( ' Th i s func t i on i s never cal l ed . ' ) >»>>>

1.6.4. tnlee : трассмровка выn0/\Н8НМR nроrраммы

1 073

Чтобы сохранить данные о количестве вызовов функций для генерации отче­ тов, следует использовать аргументы infi le и out f i le при вызове конструктора Trace. Листинr 1 6.46. trace report . ру _

i mport t race f r om t race_exampl e . recu r s e import recu r s e t ra c e r

t race . T race ( count=T rue , tra ce=Fa l s e , out f i le= ' t ra ce_rep ort . dat ' ) t race r . runfunc ( recu r s e , 2 ) =

report_t racer = t race . T race ( count=Fa l s e , t race=Fa l s e , i n f i l e= ' t r a c e report . dat ' ) r e su l t s t r a ce r . reзu l t s ( ) resu l t s . wri t e_resul t s ( sumrnary=T rue , cove rdi r= ' / tmp ' ) =

Имя вхо1щого файла, из которого необходимо прочитать ранее сохраненные данные, задается с помощью аргумента infile, а имя выходного файла, в кото­ рый необходимо записать новые результаты после выполнения трассировки , с помощью аргумента out f i l e . В случае совпадения имен входного и выходного файлов предыдущий код обновляет файл кумулятивными данными.

-

$

pythonЗ t r a ce_report . py

recu r s e ( 2 ) recu r s e ( l ) recur s e ( O ) cov% modu l e l i nes ( path ) 7 42% t race_exampl e . recu rse ( . . . / t race examp l e / recurs e . py ) _

1 6.4. 7. Опции Конструктор Trace поддерживает несколько необязательных параметров , позволяющих управлять поведением объекта в о время вы полнения. •









count

булево значение. Включает подсчет количества вызовов каждой инструкции. По умолчанию п ринимает значен ие T rue. -

count funcs

булево значение. Включает вывод списка функций, вызыва­ емых в процессе выполнения . По умолчанию принимает значение Fa l se. -

countca l le r s булево значен ие. Включает отслеживание вызывающих и вызываемых объектов. По умолчанию принимает значение Т rue. -

igno remods последовательность. Список модулей или пакетов, которые будут игнорироваться при отслеживании покрытия кода. Значен ием по умолчанию является пустой кортеж. -

ignoredi r s - последовательность. Список каталогов, содержащих модули или пакеты, которые следует игнорировать. По умолчанию - пустой кортеж.

Гмва 16.

1 074 •



ИtfC'IPYМ8tnЫ рарабсnки

infile - имя файла, содержащего кешированные значения счетчиков. По умолчанию принимает значение None. out f i l e - имя файла, используемого для сохранения кешированных значе­ ний счетчиков. По умолчанию принимает значение None, при котором дан· ные не сохраняются.

Дополнительные ссылки •

Раздел документации стандартной библиотеки, посвященный модулю t race 12 •



Раздел 1 7.2.7. Модуль sys включает средства для добавления пользовательской трас­ сировочной функции в интерпретатор во время выполнения .



covera g e . ру (Ned Batchelder)13• Модуль для определения покрытия кода.



f i g l e a f (Titus Brown)14• Приложение для определения покрытия кода.

1 6. 5 .

traceback:

исключения и стек вызовов

Модуль t racebac k обрабатывает информацию о стеке вызовов для вывода со­ общений об ошибках. Цтсrировочная инфармация содержит данные о стеке вызо· вов, начиная с точки вызова обработчика исключения и далее до той точки в це· почке вызовов, в которой возникло исключение. Также возможен доступ к трас· сировочной информации о текущем стеке охватывающих вызовов (без контекста ошибки) . что может быть пригодиться для определения путей выполнения , веду­ щих к вызову данной функции. Высокоуровневый API модуля t racebac k сохраняет представление стека вызо­ вов в экземплярах Stac kSummary и FrameSummary. Эти объекты МОГ}Т кон а 1 с = о =

=

. . . / c g itb_l oca l_va r s . py i n func2 ( a= l , d i v i s o r= O ) 13 1 4 de f func 2 ( a , d i v i s o r ) : 15 return а / divi s o r 16 17

а 1 divi s o r О Ze roDivi s i onError : d i v i s i o n Ьу z e r o cause None class < c l a s s ' Z e roDi v i s i onE r r o r ' > None context de l a t t r

init ' o f ZeroDivi s i onError

. . . / cgi tb_wi th_c l as s e s . py i n i ni t ( s e l f=< _mai n . BrokenC l a s s ob j e ct > , a= l , Ь= О ) self . a 21 а self . b = Ь 22 23 s e l f . c = s e l f . a * sel f . b 24 # Rea l l y 25 # l ong 26 # c omment # goes 27 # here . 28 29 sel f . d s e l f . a / se l f . b 30 r e t urn 31 3 2 о = B r o kenC l a s s ( l , 0 ) s e l f = < m a i n . BrokenC l a s s ob j e ct > s e l f . d unde f i ned self . a = 1 se l f . b О Z e r o Divi s i onErro r : divi s i o n Ь у zero =

rмва 16. Инструменты разрабсmоt

1 090

cause None < c l a s s ' ZeroDivi s i onError ' > class c ontext None _de l a t t r_

= { } dict di r o f Z e roDivi s i onError

' S econd argument t o а divi s i o n or modu l o operat i o n doc w a s z e ro . ' _e q_

f o rmat = _ge_ = _get a t t ribute_

ha sh ' o f ZeroDi v i s i onError

_s i z e o f_

_s t r_

sub c l a s s h o o k

_suppres s_c ontext_ Fa l s e traceback < t raceback obj e c t > None wea k r e f args ( ' Normal me s s age ' , ) bad value 99 w i t h t r aceback

=

=

=

= =

=

=

=

=

=

:::::::

=

=

=

=

=

=

=

=

=

The above is а de s c r i p t i o n o f an e r r o r i n а Python p r o g r am . Here i s the o r i g i n a l t r a cebac k : T raceback (mo s t recent c a l l l a s t ) :

16.6. � ПQАРо6ные ONnW о нео6ребоtанных ИСIWОЧIНИМ

1 003

Fi l e " cg i t b_excep t i on_p rope r t i e s . p y" , l i n e 2 3 , i n r a i s e MyExcept i on ( ' No rma l me s s a g e ' , bad_va lue=9 9 ) MyExcept i on : Norma l me s s a g e

1 6.6.S. Вывод в формате HTM L Поскольку модуль cgitb первоначально разрабатывался для обработки исклю­ чений в веб-приложсниях, обсуждение будет пеполпым, если не сказать хотя бы несколько слов о его первоначальном формате вывода HTML. Во всех предыду­ щих примерах вывод представлял собой простой текст. Для по.лучения вывода в формате HTMI. следует опустить аргумент format (или указать для него значение "h trnl " ) . Большинство современных всб-приложений создается с помощью како­ го-либо фреймворка, который включает средства вывода сообщений об ошибках, поэтому использование НТМL-формы вывода в значительной степени устарело.

1 6.6.6. Запись трассировочной информации в журнал Во многих ситуациях вывод подробной трас •

.

.

/ c g i t b_log_except i on . py i n func ( a= l , di v i s or= O ) 24 2 5 def func ( a , divi s o r ) : return а / divi s o r 26 27 2 8 func ( l , 0 ) а = 1 divi s o r О ZeroDivi s i onError : divi s i on Ьу z ero None cause class < c l a s s ' Z eroDivi s i on E r ro r ' > context None _de l a t t r_

dict { } dir

doc ' S econd a r gument to а d i v i s i o n or modu l o ope r a t i o n w a s z e ro . ' _eq_

fo rma t_

ge ge = g e t a t t r ibut e

_gt_ = _ha s h_

ini t =

lt

�пе_ �new_

reduce

�repr_

�s etat t r_ = setstate

=

subc l a s sho ok

suppre s s_contex t_ Fa l s e t ra ceba c k = < t raceba c k obj e c t > a r g s = ( ' d i v i s i o n Ьу z e ro ' , ) wi th t r a ceba c k

=

-

-

=

=

=

=

=

=

=

The above i s а des c r i p t i o n o f an error i n а Python prog ram . H e re i s t h e o r i g i n a l t ra ceba c k : T r aceba c k ( mo s t recent ca l l l a s t ) : Fi l e " c gi t b_l og_except i on . py " , l i n e 2 8 , i n func ( l , 0 ) Fi l e " cg i t b l o g except i on . py" , l i ne 2 6 , i n func return а 7 di v i s or Ze roDivi s i onErro r : divi s i on Ьу z e r o Дополнительн ые ссылки •

Раздел документации стандартной библиотеки, посвященный модулю cgitb 16 •



t ra ceba c k (раздел 1 6.5). Модуль стандартной библиотеки, предназначенный для ра­ боты с трассировочной информацией.



i nspect (раздел 1 8.4). Модуль inspect включает дополнительн ые функции, предна­ значенные для изучения содержимого стека.

16

https : / / docs . python . o rg / 3 . 5 / l ibra r y / cg i t b . html

1 098 •



s ys (раздел 1 7.2). Модуль sys предоставляет доступ к значению текущего исключения и обработчику, который вызывается при возбуждении данного исключения. Улучшенный модуль t ra ceba ck1 7 • Обсуждение в списке рассылки для разработчиков на языке Pythoп, касающееся усовершенствования модуля t ra ceback и родственных улучшений, локально используемых другими разработчиками.

1 6. 7.

рdЬ:

интерактивный отл адчик

Модуль рdЬ реализует интерактивную среду отладки для программ на языке Руtlюп. Он поддерживает приостановку выполнения программы, просмотр зна­ чений переменных и выполнение программы в пошаговом режиме, тем самым предоставляя возможность разобраться в том , что именно делает код, и найти ошибки в логике работы программы.

1 6. 7 . 1 . Запуск отладчика Первое, что необходимо сделать, прежде чем приступить к работе с модулем pdb, - это за(·тавить интерпретатор в нужный момент перейти в режим отладки. В зависимости от условий запуска отладчика и объекта отладки это можно сделать несколькими способами. 1 6 .7 . 1 . 1 . Вызов отладчика из командной строки

Самым непосредственным способом использования отладчика является его вызов из командной строки с указанием программы, подлежащей выполнению. Листинг 1 6 .63. pdЬ_scr:lpt . py 1 # ! /usr/bin /env pythonЗ 2 # encoding : ut f - 8 з # 4 # Copy r i ght ( с ) 2 0 1 0 Doug He l lmann . Al l r i ghts reserve d . 5 # 6 7 8 c l a s s MyObj : 9 init ( se l f , nurn l oops ) : de f 10 s e l f . count num_l o ops 11 12 13 de f go ( s e l f ) : for i i n range ( s e l f . count ) : 14 print ( i ) 15 return 16 17 rnai n . . 18 if name MyObj {S ) . go ( ) 19

Запущенный из командной строки отладчик загружает указанный файл с ис­ ходным кодом и прекращает выполнение, как только встречается первая инструк17

https : / / l i s t s . gt . net /python /dev / 8 02 8 7 0

1087

16.7. pdb: Иtrrер8К1118НЫЙ ОТ118ДЧМК

ция. В данном случае он останавливается перед обработкой определения класса MyObj на строке 8.

$ pythonЗ -m pdb pdb_s cript . py > . . . /pdb_s cript . py ( B ) ( ) - > c l a s s MyOb j ( obj ect ) : ( РdЬ ) Примечан ие

Обычно команда pdb включает полный путь к каждому модулю при выводе имени файла. С целью упрощения примеров, приведенных в этом разделе, пути в выводе заменяются многоточиями ( . . . ) .

1 6. 7 . 1 . 2 . Вызов отладчика из интерпретатора

В процессе разработки ранних версий модулей многие разработчики исполь­ зуют интерактивную оболочку интерпретатора, обеспечивающую более удобный итеративный способ выполнения команд, не требующий применения циклов типа "сохранить/ выполнить/повторить", которые приходится исполь:ювать в автономных сценариях. Для вызова отладчика из интерактивного интерпретато­ ра следует использовать функцию run ( ) или r uneva l ( ) .

$ python З Python З . 5 . 1 ( v3 . 5 . 1 : 3 7 a 07 cee 5 9 6 9 , Dec 5 2 0 1 5 , 2 1 : 1 2 : 4 4 ) [ GCC 4 . 2 . 1 ( Appl e Inc . bui ld 5 6 6 6 ) ( dot 3 ) ] on da rwin Туре " help " , " c opyri ght " , " credi t s " or " l i cens e " for more i n forma t i o n . >>> import pdb_s cr ipt »> import pdb >>> pdb . run ( ' p db_s cript . MyObj ( 5 ) . go ( ) ' ) > < s t ring> ( l ) ( ) ( Pdb )

Аргументом функции run ( ) служит строковое выражение, которое может быть вычислено интерпретатором Pythoп. Отладчик анализирует его, а затем приостанавливает выполнение непосредственно перед вычислением первого вы­ ражения. Для навигации по коду и управления его выполнением можно использо­ вать описанные здесь инструкции. 1 6.7. 1 .3. Вызов отладчика из программы

В обоих предыдущих примерах отладчик запускался в начале программы . Однако в случае длительно выполняющихся процессов, когда проблема может п роявиться на поздних стадиях выполнения, гораздо удобнее запускать отладчик из программы с помощью функции set _ t race ( ) . Листинг 1 6.64. pdЬ_ set_trace . py 1 # ! /us r / bin/env pythonЗ 2 # e ncoding : utf-8

1 088 # # Copyri ght ( с ) 2 0 1 0 Doug Hel lmann . Al l r i ght s rese rved . #

3 4 5 6 7

import pdb

в

9 1 0 c l a s s MyOb j : 11 12 def �i n i t� ( s e l f , num_loops ) : 13 se l f . count num_l oops 14 15 def go ( se l f ) : 16 for i i n range ( s e l f . count ) : 17 pdb . set_ trace ( ) 18 print ( i ) 19 return 20 21 if пате ma i n 1 : 22 MyObj (S ) . g o ( J

В этом сценарии отладчик запускается в строке 1 7, приостанавливая выполне­ ние прш·раммы на строке 1 8. $ pythonЗ . /pdb_set_t race . py > . . /pdb_set_t race . py ( l 8 ) go ( ) - > print ( i ) .

( РdЬ )

Функция s e t_trace ( ) - обычная функция Руtlюп , которая может быть вы­ звана в любой точке программы. Отсюда следует, что вход в отладчик может осу­ ществляться на основании некоторых условий, в том числе посредством обработ­ чика исключений или определенной ветви условной инструкции. 1 6. 7. 1 .4. Поставарийный вызов отладчика

Отладка программы после ее аварийного завершения называется поставарий­ най. Модуль pdb поддерживает поставарийную отладку посредством функций

pm ( ) и po s t_mortem ( ) .

Листинг 1 6 .65. pdЬ_po s t_mortem . py 1 2 3 4 5 6

# ! /usr/bin/env python З # encodi ng : u t f - 8 # # Copyri ght [ с ) 2 0 1 0 Doug Hel lrnann . A l l r i ghts reserved . #

7 В c l a s s MyOb j :

9 10 11

de f �i n i t� ( s e l f , num_loops ) : s e l f . count num_l oops =

16.7. pdb: Иtnep8К'IМ8Нblti OINIДlfИK

12 13 14 15 16

10Н

def go ( s e l f ) : for i i n range ( s e l f . num_l oops ) : pri nt ( i ) return

В этом примере попытка испольаовать некорректное имя атрибута в стро­ ке 1 4 приводит к воабуждению исключения At t r ibuteError, в реаультате чего вы1ю;шение программы прекращается. Функция pm ( ) ищет активный объект t raceback, содержащий трассировочную информацию, и запускает отладчик в той точке стека вызовов, в которой воаникло исключение. $ python3 Python 3 . 5 . 1 ( v3 . 5 . 1 : 3 7 a 0 7 cee 5 9 6 9 , Dec 5 2 0 1 5 , 2 1 : 1 2 : 4 4 ) [ GCC 4 . 2 . 1 ( Apple Inc . bui l d 5 6 6 6 ) ( dot 3 ) ) on darwin Т уре " he lp " , " copyri ght " , " credit s " o r " l i cens e " for more informat i on . >>> from pdb_pos t_mortem import MyObj >>> MyObj ( 5 ) . go ( ) Tracebac k ( mo s t recent c a l l l a s t ) : Fi l e " < s tdin> " , l i ne 1 , i n Fi l e " . . . /pdb_pos t_mortem . py" , l i ne 1 4 , i n go for i i n range ( se l f . num_l oops ) : At t ribut eError : ' MyObj ' obj e ct has по a t t r i bute ' num_loops ' »> import рdЬ >» pdb . pm ( ) > . . . /pdЬ /pdЬ_post_mortem . py ( l 4 ) go ( ) - > for i i n range ( se l f . num_l oops ) : ( РdЬ )

1 6.7.2. Управление отладчиком Интерфейс для работы с отладчиком - это яаык команд, позволяющих переме­ щат1.ся по стеку вызовов, исследовать и изменять значения переменных, а также выполнять программу под управлением отладчика. Интерактивный отладчик ис­ пользует модуль readl ine (см. раздел 1 4.3) для получения команд и поддерживает завершение команд, имен файлов и функций с помощью клавиши . . . /pdb_set =t ra ce . py ( 1 8 ) go ( ) - > print ( i ) ( Pdb ) where . . . /pdb_set_trace . py ( 2 2 ) ( )

ГА888 16. Инструменn.� рара6сmсм

1 1 00 - > My0bj { 5 ) . go ( ) /pdb_set_trace . py ( 1 8 ) go { ) > -> print ( i ) { Pdb ) •



.

Чтобы добавить контекст, окружающий текущую позицию в коде, следует ис­ пользовать команду l i s t ( 1 ) . { Pdb ) 1 13

se l f . count

14

de f go ( sel f ) : for i i n range ( se l f . count ) : pdb . set _ t ra ce ( ) print { i ) return

15 16 17

18

->

19 20 21 22

if

[ EOF) { Pdb )

num_l oops

name ma in MyObj ( 5 ) . go ( )

-

-

-

-

':

По умолчанию выводятся 1 1 строк исходного программного кода (текущая строка и по 5 строк до и после нее). Команда l i s t с одним числовым аргументом задает вывод 1 1 строк программно1·0 кода, начиная с указанной строки. { Pdb l l i st 1 4 9 10 11 12 13 14

c l a s s MyObj : de f �init� ( se l f , num_l oops ) : se l f . count num_l oops

15 16

17 18 19

->

de f go ( sel f ) : for i i n range ( sel f . count ) : pdb . set_t ra c e ( ) print { i ) return

В случае предоставления команде l i s t двух аргументов они интерпретируют­ ся соответственно как первая и последняя строка диапазона строк, включаемых в вывод. { Pdb ) l i st 7 , 1 9 import pdb 7 8 9 10 11

12

13 14

15

c l a s s MyOb j : de f �i n i t� ( se l f , num_l oops ) : s el f . count num_l oops =

de f go ( se l f ) :

16.7. pdb: ИнтеракtИВНЫЙ ОWIАЧИК

16 17 18 19

1 1 01

i i n range ( s e l f . count ) : pdb . set_trace ( ) print ( i ) return

for

->

Команда longl i s t ( 1 1 ) выводит исходный код текущей функции или фрейма без указания номеров строк. Ее название "loнglist" ("длинный список") объясняет­ ся тем, что размер ее вывода может значительно превышать тот, который пред­ усмотрен по умолчанию для команды l i s t . ( РdЬ ) l ong l i s t 15 de f go ( s el f ) : 16 fo r i i n range ( s e l f . count ) : 17 pdb . set_trace ( ) 18 -> print ( i ) 19 return

Команда source загружает и выводит на экран весь исходный программный код для произвольного класса, функции или модуля. ( Pdb ) s ource MyObj c l a s s MyObj : 10 11 12 d e f �ini t� ( s e l f , num_l oops ) : 13 s e l f . count num_l oops 14 15 def go ( s el f ) : 16 for i in range ( s e l f . count ) : 17 рdЬ . s e t_ trace ( ) 18 print ( i ) 19 re turn

Для перемещения между фреймами в пределах текущего стека вызовов исполь­ зуются коман;�ы up и down. Кома1ща up (сокращенно - u ) выполняет перемеще­ ние по стеку в направлении к более ранним фреймам, команда down ( d ) - к более поздним фреймам. При каждом перемещении вверх или вниз по стеку отладчик выводит текущий фрейм в том же формате, что и при выводе с помощью коман­ ды whe re. ( Pdb ) up > . . . /pdb_s et t r a ce . py ( 2 2 ) ( ) - > MyObj ( 5 ) . go ( ) ( РdЬ ) down > . . . /pdb_s et_t r ace . py ( 1 8 ) go ( ) - > print ( i )

Числовой аргумент, передаваемый любой из команд up и down, указывает, на какое количество уровней следует переместиться в соответствующем направле­ нии за один раз.

1 1 02 1 6.7.2.2. Исследование переменных в стеке

Каждый фрейм в стеке поддерживает набор переменных, включая как локаль­ ные переменные выполняемой функции, так и глобальную информацию о состо· янии. Модуль рdЬ предоставляет несколько способов исследования содержимого этих переменных. Листинг 1 6.66. pdЬ_funation_argwnents . ру 1

# ! /usr/bin /env pythonЗ # encodi ng : u t f - 8 # # Copyr i gh t ( с ) 2 0 1 0 Doug Hel lmann . #

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

All r i ght s reserved .

import pdb d e f recu r s i ve- funct ion ( n� S , output= ' t o Ь е printed ' ) : if n > О: recurs i ve_func t i on ( n - 1 ) else : pdb . set trace ( ) print ( output ) return _

if

== ' mai n name recurs i ve_func t i on ( ) -

-

1 .

Команда args (сокращено - а) вы1юдит все ар1'}'Менты функции , активные в текущем фрейме. Кроме того, чтобы продемонсчшровать, как выглядит более глубокий стек при выводе его содержимого, в этом примере используется рекур­ сивная функция.

$ pythonЗ pdb funct i on a rguments . py > /pdb_func t i on_arg�ent s . py ( 1 5 ) recu r s i ve_funct i on ( ) -> print ( output ) ( РdЬ ) where /pdb func t ion a rgument s . py ( 1 9 ) ( ) -> recursive function ( ) . . . /pdb funct i on a rgument s . py ( 1 2 ) recurs ive - funct i on ( ) - > recursive function ( n - 1 ) . . . /pdb func t i on a rgument s . py ( 1 2 ) recurs i ve- function ( ) -> recursive_function ( n - 1 ) /pdb funct i on argument s . py ( 1 2 ) recurs i ve function ( ) - > recursive function ( n - 1 ) . . . /pdb funct i on a rgument s . py ( 1 2 ) recur s i ve - funct ion ( ) - > recursive function ( n - 1 ) /pdb_funct i on_a rguments . py ( 1 2 ) recu r s i ve_funct i on ( ) -> recur s i ve function ( n - 1 ) > . . . /pdb func t i on-a rgument s . py ( l 5 ) re cu r s i ve funct ion ( ) - > print ( output ) ( Pdb l a rgs •



.

.





.







.



-

16.7. pdb: Мнtер8КIМ8НЫА OWIAЧMK

1 1 03

п

= о output

t o Ье pri nted

( РdЬ ) up

> . . . /pdb_funct i o n_a rgument s . py ( l 2 ) recur s i ve_funct i on ( ) - > recu r s i ve_func t i o n ( n - 1 )

( Pdb ) a r g s 1 output = t o Ье printed

п

=

Команда р вычисляет выражение, предоставленное в качестве аргумента, и выводит результат. Доступна также функция print ( ) , но вместо того чтобы вы­ полняться в качестве команды отладчика, опа передается для выполнения интер­ претатору. ( РdЬ ) р 1

п

( Pdb ) print ( n ) 1

Аналогичным образом, если выражению предшествует символ ! , оно переда­ ется для вычисления интерпретатору Python. Это можно использовать для вычис­ ления произволы1ых выражений Pytl1011, включая изменение переменных. В сле­ дующем примере, перед тем как позволить отладчику продолжить выполнение программы, изменяется значение переменной output. Инструкция, которая сле­ дует за вызовом s e t_t race ( ) , выводит на печать переменную output , отображая ее измененное значение. $ pythonЗ pdb_funct i on_a rgument s . py > . . . /pdb_funct ion_a rgument s . py ( l 4 ) recursi ve_funct ion ( ) - > print ( output )

( Pdb ) ! output ' t o Ье printed ' ( РdЬ )

! output= ' changed va lue '

( Pdb ) cont i nue changed value

В случае более сложных значений, таких как вложенные или крупные струк­ туры данных, для их вывода следует использовать команду "красивой" печати рр. В следующей программе выполняется чтение нескольких строк из файла. Листинг 1 6.67. рс:IЬ_рр . ру 1 # ! /u s r / Ьi n / env pythonЗ 2 # encoding : ut f- 8 3 # 4 # Copyri ght ( с ) 2 0 1 0 Doug He l lmann . Al l r i g h t s reserved .

1 1 04

ГАава 16. Инсrрументы раработки

5 lt 6 7 import pdb

в 9

10 11

with open ( ' lorem . txt ' , ' rt ' ) a s f : l ines = f . readl ines ( )

1 2 pdb . s et_t race ( )

Вывод переменной lines, получаемый с помощью команды р, трудно подда· ется чтению из-за того, что переход текста на следующую строку может осущест­ вляться неподходящим образом. Команда рр использует модуль pprint (см. раз· дел 2. 10), форматирую щий значения для получения более аккуратного вывода. $ pythonЗ pdb_pp . py > . . . /pdb_pp . py ( l 2 ) ( ) - >None - > pdb . s et_t race ( ) ( Pdb ) р l i ne s [ ' Lo rem ipsum d o l o r s i t amet , cons ectetuer adip i s c i ng e l i t . \n ' , ' Donec egest a s , enim et conse c t e t uer u l l amcorpe r , l e c t us \ n ' , ' l i gu l a rut rum l eo , а e l ementum e l i t tortor eu quam . \n ' ] ( Pdb ) рр l i nes [ ' Lorem ipsum dolor sit ame t , consectetuer adipi s c i ng e l i t . \n ' , ' Donec ege s t a s , enim et consect etuer u l l amcorper , lectus \ n ' , ' l i gu l a rut rum l e o , а e l ementum e l i t t o r t o r eu quam . \ n ' ] ( РdЬ )

Если требуется интерактивно исследовать содержимое переменных и выпол· нять эксперименты, можно выйти из отладчика и перейти в стандартный инте­ рактивный режим Руtlюп, в котором 1:лобальные переменные, а также локал1,ные переменные из текущего фрейма уже бу�ут заполнены значениями . $ pythonЗ -m pdb pdb i nteract . py > . . . /pdb_i nteract . py ( 7 ) ( ) - > import pdb ( Pdb ) break 1 4 Breakpoint 1 at . . . /pdb_i ntera c t . py : l 4 ( РdЬ ) cont i nue > . . . /pdЬ_i nte ract . py ( l 4 ) f ( ) - > print ( 1 , m, n ) ( РdЬ ) р 1 [ 'а,, 'Ь' ] ( РdЬ ) р m

9

( РdЬ ) р n 5

16.7. рdЬ: Mнrep&IКIN8НlllA О1118АЧИК

1 1 05

( Pdb ) interact * i nteract ive * »> 1 [ ' а, , 'Ь' ] >>> m 9

>>> n

5

Изменяемые объекты, такие как списки, могут быть изменены в интерактив­ ном интерпретаторе. В отличие от этого неизменяемые объекты нельая изме­ нить, а имена нельзя повторно связать с новыми значениями. » > 1 . append ( ' с ' ) > > > m += 7 »> n = 3 >» 1 [ 'а' , 'Ь' ,

'с' ]

>>> m 16 >>> n

3

Для выхода из режима интерактивного интерпретатора и возврата в отлад­ •шк следует ввести символ конца файла с помощью комбинации клавиш . В данном примере список 1 был изменен , однако значения переменных m и n остались прежними. >>> " D

( РdЬ ) р 1 'Ь' , ,с , ]

[ ,а, ,

( РdЬ )

р

m

( Pdb ) 5

р

n

9

( Pdb )

1 6. 7 2 3 Пошаговое выполнение п рограммы .

.

.

Кроме перемещен ия вверх и вн из по стеку вызовов в то время, когда выполне­ ние программы приостановлено, существует возможность пошагового выполне­ ния программы с той точки , в которой был выполнен вход в отладчик.

1 1 08 Листинг 1 6.68. pdЬ_s tep . py

1 # ! /usr /bin/env pythonЗ 2 # encodi ng : ut f-B з # 4 # Copyr i ght ( с ) 2 0 1 0 Doug Hel lrnann . Al l r i gh t s re s erved . 5 # 6 7 import pdb в 9

1 0 de f f ( n ) : 11 for i i n r ange ( n ) : 12 i * n j 13 print ( i , j ) 14 ret urn 15 16 if narne mai n 17 pdb . s et_t race ( ) 18 f (5) =

==

'

1 .

Команда s tep (сокра щенная версия - s ) выполняет текущую строку исходного кода и приостанавливает программу в следующей то ч ке выполнения , будь то пер­ вая инструкция в вызываемой функции или следующая строка текущей функции. $ pythonЗ pdb_ s t ep . py > . . . /pdb_step . py ( l B ) ( ) -> f ( 5 )

После каждого вызова функции s e t_ trace ( ) интерпретатор приостанавлива­ ет выполнение и передает управление отладчику. Результатом выполнения пер­ вой команды s t ep является вход в функцию f ( ) . ( Pdb l step --Cal l - > . . . /pdb_s tep . py ( l O ) f ( ) - > de f f ( n ) :

Следующая команда s tep перемещает то ч ку выполнения в первую строку f ( ) и запускает цикл. ( Pdb ) s t ep > . . . /pdb_step . py ( 1 1 ) f ( ) - > for i i n range ( n ) :

Повторное вьшолпение команды s t ep перемещает точку выполнения в пер­ nую строку внутри цикла, н которо й определяется переменная j . ( Pdb ) s t ep > . . . /pdЬ_step . py ( 1 2 ) f ( ) -> j = i * n

1 1 07 ( РdЬ ) р i

о

Значение переменной i равно О , поэтому после выполнения еще одного шага значение j должно быть равным О . ( РdЬ ) s t ep > . . . /pdb_ s t ep . py ( 1 3 ) f ( ) -> p r i nt ( i , j ) ( Pdb ) р j

о

( Pdb )

Выполнение программы по одной строке за раз описанным способом может стать утомительным, если точ ке программы, в которой происходит ошибка, пред­ шествует большое количество строк кода или одна и та же функция вызывается многократно. Листинг 1 6.69. pdЬ_next . py 1 2 3 4 5 6 7 в

# ! /us r / bi n / env python3 # encoding : u t f - B

# # Copyr i ght ( с ) 2 0 1 0 Doug Hel lmann . Al l rights reserved . # import pdb

9 1 0 def c a l c ( i , n ) : j = i * n 11 12 return j 13 14 1 5 def f ( n ) : 16 f o r i i n range ( n ) : 17 j calc ( i , n ) 18 print ( i , j ) 19 r e turn 20 2 1 if name ma i n 22 pdb . s e t_trace ( ) 23 f (5) =

==

'

'

·

В этом примере в функции calc ( ) ничего не может с.лучиться. Таким образом, ее пошаговое выполнение в цикле функции f ( ) затруднило бы чтение полезного вывода, поскольку в этом случае все строки функции c a l c ( ) отображались бы по мере их выполнения. $

python3 pdb_next . py

> . . . /pdb_next . py ( 2 3 ) ( )

ГАВва 16. ИнструмеН1Ъ1 разрабсnкм

1 1 08 -> f ( 5 ) ( Pdb ) s t e p - - C a l l -> . . . / pdb_next . py ( 1 5 ) f ( ) - > de f f ( n ) : ( Pdb ) s t e p > . . . /pdb_ne xt . py ( l б ) f ( ) -> f o r i i n r ange ( n ) : ( Pdb ) s t ep > . . . /pdb_next . p y ( 1 7 ) f ( ) calc ( i , n ) -> j =

( Pdb ) s t ep - - Ca l l -> . . . / pdb_ne xt . py ( l O ) c a l c ( ) -> de f c a l c ( i , n ) : ( Pdb ) s t e p > . . . /pdb_ ne xt . р у ( 1 1 ) c a l c ( ) i * n -> j =

( Pdb ) s t ep > . . . /pdb_next . py ( l 2 ) ca l c ( ) - > return j ( Pdb ) s t e p --Return-> . . . /pdb_next . py ( l 2 ) c a l c ( ) - > 0 -> return j ( Pdb ) s t ep > . . . /pdb_next . py ( l B ) f ( ) -> p r i n t ( i , j ) ( Pdb ) s t e p

о о

> . . . /pdb ne xt . p y ( l б ) f ( ) -> for i in r ange ( n ) : ( Pdb )

Команда next (сокраще н ная верси я - n ) подобна команде s tep, но не входит 11 тело функций, вы:1ыnаемых из выполняемых инструкци й . В конечном счете о на вьшолняет вес1, вызов функции до следующей инструкции в текущей функции как одну оп е рацию. > . . . /pdb_next . p y ( l б ) f ( ) -> for i i n range ( n ) : ( Pdb ) s t ep > . . . /pdb_next . p y ( l 7 ) f ( ) calc ( i , n ) -> j =

16.7. pdb: мtnepaкntвныii �чик

1 1 09

( Pdb ) next > . . . /pdb_next . py ( l B ) f ( ) - > p r i nt ( i , j ) ( РdЬ )

Команда unt i l аналогична команде next , за исключ ением того, что она про­ должает выполнение программы до тех 1юр, пока ноток выполнения не покинет текущий фрейм стека или не будет дости�·нуrа строка, помер ко·1·орой превышает номер текущей строки. Это, например, означает, что команду unt i l можно ис­ пользовать для выполнения всех команд 1�икла и приостановки выполнения на следующей строке. $ pythonЗ pdb_next . py > . . . /pdb_next . p y ( 2 3 ) ( ) -> f ( 5 ) ( РdЬ ) s t ep --Call-> . . . /pdb_next . py ( 1 5 ) f ( ) - > def f ( n ) : ( Pdb ) s t ep > . . . /pdb_next . py ( l б ) f ( ) - > f o r i i n range ( n ) : ( Pdb ) s t ep > . . . /pdb_ne x t . p y ( l 7 ) f ( ) calc ( i , n ) -> j =

( Pdb ) next > . . . / pdb_next . py ( l B ) f ( ) - > print ( i , j ) ( РdЬ ) unt i l

о о

1 5 2 10 3 15 4 20 > . . . /pdb_ne x t . p y ( l 9 ) f ( ) - > return

( РdЬ )

До выполнения команды unt i l текущей строкой бьvю строка 1 8, последняя строка цикла. П осле выполнения команды un t i l точка выполнения 1�аходи·гся в строке 19, когда цикл уже завершился. Чтобы позволить программе выполняться до тех пор, пока не будет достигнута определенная строка, следует передать номер строки команде unt i l . В отли ч ие от задания точки останова, номер строки, передаваемый команде unt i l , должен превышать текущий номер строки, поэтому данная команда оказывается наибо­ лее полезной для пропуска длинных блоков кода в пределах функции.

1 1 10

$

python3 pdb next . p y /pdЬ_next . py ( 2 3 ) ( ) > -> f ( 5 ) ( РdЬ ) l i s t pri nt ( i , j ) lB 19 return 20 if name 21 ' mai n pdЬ . s et_trace ( ) 22 f (5) 2 3 -> •

.

.

==

1 :

[ EOF]

( Pdb ) unt i l l B * * * " unt i l " l i ne numЬer i s sma l l e r than current l i ne numЬer ( Pdb ) step -- C a l l - > . . . /pdb_next . py ( 1 5 ) f ( ) -> def f ( n ) : ( Pdb ) step > . . . / pdЬ_next . ру ( 1 6 ) f ( ) - > for i i n range ( n ) : ( Pdb ) l i s t 11 j i * n return j 12 13 14 1 5 def f ( n ) : f o r i i n range ( n ) : 1 6 -> 17 j calc ( i , n ) lB print ( i , j ) 19 return 20 n ame ma i n 21 if ( Pdb ) unt i l 1 9 =

=

1 .

о о

1 5 2 10 3 15 4 20 > . . . /pdb_next . py ( 1 9 ) f ( ) -> re turn ( РdЬ )

Команда return - еще один полезный способ быстрого прохождения части функ ц ии. Она продолжает выполнение до тех пор, пока функция не будет готова выполнить инструкцию return. В этот момент выполнение программы приост-а· навливается, предоставляя возможность проверить возвра щ аемое значение, пре­ жде чем будет выполнен возврат из функции.

16.7. pdb: мнrеракrмвнwА �чик

1111

$ pythonЗ pdb_next . p y > . . . /pdb_next . py ( 2 3 ) ( ) -> f ( 5 ) ( РdЬ ) s t ep --Cal l-> . . . /pdb_next . py ( 1 5 ) f ( ) - > def f ( n ) : ( РdЬ ) s t ep > . . . /pdb_next . py ( l б ) f ( ) - > f o r i i n range ( n ) : ( РdЬ ) return

о

о

1 5 2 10 3 15 4 20 - - Re t u r n - > . . . /pdb_nex t . py ( 1 9 ) f ( ) - >None - > r e t u rn ( РdЬ )

1 6. 7 . 3 . Точки останова По мере роста размеров прог рамм процесс отладки резко замедляется и ста­ новится чересчур г ромоздким даже в случ ае использования команд next и unt i l . Вместо то го чтобы управлять пошаговым выполнением программы в руч ную, лу•1ше позволить ей выполняться обычным образом до тех пор, пока она не достиг­ нет точки , в которой отладчик прервет ее выполнение. Для запуска отладчика можно использовать функцию set _t race ( ) , но такой подход сработает лишь в том случае, сели в про грамме имеется единственная точка, в которой вьшолне­ ние про г раммы должно приостановит1,ся. Гораздо удобнее выполнять программу посредством отладчика с забла говременной передачей ему информации о точ:ках останова, в которых выполнение про г раммы должно приостанавливаться . Листинг 1 6.70. pdЬ_break . py

1 # ! / u s r / b i n / env pythonЗ 2 # encoding : u t f - 8 3 # 4 # Copyright ( с ) 2 0 1 0 Doug He l lrnann . Al l ri ght s rese rved . 5 # 6 7 8 de f c a l c ( i , n ) : 9 j i * n p r int ( ' j = ' , j ) 10 11 if j > О : =

fl\888

1 1 12 12 13 14 15 1 6 de f 17 lB 19 20 21 22 if 23

16. ИНС1РУМ8tnЫ раара6сmси

p r i n t ( ' Po s i t i v e ! ' ) return j

f (n) : for i in range ( n ) : i) print ( ' i # noqa calc ( i , n ) j re turn = '

,

=

name f (S)

ma i n

1 .

Существует несколько способов задания точек остано ва для команды break (сокращенно - Ь) , в том числе с указанием номера строки, файла или функции, в которых обработка должна приостана вливаться. Чтобы задать точку останова на определенной строке текущего файла, следует использо в ать команду break но ­

мер строки. _

$ python3 -m pdb pdb_bre a k . py > . . . /pdb_bre a k . py ( B ) ( ) - > de f c a l c ( i , n ) : ( Pdb ) bre a k 1 2 Brea kpo i nt 1 a t . . . /pdb_brea k . py : 1 2 ( Pdb ) cont i nue

i j

о о

i 1 j 5 > . . . /pdЬ_bre a k . p y ( 1 2 ) c a l c ( ) - > p r i nt ( ' Pos i t i ve ! ' ) ( РdЬ )

Команда cont inue (сокращенная версия с ) информирует отладчик о необ­ ходимости возобновить выполнение программы до следующей точки останова. В дан ном примере вызов команды с приводит к выполнению первой итерации цикла for в функции f ( ) и приостановке выполнения программы в теле функции calc ( ) при прохождении второй итерации. Точки остано ва можно также устанавливать на первой строке функции, указав имя функции вместо номера строки. В следующем примере показано, что прои­ зойдет, если добавить точку останова для функции calc ( ) . -

$ python3 -m pdb pdb_bre a k . py > . . . /pdЬ_bre a k . py ( B ) ( ) -> de f c a l c ( i , n ) : ( РdЬ ) bre a k c a l c Breakpoint 1 a t . . . /pdb_bre a k . py : B

18.7. pdb: интеракnевнwii 01МАчик

1 1 13

( Pdb ) cont i nue i о > . . . / pdb_brea k . p y ( 9 ) ca l c ( ) -> j i * п =

=

( Pdb ) whe re . . . / pdb_bre a k . py ( 2 3 ) ( ) -> f ( S ) . . . / pdb_brea k . py ( l 9 ) f ( ) -> j calc ( i , n ) > . . . / pdb_brea k . py ( 9 ) c a l c ( ) -> j i * п =

=

( Pdb )

Чтобы задать точку останова в другом файле, следует предварить номер стро­ ки или имя функции префиксом в виде имени файла. Листинг 1 6.71 . pdЬ_break_remote . py 1 # ! / u s r /b i n/ env pythonЗ 2 # encod i ng : ut f - 8 з

4 from pdb_bre a k import f

5

б

f (S)

Ниже для про г раммы pdb_brea k_ rernote . р у в качестве точки ос1·а11ова задана строка 1 2 в файле рdЬ_Ьrеаk . ру. $ pythonЗ -m pdb pdЬ_break_rernote . py > . . . / pdb_bre a k_remot e . py ( 4 ) ( ) -> f rom pdb_bre a k import f ( Pdb ) bre a k pdb -b r e a k . py : l 2 Bre a kpoint 1 a t . . . /pdb_brea k . py : 1 2 ( Pdb ) cont i nue о i о j 1 i

j

5

> . . . /pdЬ_brea k . py ( 1 2 ) c a l c ( ) -> p r i n t ( ' Posi t i ve ! ' ) ( Pdb )

В качестве имени файла можно указать либо полный путь к файлу, либо отно­ сительный путь, доступный через список s y s . ра th. Для получения текущего списка заданных точек останова следует выполнить команду break без аргументов. Вывод включает имя файла и номер строки для ка­ ждой точки останова, а также информацию о том , сколько раз она встретилась.

ГАа8а 16. ИнсrрумеtпW раэра6сnкм

1 1 14

$ pythonЗ -m pdb pdЬ_brea k . py > . . . /pdb bre a k . py ( B ) ( ) - > de f cal c ( i , n ) : ( РdЬ ) bre a k 1 2 Bre a kpo i nt 1 a t . . . / pdb_brea k . py : 1 2 ( Pdb l bre a k Num Туре 1 bre a kp o i nt

Disp Enb keep ye s

Whe re a t . . . / pdb_brea k . py : 1 2

( Pdb ) cont i nue i о о i 1 =

5

> . . . / pdb_brea k . py ( 1 2 ) c a l c ( ) - > pr i nt ( ' Po s i t ive ! ' ) ( РdЬ ) cont i nue P o s i t i ve ! i 2 10 j > . . . /pdb_brea k . py ( 1 2 ) c a l c ( ) - > p r i nt ( ' Po s i t i ve ! ' ) = =

( Pdb ) bre a k

Num Т уре

1

Disp Enb Whe re b re a kpoi nt keep yes a t . . . /pdb_brea k . py : 1 2 bre a kpo i n t a l ready h i t 2 t ime s

( Pdb )

1 6. 7 .3. 1 . Управление точками останова При добавлении каждой новой точки останова ей присваивается числовой идентификатор. Эти идентификаторы используются для активизации, деакти­ визации и удаления указанных точек останова в ин те ракти в ном режиме. Отклю­ чение точки останова с помощью 1соманды di saЫe информирует отладчик о том, что выполнение программы при достижении данной строки не должно приоста­ навливаться. В этом случае точка останова запоминается , но игнорируется. $ pythonЗ -m pdb pdЬ_bre a k . py > . . . /pdb_brea k . py ( B ) ( ) - > def calc ( i , n ) : ( Pdb ) bre a k ca l c Brea kp o i n t 1 a t . . . / pdb_bre a k . py : B ( РdЬ ) bre a k 1 2 Brea kp o i n t 2 a t . . . /pdb_brea k . py : 1 2

16.7. рdЬ: интер8К1ИВНWА OINIAЧMK ( Pdb ) bre a k N um Туре 1 brea kp o i n t 2 br e a kp o i n t

1 1 15

Disp E nb keep yes keep yes

Where a t . . . /pdb_br ea k . py : 8 a t . . . /pdb_ bre a k . py : 1 2

Disp Enb keep п о keep ye s

Where a t . . . /pdb_brea k . p y : 8 a t . . . /pdb_bre a k . p y : 1 2

( Pdb ) d i s a Ы e 1 ( РdЬ ) bre a k

N um Туре

1 2

brea kpoint bre a kpoint

( РdЬ ) cont i nue i о о j i 1 5 j > . . . /pdb_brea k . py ( 1 2 ) c a l c ( ) - > p r i n t ( ' Pos i t i ve ! ' ) ( Pdb )

В при веде нном ниже сеансе отладки в про грамме устана вли в аются две то чки остано в а, одна из которых затем деактивизируется . П ро г рамма в ыпол няется , пока не в стретится оста вш аяся то ч ка остано в а , после че го другая точка остано в а акти в изируется, прежде чем про грамма возобно в ит в ыполнение .

$

pythonЗ -m pdb pdЬ_bre a k . py

> . . . /pdb_brea k . py ( 8 ) ( ) -> de f c a l c ( i , n ) : ( РdЬ ) bre a k c a l c Brea kpo i n t 1 a t . . . /pdb_brea k . py : 8 ( Pdb ) bre a k 1 8 Bre a kpo i n t 2 a t . . . /pdb_brea k . py : 1 8 ( РdЬ ) d i s aЫe 1 ( РdЬ ) cont i nue > . . . /pdb_bre a k . py ( 1 8 ) f ( ) -> print ( ' i = ' , i ) ( Pdb ) l i s t 13 14 15 de f 16 17 1 8 В-> 19 20 21

return j

f (n) : f o r i in ra nge ( n ) : p r i nt ( ' i = ' i) j = ca l c ( i , n ) # noqa return ,

Гмва 18. ИНС1JJУМ8111W рарабсmсм

1 1 18 22 23

if

name f(S)

ma i n

-

-

' :

( Pdb l c o nt i nue i = о j = о /pdb_bre a k . py ( l B ) f ( ) > -> print ( ' i = ' , i ) •



.

( Pdb ) l i s t 13 14 15 de f 16 17 1 8 В- > 19 20 21 if 22 23

re turn j

f (n) : for i i n ra nge ( n ) : print ( ' i = ' , i ) ca l c ( i , n ) # noqa j re turn =

name f(S)

ma i n

-

1 .

( Pdb ) р i 1 ( Pdb ) е nаЫе 1 EnaЫed bre a kpo i nt 1 at . . . /pdb_brea k . py : B ( Pdb ) cont i nue i 1 /pdb_brea k . py ( 9 ) ca l c ( ) > -> j i * п = .



.

=

( Pdb l l i s t # Copyri ght ( с ) 2 0 1 0 Doug He l lmann . 4 # 5 6 7 de f ca l c ( i , n ) : в в j = i * п 9 -> print ( ' j = ' , j ) 10 11 if j > О : 12 pri nt ( ' Pos i t ive ! ' ) return j 13 14

Al l r i gh t s re s e rve d .

( Pdb )

Префикс В в строках вывода, полученных с 1юмо щ 1,ю команды l i s t , указыва­ ет, где именно в программе заданы точки останова (строки 8 и 1 8) . Для 1юлнш·о сброса точки ос·1�анова следует испол ьзовать команду clear.

16.7. рdЬ: мнrервктмвнwii ot/UIAЧMK

1 1 17

$ pythonЗ -m pdb pdb_brea k . py > . . . / pdb_br e a k . py ( B ) ( ) -> d e f c a l c ( i , n ) : ( Pdb ) bre a k c a l c Brea kpoint 1 a t . . . /pdЬ_br e a k . py : B ( Pdb ) brea k 1 2 Brea kpoint 2 a t . . . /pdb_brea k . py : l 2 ( Pdb ) brea k 1 8 Brea kpoint З a t . . . /pdb_brea k . py : l B ( Pdb ) brea k Num Туре 1 breakp o i n t breakpo i n t 2 brea kpo i n t з

D i sp keep keep keep

Enb yes yes yes

Where at . . . /pdb_brea k . py : B at . . . /pdb_brea k . py : l 2 at . . . /pdb_brea k . p y : l B

( РdЬ ) c l e a r 2 De l e t ed brea kpo i n t 2 at . . . /pdb_brea k . py : l 2 ( Pdb ) bre a k Num Т уре bre a kpo i n t 1 З breakpo i n t

Disp Enb keep yes keep yes

Where a t . . . /pdb_brea k . py : B at . . . /pdb_br eak . py : l B

( РdЬ )

Оставшиеся точки останова сохраняют свои идентификаторы и не перенуме­ ровываются.

1 6. 7 3 2 Временные точки останова .

.

.

Временная точка останова, созданная с 1юмощью команды tbre a k , автомати· чески сбрасывается после того, как поток выполнения программы впервые до· стигает ее. Использование временной точки останова упрощает быстрое дости­ жение определенной точки в программе, как и в случае обычной точки останова. Однако, поскольку временная точка останова немедленно сбрасывается , она не будет создавать задержку, если данный участок программы станет выполняться повторно. $ pythonЗ

-

m

pdb pdb_brea k . py

> . . . /pdЬ_bre a k . py ( B ) ( ) -> d e f c a l c ( i , n ) : ( Pdb ) tbrea k 1 2 Brea kp o i n t 1 a t . . . /pdb_brea k . py : l 2 ( Pdb ) cont i nu e i о

j =

о

1 1 18 i

=

1

j = 5

De leted brea kpoint 1 a t . . . / pdb_brea k . py : l 2 > . . . /pdb_brea k . py ( l 2 ) c a l c ( ) - > print ( ' P o s i t ive ! ' ) ( РdЬ ) bre a k ( РdЬ ) cont i nue P o s i t ive ! i 2 j 10 P o s i t ive ! i 3 15 j P o s i t ive ! i 4 j 20 P o s i t ive ! The prog ram f i ni s hed and w i l l Ье re s t a rt e d > . . . /pdb_brea k . py ( B ) ( ) - > def c a l c ( i , n ) : =

=

= =

= =

( Pdb )

После того как программа впервые достигнет строки 12, точка останова будет сброшена. Выполнение программы не станет приостана вливаться в этой точке до полного завершения программы.

1 6.7.3.3. Условные точки останова К точкам останова можно применять правила, в соответст вии с которыми при­ остановка выполнения программы будет происходить лишь при выполнении оп­ ределенных условий. По сравнению с активизацией и деактивизацией точек оста­ нова вручную этот подход обеспечивает более гибкие возможности управления процессом отладки. Усло в ные точки останова можно устанавливать двумя спосо­ бами. Первый из них заключается в том, чтобы указать условие, при соблюдении которого в ыполнение команды break приведет к созданию точки останова. $ pythonЗ -m рdЬ pdb_bre a k . py > . . . /pdb_bre a k . py ( 8 ) ( ) - > def c a l c ( i , n ) : ( Pdb ) b r e a k 1 0 , j > O Breakp o i nt 1 at . . . /pdb_brea k . py : l O ( P db ) brea k D i s p Enb Num Туре keep yes 1 bre a kpo i n t s t op only if j > O ( Pdb ) cont i nue i о j о =

Where a t . . . /pdb_bre a k . py : l O

16.7. pdb: мнтеракrмвн"А O'INIAЧИK

1 1 19

i = 1 > . . . /pdb_b rea k . py ( l O ) ca l c ( ) -> p r i n t ( ' j = ' , j ) ( РdЬ )

Значения, которые используются в выражении аргумента, определяющего условие, должны быть видимыми в том фрейме стека, в котором определяется точка останова. Выполнение программы будет приостановлено в точке останова только в том случае, если результат вычисления выражения является истинным. Альтернативный вариант заключается в применении условия к существующей точке останова с помощью команды condi t i on. Аргументами этой команды слу­ жат идентификатор точки останова и выражение. $ pythonЗ

-m

pdb pdb_brea k . py

> . . . /pdb_bre a k . py ( B ) ( ) -> de f c a l c ( i , n ) : ( РdЬ ) bre a k 1 0 Breakp o i n t 1 a t . . . /pdb_brea k . py : l O ( Pdb ) brea k Num Т уре D i s p Enb Where 1 b r e a kp o i n t keep yes a t . . . /pdb_brea k . py : l O ( Pdb ) cond i t i on 1 j > O New condi t i on s e t f o r bre a kpoint 1 . ( РdЬ ) brea k Disp Enb Num Т уре keep yes 1 b r e a kpoint stop o n l y if j > O

Whe re a t . . . /pdb_bre a k . py : l O

( Pdb )

1 6. 7

.

3 4 Игнорирование точек останова .

.

Отладку программ, содержащих циклы или использующих большое количе­ ство рекурсивных вызовов одной и той же функции, часто можно упростить за счет заблаговременного отказа от наблюдения за каждым вызовом или каждой точкой останова и сквозного выполнения соответствующих участков кода. Это можно сделать с помощью команды ignore, сообщающей отладчику о том, что точка останова должна быть пройдена без задержки. Каждый раз, когда п рограм­ ма достигает точки останова, счетчик проходов команды ignore уменьшается на единицу. Точка останова активизируется заново, когда значение счетчика стано­ вится равным нулю. $ pyt h o n З -m рdЬ pdb_brea k . py > . . . /pdb_brea k . py ( B ) ( ) - > d e f ca l c ( i , n ) : ( Pdb ) break 1 9

ГАВва 16 . ИftC'IPYМ8Нlbl рвзребсmси

1 1 20 Breakpoint 1 a t . . . /pdb_bre a k . p y : 1 9 ( РdЬ ) cont i nue i о > . . . /pdb_break . py ( l 9 ) f ( ) -> j calc ( i , n ) # noqa =

=

( РdЫ next о j > " . /pdЬ_bre a k . py ( 1 7 ) f ( ) - > for i i n range ( n ) : =

( РdЬ ) i gnore 1 2 Wi l l i gnore next 2 c r o s s i n g s o f breakpo int 1 . ( РdЬ ) break Num Т уре Di sp Enb Whe r e 1 bre a kpo int keep ye s at . . . /pdb_bre a k . py : 1 9 i gnore next 2 hi t s bre a kpoint a l ready h i t 1 t ime ( Pdb ) cont i nue i 1 j 5 Po s i t ive ! i 2 j 10 Pos i t i ve ! i з > . . . / pdb_brea k . py ( 1 9 ) f ( ) calc ( i , n ) # noqa -> j =

=

= =

=

=

( РdЬ ) break Num Т уре D i s p Enb Whe r e 1 bre a kpoint k e e p ye s a t . . . / pdb_brea k . py : 1 9 breakpoint a l ready h i t 4 t ime s ( РdЬ )

Явный сброс значения счетчика проходов команды ignore приводит к немед· ленной реактивизации точки останова. $

pythonЗ -m pdb pdb_bre a k . py

> . . . /pdb_brea k . py ( B ) ( ) - > de f c a l c ( i , n ) : ( РdЬ ) break 1 9 Brea kpoint 1 a t . . . / pdb_brea k . p y : 1 9 ( РdЬ ) i gnore 1 2 W i l l i gnore next 2 cross ings of breakpoint 1 . ( РdЫ brea k Num Т уре

Disp Enb

Whe re

18.7. pdb: Мtn8р&К'IМ8Ны4i ОWIДЧМК 1

brea kpo i nt keep ye s i gnore next 2 h i t s

1 1 21 a t . . . /pdb_brea k . py : 1 9

( Pdb ) ignore 1 О Wi l l s top next t i me bre a kpo i n t 1 is rea ched . ( Pdb ) br e a k Num Т уре 1 brea kpo int ( Pdb )

Disp Enb keep yes

Where a t . . . /pdb_bre a k . py : 1 9

1 6.7.3.S. Запуск выполнения операций по достижении точки останова Кроме интерактивного режима модуль рdЬ поддерживает выполнение про­ стых сценариев. Команда comrnands позволяет выполнить последовательность команд интерпретатора, в том числе инструкции Pytl1011, при достижении опре­ деленной точки останова, номер которой задается в качестве аргуменТ"d. После выполнения команды comrnands отображаемое отладчиком приглашение ко вводу ( командная подсказка) заменяется приглашением ( com ) . Нужные команды сце­ нария вводятся по одной за раз и завершаются командой end, которая сохраняет сценарий и возвращается к основному при1лаше11ию отладчика. $ pythonЗ -m pdb pdb_brea k . py > . . . /pdb_brea k . py ( 8 ) ( ) - > def ca l c ( i , n ) : ( Pdb l bre a k 1 0 Brea kpo i n t 1 a t . . . /pdb_brea k . py : l O ( Pdb ) ( com ) ( com ) ( com ) ( corn)

coпunands 1 pri nt ( ' debug i = ' i) p r i n t ( ' debug j = ' , j ) print ( ' debug n = ' n) end

( Pdb ) cont i nue i = о О debug i О debug j debug n 5 > . . . /pdb_bre a k . p y ( l O ) c a l c ( ) -> p r i n t ( ' j = ' , j ) ( Pdb ) con t i nue о i = 1 debug i 1 5 debug j debug n 5 > . . . /pdb_br e a k . p y ( l O ) ca l c ( ) - > p r i nt ( ' j = ' , j )

j

=

( Pdb )

Гмва 16. Инс:трумеtm.1 рара6сmси

1 1 22

Эта возможность особенно полезна при отладке кода , содержащего большое количество структур данных или переменных. Она обес п ечивает автоматический вывод всех значений, избавляя от необходимости выводить их вруч ную каждый раз, ко гда встречается указанная точка останова.

1 6. 7 .3.6. Отслеживание изменения данных Также существует возможность следить за изменениями значений переменных в процессе выполнения про граммы без явно го исполь:ювания команд print. Для этого служит команда displ ay. $ pythonЗ -m pdb pdЬ_bre a k . py > . . . /pdb_bre a k . p y ( 8 ) ( ) -> de f c a l c ( i , n ) : ( Pdb ) bre a k 1 8 Breakp o i n t 1 a t . . . / pdb_bre a k . py : 1 8 ( РdЬ ) c o n t i nue > . . . /pdb_bre a k . p y ( 1 8 ) f ( ) -> pr i n t ( ' i = ' , i ) ( Pdb ) d i splay j d i s p l a y j : * * ra i sed Name E r r o r : name ' j ' i s not de f i ned * * ( Pdb ) next i = о > . . . /pdb_bre a k . p y ( 1 9 ) f ( ) -> j ca l c ( i , n ) # n o qa =

( РdЬ ) next j о > . . . /pdb_brea k . p y ( 1 7 ) f ( ) - > for i i n range ( n ) : display j : О [ o l d : * * rai sed Name E r r o r : name ' j ' i s not de f i ned **] =

( РdЬ )

Каж11:ый раз, ког11:а выполнение приостанавливается во фрейме стека, вычис­ ляется выражение. Если его значение изменилось, то оно выводится вместе с прежним значением. Команда di spl ay, для которой не заданы аргументы, выво­ дит с п исок отображаемых значений, активных для текущего фрейма. ( Pdb ) di s p l a y Curre n t l y di splayi ng : j: о ( Pdb ) up > . . . /pdb_bre a k . p y ( 2 3 ) ( ) -> f ( 5 ) ( Pdb ) di s p l a y

1 1 23 Curren t l y d i s p l a y i n g : ( РdЬ )

Для сброса выражения команды display нужно использовать команду undisplay. ( Pdb ) di s p l a y Currentl y d i sp l a ying : j: о ( Pdb ) undi s p l a y j ( Pdb ) di s p l a y Current l y d i s p l a y i n g : ( Pdb )

1 6. 7 .4. Изменение потока управления Команда j urnp изменяет поток управления программы без внесения измене­ ний в код. Она обеспечивает пропуск выполнения некоторых уча . . . /pdb_j ump . p y ( 8 ) ( ) - > de f f ( n ) : ( Pdb ) bre a k 1 3 Bre a kpoint 1 a t . . . /pdb_ j ump . py : l З ( Pdb ) cont i nue > . . . /pdb_j ump . p y ( 1 3 ) f ( ) -> j += n ( РdЬ ) р j о ( РdЬ ) step > . . . /pdb_j ump . py ( 1 4 ) f ( ) - > re sul t . append ( j ) ( РdЬ ) р j 5 ( Pdb ) cont i nue > . . . /pdb_j ump . py ( l З ) f ( ) - > j += n ( РdЬ ) j ump 1 4 > . . . /pdb_j ump . py ( 1 4 ) f ( ) - > re sul t . append ( j ) ( РdЬ ) р j 10 ( Pdb l di s aЫ e 1 Di saЫed b r e a kpoint 1 at . . . / pdb_j ump . py : l З ( РdЬ ) cont i nue [5, 10, 25, 45, 70] The program f i n i shed a nd wi l l Ье r e s t a rted > . . . /pdb_j ump . py ( 8 ) ( ) -> de f f ( n ) : ( РdЬ )

16.7. pdb: инrеракnсвныА �...к

1 1 25

1 6. 7 .4.2. Переходы назад

Команду j urnp можно также использовать для перемещения точки выполнения в обратном направлении, что по:�воляет повторно выполнить ранее выполнен­ ный код. В следующем примере значение j инкрементируется один лишний раз, поэтому числа в результирующей последовательности окааываются большими, чем они были бы в противном случае. $

python3 -m pdb pdb_j ump . py

> . . . /pdb_j ump . py ( 8 ) ( ) - > de f f ( n ) : ( Pdb ) brea k 1 4 Bre a kp o i n t 1 a t . . . /pdb_j ump . py : l 4 ( Pdb ) con t i nue > . . . /pdb_j ump . py ( l 4 ) f ( ) - > resu l t . append ( j ) ( Pdb ) р j 5 ( РdЬ ) j ump 1 3 > . . . /pdb_j ump . py ( l 3 ) f { ) -> j += n ( Pdb ) cont i nue > . . . /pdb_j ump . py ( 1 4 ) f ( ) -> re sul t . append ( j ) ( Pdb ) р j 10 ( Pdb ) di s aЫe 1 Di saЫed bre a kpo i n t 1 at . . . /pdb_j ump . py : l 4 ( РdЬ ) conti nue [ 1 0, 2 0 , 35, 55, 8 0 ] The program f i n i shed and wi l l Ье r e s t a r t e d > . . . /pdb_j ump . p y ( 8 ) ( ) -> de f f ( n ) : ( РdЬ )

1 6. 7 4 3 Запрещенные переходы .

.

.

Переходы в тело некоторых инструкций потока выполнения или из них опас­ ны или вызывают неопределенность. Отладчик не допускает такое поведение. Листинг 1 6.73. pdЬ_no_j wnp . py 1 # ! / u s r /bi n / env python3 2 # encodi ng : u t f - 8 3 #

1 1 28 # Copyri ght ( с ) 2 0 1 0 Doug H e l lmann . All r i ght s re s e rved . #

4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

de f f ( n ) : if n < О : r a i s e Va lueE r r o r ( ' I nva l i d n : [] resu l t о j for i i n range ( n ) : j • i * n + j j +• n resu l t . append ( j ) re turn r e s u l t

{ } ' . forma t ( n ) )

=

=

if

ma i n 1 : == ' name try : print ( f ( 5 ) ) fi na l l y : p r i n t ( ' Al wa y s p r i n t e d ' )

-

-

try : print ( f ( -5 ) ) excep t : p r i n t ( ' There wa s an e r r o r ' ) e l se : p r i nt ( ' There wa s no e r ror ' ) p r i n t ( ' La s t s t a tement ' )

Несмотря 11а то что команда j ump может быть использована для перехода внутрь функции, нормальная работа кода маловероятна, поскольку аргумент функции не определен. $ python3 -m pdb pdb_no_j ump . py /pdЬ_no_j ump . py ( 8 ) ( ) > -> de f f ( n ) : ( Pdb ) bre a k 2 2 /pdb_n o_j ump . py : 2 2 Breakpoint 1 at •



.

.



.

( Pdb ) j ump 9 > . . . /pdb_no_j ump . py ( 9 ) ( ) -> i f n < О : ( Pdb ) р n * * * NameError : name ' n ' i s not de f i ne d ( РdЬ ) a r g s ( РdЬ )

1 127

16.7. pdb: Интер8КIМ8НWЙ O'INIAЧMK

С помощью команды j ump нельзя входить в середину блока, такого как цикл for или инструкция t ry : except. $ pythonЗ -m pdb pdb_no_j ump . py > . . . /pdb_no_j ump . py ( B ) ( ) -> de f f ( n ) : ( Pdb ) break 2 2 Brea kp o i n t 1 a t . . . /pdb_n o_j ump . py : 2 2 ( РdЫ conti nue > . . . /pdb_no_j ump . p y ( 2 2 ) ( ) -> p r i n t ( f ( S ) ) ( Pdb ) j ump 2 7 * * * Jurnp f a i led : can ' t j ump i n t o t he midd l e o f

а

Ыосk

( РdЫ

Код в блоке f inal l y должен всегда выполняться , поэтому выход за пределы блока с помощью команды j ump невозможен. $ pythonЗ -m pdb pdb_no_j ump . py > . . . / pdb_no_j ump . py ( B ) ( ) - > de f f ( n ) : ( РdЫ bre a k 2 4 Brea kp o i n t 1 a t . . . / pdb_no_j ump . p y : 2 4 ( РdЫ conti nue ( 5 , 15, 30, 50, 75] > . . . /pdb_no_j ump . py ( 2 4 ) ( ) - > p r i n t ' Al ways p r i nted ' ( Pdb ) j ump 2 6 * * * Jump f a i l ed : can ' t j ump i n t o o r out o f

а

' final l y ' Ыос k

( РdЫ

Основное ограничение заключается в том, что переходы ограничены нижним фреймом стека. Порядок выполнения программы нельзя будет изменить, если контекст отладки был изменен с помощью команды up. $ python З -m pdb pdb_no_j ump . py > . . . /pdb no j ump . py ( B ) ( ) - > d e f t (n) : ( Pdb ) break 1 2 B r e a kp o i n t 1 a t . . . /pdb_no_j ump . py : l 2 -

( Pdb ) conti nue . . /pdb_n o_j ump . py ( l 2 ) f ( ) > о -> j .

=

rмва 16. ИfЮУРУМ8Н1Ы раара6сmси

1 1 28 ( РdЬ ) whe re . . . / liЬ/ p ython 3 . 5 / bdb . p y ( 4 3 1 ) run ( ) -> ехес cmd i n g l oba l s , l o c a l s < s t r i ng > ( l ) ( ) . . . /pdb_no_j ump . py ( 2 2 ) ( ) -> p r i nt ( f ( 5 ) ) > . . . / pdЬ no_j ump . p y ( l 2 ) f ( ) _ -> j о =

( Pdb ) up > . . . /pdb_no_j ump . p y ( 2 2 ) ( ) -> p r i nt ( f ( S ) ) ( РdЬ ) j ump 2 5 * * * You c a n onl y j ump wi t h i n the bottom frame ( Pdb )

1 6. 7 4 4 Перезапуск программы .

.

.

Ко гда отладчик дос т и гает конца про граммы, она автоматически перезапуска­ еТ , l i ne 1 ) ( Pdb )

1 6.7.6. Сохранение конфигурационных параметров Процесс отладки программы состоит из ряда повторяющихся этапов: выпол· нение кода, анализ результатов, исправление кода или данных и повторное вы­ полнение кода. Модуль рd.Ь позволяет уменьшить количество таких повторений и тем самым сосредоточить основное внимание на коде, а не на управлении процес­ сом отладки. Для этого в модуле pdb предусмотрены средства, обес11ечивающие чтение конфигурационной информации, сохраненной в текстовых файлах, кото­ рая интерпретируется при запуске отладчика. Сначала читается файл / . pd.Ьrc. Он устанавливает глобальные персональные настройки для всех сеансов отладки. Затем из текущего каталога читается файл . / . pdbrc, с помощью которого устанавливаются локальные параметры для кон­ кретного проекта. �

$ cat - / . pdbrc # Отобразить справку Python a l i a s ph ! he lp ( % 1 ) # Переопределенный псевдоним a l i a s rede f i ned р ' home defi n i t i on '

$ cat . pdbr c # Точки о с танова bre a k 11 # Переопределенный псевдоним a l i a s rede f i ned р ' l ocal de f i n i t i o n ' $ pythonЗ -m pdb pdЬ_ fun c t i on_argument s . py B r e a kp o i n t 1 at . . . /pdb_func t i on_a rgument s . py : l l > . . . /pdb_funct i o n_a rgumen t s . py ( 7 ) ( ) - > import рdЬ ( Pdb l a l i a s ph ! he l p ( % 1 ) rede fi ned р ' l ocal defini t i on ' =

=

( Pdb ) bre a k

1 1 32 Num Т уре bre a kpo i nt 1

Di sp Enb keep yes

Whe re at . . . /pdb_fun c t i on_a rgument s . py : l l

( РdЬ )

Любые команды конфигурирования , которые можно вводить в ответ на при­ глашение, отображаемое отладчиком, могут быть сохранены в одном из таких файлов автозапуска. Точно так же могут быть сохранены некоторые команды, управляющие процессом выполнения (например, cont inue или next) . $ c a t . pdЬr c bre a k 1 1 cont i nue list $ pythonЗ - m pdb pdЬ_fun c t i o n_a rgument s . py Breakpo i n t 1 a t . . . /pdb_func t i o n_a r gument s . py : l l 6 7 import pdb в 9 1 0 de f recur s i ve_func t i o n ( n=5 , output= ' t o Ье p r i n t ed ' ) : 1 1 В-> if n > О : 12 recu r s i ve_func t i on ( n - 1 ) else : 13 pdb . set_trace ( ) 14 pri nt ( output ) 15 16 return > . . . /pdb_ funct i on_a rgument s . py ( l l ) recursi ve_fun ct i on ( ) -> i f n > О : ( Pdb )

Особенно полезно сохранять команды run. Это позволяет задавать аргументы командной строки в файле . / . рс!Ьrс, тем самым обеспечивая их последователь­ ное использование на 11ротяжении нескольких запусков. $ cat . pdbrc run а Ь с " l ong a rgument "

$ pythonЗ -m pdb pdЬ_run . py Res t a rt i n g pdb_run . py wi t h a rgument s : а Ь с " l o ng a r gume nt " > . . . /pdb_run . p y ( 7 ) ( ) - > impo rt sys ( Pdb ) cont i nue Command- l i ne arg s : [ ' pdb_run . py ' , ' а ' , ' Ь ' , ' l ong a r gument ' ] The prog rarn f i n i shed a nd wi l l Ье r e s t a rted > . . . /pdb_run . py ( 7 ) ( ) - > i mpo r t sys ( Pdb )

'с' ,

16А proflle и P8t818: 8НМ113 �

1 1 33

Дополнительные ссылки • • • • •

Раздел документации стандартной библиотеки, посвященный модулю pdb 18 • readl i ne (раздел 1 4.3). Библиотека средств редактирования интерактивной подсказки. crnd (раздел 1 4. 5) . Создание интерактивных программ. s h l ex (раздел 1 4.6). Синтаксический анализ командной строки. Python issue 26053 1 9. Если вывод команды run не соответствует значениям, приведен­

ным в примере, прочтите это сообщение, в котором подробно проанализирована причина различий вывода рdЬ в версиях Pythoп 2. 7 и 3.5.

1 6.8.

profi le

и

ps tats :

анализ производ ительности

Модуль prof i l e предоставляет программные интерфейсы, предназначенные для сбора и анализа статистической информации о потреблении процессорного времени и других ресурсов кодом на языке Руtlюп. Примеч а ние

Приведенные в этом разделе отчеты были переформатированы для того, чтобы строки умещались по ширине страницы. Строки, заканчивающиеся символом обратной косой черты ( \), продолжаются на следующей строке.

1 6.8. 1 . Запуск профилировщика Проще всего начать работу с модулем pro f i l e , вызвав . функцию run ( ) . Эта функция получает строку инструкции в качестве аргумента и создает отчет, в ко­ тором отображается количество времени, затраченного на выполнение различ­ ных строк кода при обработке данной инструкции. Листинг 1 6.75. profile_fibonacci_raw . py i rnp o r t p r o f i l e def f i Ь ( n ) : # Код в зят на сайте l i t e r a t eprog rarns . org # h t tp : / / b i t . l y /hlOQSrn if n О: return О elif n 1: r e t ur n 1 else : return fiЬ ( n - 1 ) + fib ( n - 2 ) ==

==

de f f i b_s eq ( n ) : seq [] if n > О: se q . ex t end ( f ib_seq ( n - 1 ) ) =

18 19

h t t p s : / / do c s . python . o rg/ 3 . 5 / l i b r a r y/pdb . html h t t p : / / bugs . p ython . org / i s s ue2 6 0 5 3

Г118ва 16. ИНС1Румеtт1 pupe6cmcм

1 1 34 s e q . append ( fi b ( n ) ) return seq profi l e . run ( ' pr i n t ( f ib_s eq ( 2 0 ) ) ; p r i nt ( ) ' )

Вышеприведенная рекурсивная версия программы для вычисления последова­ тельности чисел Фибоначчи особенно хорошо подходит для демонстрации воз­ можностей модуля pro f i l e , поскольку ее производительность может быть зна­ чительно улучшена. В отчете стандартного формата сначала выводятся итоговые данные, а затем подробные сведения для каждой выполнявшейся функции. $ python3 p r o f i l e_fibona c c i _raw . py [ О , 1 , 1 , 2 , 3 , 5, 8 , 13, 2 1 , 3 4 , 55, 8 9 , 7 , 1 5 9 7 , 2 58 4 , 4 1 8 1 , 6 7 6 5 ) 5 7 3 5 9 fun c t i o n c a l l s

1 4 4 , 2 33 , 3 7 7 , 6 1 0 , 98 \

( 6 9 prirni t i ve c a l l s ) i n 0 . 2 1 9 s e co \

nds Ordered Ьу : s t anda rd narne nca l l s t o t t irne perca l l nc t i o n ) 21 О . ООО О . ООО 1 О . ООО О . ООО 20 О . ООО О . ООО 2 О . ООО О . ООО 1 О . ООО О . ООО 1 0 . 001 0 . 001 >) 1 О . ООО О . ООО b_s eq ( 2 0 ) ) ; p r i nt ( ) ) о О . ООО ) 57291/2 1 0 . 12 6 О . ООО raw . py : l l ( fi b ) 21/1 О . ООО О . ООО r aw . py : 2 2 ( fib_s e q )

cumt irne

perc a l l f i l ename : l i neno ( fu \ : O ( append ) : О ( ехе с ) : O ( ext end ) : O ( p r i nt ) : О ( s etpro f i l e ) < s t r i ng > : l ( >> >>> 1

impor t sys s ys . ps l 1

s ys . ps 2 1

>>>

Любое и;) этих приглашений можно заменить другой строкой. >>>

s ys . p s l '::: ' s ys . ps 2 · ��� ' for i in range ( З ) : print i =

=

о 1

2

В качестве приглашения можно использовать любой объект, преобразуемый в строку (посредством вызова метода _s t r_) . Листинг 1 7 . 1 5. sys _ps 1 . ру

import s ys c l a s s LineCount er : de f

init ( se l f ) : se l f . count = О

de f

str ( self ) : s e l f . count += 1 return ' ( { : Зd ) ) > ' . forma t ( s e l f . count )

Объект LineCount er отслеживает, сколько раз использовалось приглашение, поэтому номер прю'1ашения каждый раз увеличивается на единицу. $ python Python 3 . 4 . 2 ( v3 . 4 . 2 : ab 2 c 0 2 3 a 9 4 3 2 , Oct 5 2 0 1 4 , 2 0 : 4 2 : 2 2 )

17.2 . sys: наС1)Юйка конфиrурацмонных параметров, сnецмфичеСIСМх A!tR СМС1'8МЫ

1 1 75

[ GCC 4 . 2 . 1 ( Appl e Inc . bui ld 5 6 6 6 ) ( dot 3 ) ] on darwi n Т уре " he l p " , " copyr i ght " , " credi t s " or " l i cense " for more i nforma t i on . >>> f rom s ys_ps l import Li neCounter >>> import s ys >>> s ys . ps l L i neCounte r ( ) ( 1)> ( 2)> ( 3)> =

1 7 . 2 . 1 . 6. Перехват вывода

Функция sys . di sp layhook ( ) вызывается интерактивным интерпретатором всякий раз, когда пользователь вводит выражение. Результат вычисления выра­ жения передастся этой функции в качестве единственного аргумента. Листинг 1 7 . 1 6. sys di splayhook . ру _

import s ys c l a s s Expres s i onCounter : de f

init ( s el f ) : s el f . count О sel f . previous_va l ue =

de f

=

sel f

c a l l ( s e l f , value ) : print ( ) print ( ' Previ ous : ' , se l f . prev i ous_va l ue ) print ( ' New : ' , va lue ) print ( ) i f value ! = se l f . previous_va lue : sel f . count += 1 s ys . ps l = ' ( { : 3d ) ) > ' . format ( se l f . count ) se l f . previ ou s_va lue = va lue s ys . �d i sp l a yhook� ( va l ue )

print ( ' i n s t a l l ing ' ) s ys . d i s p l a yhook Expres s ionCount e r ( ) =

Зна•1ение по умолчанию ( сохраненное в sys . _di sp layhoo k_) вы водится в качестве результата в стандартный поток s tdout и сохраняется в переменной _, на которую впоследствии можно легко ссылаться. $ python3 Python 3 . 4 . 2 ( v3 . 4 . 2 : ab2 c02 3 a 94 3 2 , Oct 5 2 0 1 4 , 2 0 : 4 2 : 2 2 ) [ GCC 4 . 2 . 1 ( Appl e Inc . bui ld 5 6 6 6 ) ( dot 3 ) ] on da rwin Т уре " he lp " , " copyr i gh t " , " c redi ts" or " l i cens e " for more informa t i on . >>> import s ys_displ a yhook i n s t a l l i ng

Г/\888 17. ИнcrpyмetrrW cp8AW времени 8ЫП0Nt8Н14'1

1176 >>> 1

+

2

Previ ous : < s ys_displayhook . Expres s ionCounter obj ect at Oxl 02 1 0 3 5 f B > New 3 3 (

1 ) > ' аЬс ' Previous : 3 аЬс New

' аЬс ' 2 ) > ' аЬс ' Previ ous : аЬс New аЬс ' аЬс ' 2 ) > ' аЬс ' * 3 Previous : аЬс New аЬсаЬсаЬс ' аЬсаЬсаЬс ' ( 3) >

1 7 . 2 . 1 . 7. Каталог установки интерпретатора Пугь к фактическому каталогу установки интерпретатора доступен через атри­ буг sys . executaЫe во всех системах, для которых понятие пути к интерпрета­ тору имеет смысл. Эта информация полезна тем , что позволяет убедиться в кор­ ректности используемой версии интерпретатора и получить представление о тех пугях, которые могли быть установлены на основании данного пуги к расположе­ нию интерпретатора. Атрибут sys . prefix ссылается на родительский каталог установки интерпре­ татора. Как правило, он включает каталоги Ьin и l ib, предназначенные для раз­ мещения исполняемых файлов и модулей соответственно. Листинг 1 7 . 1 7 . sys_locations . py

import sys print print print print

( ' Interpret er executaЫ e : ' ) ( sys . exe cut aЫe ) ( ' \n!ns t a l l a t i on prefi x : ' ) ( sys . prefix )

Приведенный ниже вывод был получен на компьютере Мае, где выполняет­ ся фреймворк Pytho11, установленный с помощью пакета, загруженного с сайта python . org.

1 1 77 $ python3 s ys_l oca t ions . py Interpret e r executaЫ e : / L ibrary/ Framewor k s / Python . f ramewor k/Ver s ions/ 3 . 5 /bin /python3 I n s t a l l at ion prefi x : /Libra ry/ Framewor ks/ Python . f ramework/Ve r s i ons/ 3 . 5

1 7 2 2 Среда времени выполнения .

.

.

Модуль s y s предоставляет низкоуровневые программные интерфейсы дл я вза­ имодейс.·гвия с системой вне приложения за счет обработки аргументов команд­ ной строки, получения доступа к пользовательскому вводу и передачи сообщений и информации о состоянии пользователю. 1 7 .2.2. 1 . Аргументы командной строки

Аргументы, предназначенные для интерпретатора, в нем же и обрабатывают­ ся - они пе передаются в выполняющуюся программу. Остальные параметры, в том числе имя самого сценария , сохраняются в атрибуте sys . a rgv на тот случай , если они действительно потребуются программе. Листинг 1 7. 1 8. sys_argv . py

import s y s print ( ' Arguments : ' , s y s . argv )

В третьем из представленных ниже примеров параметр -u распознается ин­ терпретатором и не передается программе. $ python3 s ys_a rgv . py Argumen t s :

( ' sys_argv . py ' ]

$ python3 s ys_a rgv . py -v foo Ы аh Argument s : [ ' sys_a rgv . py ' , ' -v ' , ' foo ' , ' Ьlah ' ] $ python3 -u s y s_argv . py Argument s : [ ' s ys_a rgv . py ' ] Дополнительные ссылки •

a rgpa r s e (раздел 1 4. 1 ) . Модуль для синтаксического анализа аргументов командной строки.

1 7 .2.2.2. Потоки ввода-вывода

Следуя парадигме Uнix, программы на языке Pythoп могут обращаться к трем дескрипторам файлов, заданным по умолчанию.

Гмва 17. ИнструмеН1Ь1 среды времени выПОNtенИll

1 1 78 Листинг 1 7. 1 9. sys_stdi o . py

import s y s print ( ' STATUS : Reading from s t din ' , f i le=sys . s t derr ) da t a

=

sys . s t di n . read ( )

print ( ' STATUS : Wri t i ng data t o s t dout ' , f i l e=sys . s tderr ) s ys . s t dout . wr i t e ( dat a ) s ys . s tdout . flush ( ) print ( ' STATUS : Done ' , f i l e=sys . s tde r r )

s tdin - стандартный поток ввода, используемый для чтения входных данных, который обычно ассоциирован с консолью, 110 также может поступать из других программ посредством конвейера. s tdou t - стандартный поток вывода, обеспе­ чивающий вывод данных для пользователя ( на консоль ) или передачу данных сле­ дующей команде конвейера. s tde r r - стандартный ноток ошибок, 11редназна python3 s ys_pa th_show . py /Users / dhe l lrnann / Document s / PyMOTW/pyrnotw- 3 / s ource / sys /my / pri v a t e / s i te-packages /my / sha red / s i t e-pa c kages . . . /python3 5 . z ip . . . / l iЬ /python3 . 5 . . . / l iЬ /python3 . 5 /p l a t -darwin . . . /python3 . 5 / l ib-dyn l oad . . . / l iЬ / python3 . 5 / s i t e-pa c kages

Кроме того, программа может изменить путь поиска, добавив элементы непо­ средственно в список sys . path. 9

ht tps : / /hg . python . org / cpython / f i l e / t i p/README

Гмва 17. ИнсrрумеН1Ы С1J8АЫ времени вwПОАНеНИJI

1 1 94 Листинг 1 7.37. sys_path_modify . py import imp import os import s ys

b a s e_d i r os . path . di rname ( �f i l e� ) o r p r i n t ( ' Ba s e d i r e c t or y : ' , ba s e_di r ) =

1

1

# Вставить каталог pa c kage_d i r_a в начало списка # путей пои ска модулей pa c kage d i r а = os . pa t h . j o i n ( ba s e-d i r , s y s . pat h . i nser t ( O , pa c k a g e_d i r_a )

' pa c kage_d i r_a ' )

# Импортировать модуль примера import examp l e p r i n t ( ' Imported exampl e from : ' , examp l e . � f i l e� ) print ( ' ' , examp l e . DATA ) # Поста вить каталог pa c kage_d i r_b первым в списке путей поиска pa c ka ge dir Ь os . path . j oi n ( ba s e-d i r , ' pa c kage_d i r_b ' ) s y s . pat h . i nsert ( O , pa c kage_d i r_b ) =

# Переэа груэить модуль для получени я другой версии imp . r e l oad ( exampl e ) p r i n t ( ' Rel oaded examp l e from : ' , exampl e . file print ( ' ' , exampl e . DATA )

При перезагрузке импортированного модуля файл импортируется заново, а

это означает, что изменение пути в промежуток времени между первоначальной операцией импорта и вызовом функции reload ( ) позволяет загрузить одноимен­

ный модуль из другого каталога. $ pythonЗ sys_pa th_mod i f y . py Base d i r e c t o r y : . Imported examp l e from : Thi s i s examp l e А Rel oaded examp l e from : Thi s i s examp l e В

. / pa c ka ge di r_a / examp l e . py _ . / pac kage_d i r_Ы ex ampl e . py

1 7 . 2 . 6 .4. Нестандартный импорт

Изменяя пути импорта, программист может выбирать расположения , из кото­ рых будут загружаться стандартные модули Pythoп. А что если программе необхо­ димо импортировать код из какого-либо другого источника, отличного от обыч­ ных мест расположения .ру - или .рус-файлов в файловой системе? В документе РЕР 302 111 эта проблема решается за счет введения функций, перехватывающих попытки найти модуль в каталогах путей поиска при его импорте и предпринима­ ющих альтернативные действия для имно ртирования кода из других мест или его предварительной обработки. 1 0 www . python . org / dev / peps /pep - 0 3 0 2

17.2. sys: нкrройка конфмrурацмонных nарвметров, сnе�'8СКМХ /J11Я с:мс:t8МЫ

1 195

Пользовательский импорт реализуется в два этапа. Искателъ (fiшler) отвечает за определение местонахождения модуля и предоставление загруз•tика (loader) для управления фактической операцией импорта. Чтобы добавить искатель, следует присоединить фабрику объектов к списку sys . path_hoo ks. В процессе импорта искателю предоставляется каждая часть пути , пока одна из них не подтвердит на­ личие поддержки ( за счет того, что не сгенерирует исключение Impor t E rror ) . Этот искатель отвечает за дальнейший поиск хранилища данных, представленно­ го собственным пуrем поиска модулей. Листинг 1 7 .38. sysуа th_hooks_noi sy . ру imp o rt s ys

c l a s s N o i s yimp o r t Finde r : PATH_TRI GGER = ' No i s yimp o r t F i nder_PATH_TRI GGER ' de f �i n i t� ( s e l f , path_en t ry ) : p r i n t ( ' Chec king { } : ' . forma t ( pa t h_entry ) , end= ' i f pa th_entry ! = s e l f . PATH_TRI GGER : p r i nt ( ' wrong f i nder ' ) r a i s e ImportErr o r ( ) else : p r i n t ( ' wo r k s ' ) return

')

de f f i nd_modu l e ( s e l f , fu l l name , p a t h=None ) : p r i n t ( ' Looking for { ! r } ' . fo rma t ( ful l name ) ) return None

sys . path_hooks . append ( No i s y imp or t F i nde r ) for hook i n sys . path_ho o ks : print ( ' P at h hoo k : { } ' . f o rma t ( ho o k ) ) sys . path . i n s e r t ( O , N o i s yimp or t Finde r . PATH TRI GGER ) _ try : p r i n t ( ' import i ng t a rget_modul e ' ) imp o rt t a rget_modu l e except Excep t i on a s е : p r i n t ( ' Impo r t fai l ed : ' , е )

Приведенный пример демонстрирует процесс инстанциализации и опроса ис­ кателей. Класс No i syimport Finde r возбуждает исключение ImportError, если он инстанциализируется с использованием пути, не совпадающего со специаль­ ным триг1·ерным значением , которое явно не представляет реальный путь в фай­ ловой системе. Благодаря такой проверке класс No i s yimpo r tFinde r не создает помех для импорта реальных модулей.

ГА8ва 17.

1 1 96

MНCIPYМ8tflW

С8)еАЫ времени ВWl'IOAН8HМJI

$ pythonЗ 5 y 5_path_hoo k5_no i 5 y . py P a t h hoo k : < c l a 5 5 ' z ipimport . z ipimp o r t e r ' > P a t h hoo k : < func t i on F i l e Finder . path_ho o k . < l o ca l 5 > . path_h o o k_for_Fi l e Fi nder at Ох 1 0 0 7 3 4 9 5 0 > P a t h hoo k : < cl a 5 5 ' _ma i n_ . No i 5 yimp or t F i nde r ' > impor t i n g t a rget_modul e Checking Noi 5yimport Finder_ PATH_TRI GGER : work5 L o o k i ng for ' t a rget_modu l e ' Import f a i l e d : No modu l e named ' ta rget_modu l e '

1 7 . 2 . б . 5 . Импорт из хранилищ модуля shelve

Если искателю удается найти модуль, он отвечает за возврат загру3Чuка, спо­ собного импортировать данный модуль. Приведенный в этом разделе пример ил­ люстрирует создание пользовательского импортера, сохраняющего содержимое своего модуля в базе данных, созданной с помощью модуля shelve ( см. раздел 7.2) . Сначала с помощью п р иведенного ниже сценария в хранилище сохраняется пакет, содержащий подмодуль и подпакет. Листинг 1 7.39. sys_shelve_importer_create . py import 5 h e l ve import 0 5 f i l ename ' / tmp/pymotw import examp l e . s he l ve ' i f o 5 . pa t h . exi s t 5 ( f i l ename + ' �db ' ) : 0 5 . unl i n k ( fi l ename + ' . db ' ) w i t h 5 h e l ve . open ( f i l ename ) а 5 db : db [ ' da ta : README ' ] Ь" " " =

=

package README · === = = = е1 = = -= =-=

T h i 5 i 5 the README for " package " . 11 11 1 1

db [ ' pa c k a ge . _i n i t_ ' ] Ь '"' " p r i n t ( ' p a c kage imported ' ) me s s a ge ' Th i s me 5 s age i 5 i n p a c ka g e . _i n i t _ ' " db [ ' p a c kage . modu l e l ' ] Ь" " " p r i n t ( ' p a c kage . modu l e l imported ' ) me s s a ge ' Th i s me ss age i 5 i n package . modul e l ' " db [ ' pa c ka ge . s ubpackage . _i n i t_ ' ] = Ь " " " p r i n t ( ' pa c kage . 5 ubpac ka g e impor t ed ' ) me 5 s age ' Thi 5 me 5 5 a ge i s i n p a c ka g e . 5 ubpa c kage . _i n i t_ ' " db [ ' pac kage . subpa c kage . modu l e2 ' ] = Ь " " " p r i n t ( ' package . subpa cka ge . modu l e 2 impo r t ed ' ) me s 5 a ge ' Th i 5 me 5 s a ge i s i n package . 5 ubpa ckage . modu l e 2 ' =

=

"

"

=

=

" "

=

" "

=

17.2. sys: насtРОЙКВ конфмrурацмонных nарвмеrров, специфических N\ff смсrемы

1 1 97

" " "

db [ ' pa c ka ge . w i t h_erro r ' ] Ь" " " p r i nt ( ' pa c kag e . w i t h e r ror bein g impo r te d ' ) r a i s e Va lueError ( ' ra i s ing ex cept i o n t o brea k import ' ) =

11 11 11

p r i n t ( ' C r ea t ed { } wi t h : ' . forma t ( f i l ename ) ) for key i n s o rted ( db . keys ( ) ) : print ( ' ' , key )

В реально м сценарии создания пакета содержимое будет читаться из ф айло­ вой системы , но в данном простом примере достаточно ис пользовать жестко за­ кодирова нны е значения. $ pythonЗ sys _s h e l ve_imp o r t e r_crea t e . py Created / tmp / pymotw_impor t_exampl e . s h e l ve w i t h : data : README p a c ka ge . init p a c ka ge . modul e'l" p a c kage . subpa c kage . �i n i t� pac kage . subpa c kage . modu l e 2 pac ka ge . wi t h_error

Пользовательский импортер должен предоставить классы для искателя и за· грузчика , которым известно, каким образом должен осуществляться поиск моду­ ля или пакета в хранили щ е. Листинг 1 7 .40. sys shel ve importer . ру _

_

import imp import 0 5 import shelve import sys

de f _mk_i n i t name ( ful lname ) : _ " " " Воз вращает имя модуля init дл я заданного имени пакет а . 1 1 11 1 1

if ful l name . endswi t h ( ' . init return fu l l name init return fu l l name + '

') :

def _ get_key_name ( fu l l name , db ) : " " " Пои ска ть в хранилище shelve имя ful l n ame или fu l l name . �i n i t� и вернут ь найденное имя . " " "

i f fu l l name i n db : return ful l name i n i t name mk i n i t_name ( fu l l name ) - i f i �i t_name i n db : return i n i t name =

ГА888 17. Инсrруменrы 1:РеАЫ времени выnО1U1еНм11

1 1 98 return None

c l a s s ShelveFinder : " " " Найти модули , собранные в хранилище shelve . " " " _ma ybe_recurs i ng de f

init

Fa l s e

( s e l f , path_ent ry ) :

# При з агрузке хранилища shelve создается рекурси в ный цикл # импорта , е сли импортируется dЬm , и нам и з в естно , что в

# этом случа е мы не будем загружа ть импортируемый модуль . # Т а ким образом, е сли обнаружи в а ется , что в озможна # рекурсия , следует просто игнорировать запрос и # исполь зовать другой искатель . i f ShelveFinde r . _ma ybe_re curs i ng : r a i s e Imp or t E r r o r try : # Проверить , что path_ent r y действител ь н о является # хра нилищем shelve t ry : ShelveFinde r . _maybe_r e c u r s i n g = True with shelve . open ( path_ent r y , ' r ' ) : pass f i na l l y : Fa l s e S helveFi n de r . _maybe_recurs ing except Except i on a s е : p r i nt ( ' shel f could n o t imp o r t from { } : { } ' . fo rma t ( path_ent r y , е ) ) raise else : p r i nt ( ' she l f added t o imp o r t pat h : ' , path_en t r y ) s e l f . path_en t r y path_en t r y return =

=

de f

str ( sel f ) : return ' < { } for { ! r } > ' . fo rma t ( s e l f . c l ass� �name s e l f . path_en t r y )

d e f find_modu l e ( s el f , f u l l name , path=None ) : path = path or s e l f . pa th_ent r y p r i n t ( ' \n l o o k i ng for { ! r } \ n i n { } ' . fo rmat ( fu l lname , pa t h ) ) wi th shelve . open ( s e l f . pa t h_ent r y , ' r ' ) as db : ke y_name _g e t_key_name ( f u l l name , db ) i f key name : print ( ' f ound i t as { } ' . forma t ( key_name ) ) return Shel veLoader ( pa t h ) p r i n t ( ' not f ound ' ) return None =

_

c l a s s She l veLoade r :

17.2. sys: настрсЖка конфмrур&1U1онных nерамеqюе, специфических /Aftff системы " " " Загрузи ть исходные коды модулей из хранилища shelve . " " " def �ini t� ( s e l f , path_ent r y ) : se l f . p a th_entry = pa th_e n t r y return def _g et_f i l ename ( s e l f , ful l name ) : # Создать фиктивное имя файла , начинающееся с данного # пути , чтобы обеспечит ь корректную работу # фун кции p kgut i l . get_da t a ( ) return os . pa th . j o i n ( s e l f . pa th_en t r y , ful l name ) def get _s ource ( s e l f , ful l name ) : p r i n t ( ' l oa d i ng source f o r { ! r } from shel f ' ful l name ) ) try : w i th shelve . open ( s e l f . path_ent r y , ' r ' ) key_name _get_key_name ( fu l l name , i f key_name : return db [ ke y_name ] r a i s e Impo r t E r ro r ( ' could not f i nd source for { } ' ful l name ) =

. fo rma t (

as db : db )

. fo rma t (

except Excep t i o n as е : p r i n t ( ' could not l o ad source : ' , е ) r a i s e ImportError ( s t r ( e ) ) def get_code ( s e l f , fu l l name ) : s ource se l f . get_s ou rce ( ful l name ) p r i n t ( ' comp i l ing code f o r { ! r } ' . forma t ( ful l name ) ) return comp i l e ( source , s e l f . _get_f i l ename ( ful l name ) , ' ех ес ' , dont_inhe r i t=T rue ) =

def ge t_da t a ( s e l f , p a t h ) : p r i n t ( ' l oo king for da ta \ n i n { ) \ n for { ! r } ' . fo rma t ( s e l f . path_en t r y , p a t h ) ) i f n o t p a t h . s ta r t s w i t h ( s e l f . pa t h_ent r y ) : r a i s e I OError path p a th [ l en ( s e l f . pa t h_ent r y ) + 1 : ) key_name = ' dat a : ' + p a t h try : w i th shelve . open ( s e l f . path_entry , ' r ' ) as db : return dЬ [ key_name ] ex c ep t Ex c ept ion : # Преобразовыв а т ь все ошибки в тип I OError raise IOError ( ) =

def i s p a c kage ( s e l f , ful l name ) : i nit _name = _mk_init_name ( fu l l name ) w i t h shelve . open ( s e l f . p a t h_ent ry , ' r ' ) as db : return i n i t name i n db

1 1 09

Гмва 17. Инс:rруме1nw с:ред111 времени выncwtettия

1 200

de f l oad_modu l e ( s e l f , full name ) : source s e l f . ge t_s ource ( fu l l name ) =

i f ful l name in sys . modul es : p r i nt ( ' reus ing modu l e from imp or t o f { ! r } ' . fo rmat ( ful l name ) ) mod s y s . modu l e s [ ful l name ] else : p r i nt ( ' creat ing а new modu l e obj ect for { ! r } ' . fo rmat ( full name ) ) mod sys . modul es . s e t de faul t ( ful l name , imp . new_modu l e ( ful l name ) =

=

1 Зада т ь свойст в а , требуемые документом РЕ Р 3 0 2 mod . f i l e_ s e l f . _get f i l ename ( f u l l name ) ful l name mod . пате mod . _path_ s e l f . pa t h_ent r y l oader mod . self 1 РЕ Р- 3 6 6 определяет , ч то пакеты устанавли в ают в 1 качестве значения атри бута _pa c kage_ свое имя , 1 а дл я модулей им является имя их родител ь ского 1 пакета ( если таковой имеется ) i f se l f . i s_p a c kage ( ful l name ) : mod . _p a c kage_ ful l name else : mod . _p a c kage_ ' . ' . j oi n ( fu l l name . sp l i t ( ' . ' ) [ : - 1 ] ) =

i f se l f . i s_pa c kage ( fu l l name ) : p r i nt ( ' a dding path for package ' ) 1 Задать а трибут _path_ для пакетов , чтобы 1 можно было найти п одмодули mod . _pa t h_ [ s e l f . pa th_ent r y ] else : p r i n t ( ' imported as regu l a r modu l e ' ) =

p r i n t ( ' exec i ng s ource . . . ' ) exec ( s ource , mod . dic t ) p r i n t ( ' done ' ) return mod

Теперь классы She l ve Finde r и Shel veLoader можно использовать для импор­ тирования кода из хранилища. Например. созданный пакет package можно им­ портировать с помощью следующего кода. Листинr 1 7.41 . sys_shelve_importer_P ackaqe . py import sys import s ys_s hel ve_imp o r t e r

def show_modul e_de t a i l s ( modu l e ) : p r i n t ( ' me ssage : ' , modu l e . me s s age )

17.2. sya: нкwроiм:а конфмrурационных f18Р8М81РО8. сnецифическмх №1 cмcntмw print print p r i nt print print

( ( ( ( (

' ' ' ' '

. name _pa ckage_ : . - f i l e. _path_ . l oader

' ' ' ' '

, , , , ,

1 201

modu l e . name modu l e . _p a c kage_ ) fi l e ) modu l e . modul e . _pa th_) modu l e . - l o ader-

f i l ename = ' / tmp / pymo tw_import_examp l e . shelve ' sys . path_hooks . append ( s ys_shelve_impo r te r . ShelveFinde r ) s ys . path . ins ert ( O , f i l ename ) p r i n t ( ' Impo r t o f "pac kage " : ' ) impo rt package print ( ) p r i n t ( ' Examine packag e det a i l s : ' ) show_modu l e_det a i l s ( pa c k a g e ) print print print print

() ( ' G l obal s e t t ings : ' ) ( ' s ys . modul es ent ry : ' ) ( s ys . modu l e s [ ' package ' ] )

Хранили ще добавляется в путь импорта при первой же операции импорта, осуществляемой после изменения пути. Искатель распознает хранили щ е и воз· вращает загрузчик, который используется для всех операций импорта из да11но· го хранилища. Первоначальное импортирование на уровне пакета создает 110· вый объект модуля, а затем использует функцию ехес для выполнения исходного кода, загруженного из хранилища. Новый модуль используется в кач естве про­ странства имен , поэтому имена, определенные в исходном коде , сохраняются в виде атрибутов на уровне модуля. $ pythonЗ s ys_shel ve_importer_pa c kage . p y Import o f " p a c kage " : she l f added t o import path : / tmp / pymot w_import_e x amp l e . she l ve l o o ki ng for ' pa c kage ' i n /tmp / pymotw_import_examp l e . she l ve found i t as pa c kage . init l oading s ource for ' pa c kage ' f r o m she l f cre a t i ng а new module ob j ect for ' pa c ka g e ' adding path for p a c kage exe c i ng s ource . . . pa c kage imported done Examine pa c kage det a i l s : me s sag e This me s s age is i n p a c kage . init pa c kage name p a c kage : p a c kage / tmp /pymotw_impor t_e x amp l e . she l ve / p a c kage file

Гмва 17. М нс:tруМеН1'Ы среды времени выnОАНения

1 202 _pa t h_ l oader_

Ох1014 6 7 8 60>

[ ' / tmp / pyrno t w impor t_examp l e . she l ve ' ] _

1 7 2 6 6 И мпорт пользовательских пакетов .

.

.

.

Загрузка других модулей и подпакетов осуществляется аналогичным образом. Листинг 1 7 .42. sys_shel ve_importer module . ру _

import sys import s ys_shel ve_imp o r t e r

de f show_modul e_det a i l s ( modul e ) : modul e . me s s a g e ) pri nt ( ' me s s age 1 name modul e . name ) p r i nt ( ' pri nt ( ' _pa c kage _ : ' , modu l e . _pa c kage_) 1 fi l e ) file modu l e . print ( ' 1 modu l e . _pa t h_ ) p r i n t ( ' _pa th_ 1 l oa de r modu l e . pr int ( ' - l oade r •









1

1

1

1

1

1

f i l ename = ' / tmp /pyrnotw_impor t_examp l e . she l ve ' sys . pat h_hooks . append ( s ys_she l ve_import e r . Shelve Fi nde r ) s ys . pa t h . i n s e r t ( O , f i l e name ) p r i nt ( ' Import o f " p a c kage . modu l e l " : ' ) import p a c kage . modu l e l p r i nt ( ) p r i n t ( ' Examine p a c kage . modu l e l de t a i l s : ' ) s how_ modul e_de t a i l s ( pa c kage . modu l e l ) p r i nt ( ) p r i n t ( ' Import o f " p a c kage . subpa c kage . modul e 2 " : ' ) import pac kage . subpac kage . modu l e 2 p r i nt ( ) p r i nt ( ' Exami ne pa ckage . subpa c kage . modul e 2 det a i l s : ' ) show_modul e_det a i l s ( pa c kage . subpackage . modul e 2 )

Искатель получает уточненное с помощью точечной нотации имя загружаемо­ го модуля и возвращает объект Shel veLoader , настроенный для загрузки модулей в соответствии с путем, ведущим к файлу хранилища. Полностью уточненное имя модуля передается методу l oad_module ( ) загрузчика, который конструирует и возвращает эк.з емпляр модуля.

1 203 $ pythonЗ sys_she l ve_importer_modul e . p y Import o f " pa c kage . modul e l " : she l f added t o import path : / tmp / pymot w_import _examp l e . she lve l o o king f o r ' pa c kage ' i n / tmp /pymotw_import_examp l e . she lve found it a s p a c kage . init loading source f o r ' pa c kage ' from s he l f cre a t ing а new module obj ect for ' pa c kage ' adding path for pa c kage execing s ource . . . pac kage imported done l o o king for ' pa c kage . modu l e l ' i n / tmp /p ymotw import examp l e . she l ve found i t as pac kage . modu l e l l oading s ource f o r ' pa c kage . modu le l ' f r om she l f crea t i ng а new module obj ect f o r ' pa c kage . modu l e l ' imported a s regu l a r modu l e execing s o u r ce . . . pac kage . modu l e l imported done Exam i ne p a c kage . modul e l detai l s : me s s age : This me ssage i s i n package . modul e l �name� : package . modul e l �pa c kage � : pa c kage / tmp / pymotw import examp l e . she l ve / p a c kage . modu l e l file �path� : / tmp / pymotw import examp l e . shelve l oade r : < s ys_she lve_impo r t e r . ShelveLoader obj e c t a t

=

=

Ох 1 0 1 3 7 6е 1 0 >

Import o f " pa c kage . subpa c kage . modul e 2 " : l oo k i ng for ' pa c kage . subpa c kage ' i n / tmp /pymotw_impor t_ex amp l e . shel ve found it a s pac kage . subpac kage . �i n i t� l oading s ource f o r ' pa c kage . subpac kage ' f r om she l f c re a t ing а n e w module obj ect for ' pa c kage . subpa c kage ' addi ng path for pa c kage execing s ource . . . package . subpa c kage imported done l oo k i ng for ' pa c kage . s ubpa c kage . modu l e 2 ' i n / tmp /p ymotw_import_examp l e . s helve found i t as package . s ubpa c kage . modu l e 2 l oading s ource f o r ' pa c kage . subpa c kage . modu l e 2 ' from she l f c r e a t i ng а new module obj e ct f o r ' pa c kage . subpa c kage . modul e 2 ' imported as regu l a r module exe c i ng s ource . . .

l"Aa88 17. Инструмемrw ср8АЫ времени BWПONteHMll

1 204 pac kage . subpackage . module2 imported done

Examine package . s ubpa c kage . modul e 2 de t a i l s : me s s age : This me s s age i s in pac kage . subpac kage . module2 пате : package . s ubpa c kage . modu l e 2 �package� : package . subpa c kage file / tmp /pymotw_impor t_exampl e . she lve /pa c kage . subpac kage . modul e 2 �path� : /tmp /pymotw_import_examp l e . s helve �l oader� : < s ys_shelve_impor t e r . Shel veLoader obj ect at Ох101 Забс8 8>

1 7.2.6.7. Переэагруэка модулей с помощью пользовательского импортера

Перезагрузка модулей осуществляется не сколько иначе. Вместо того чтобы создавать новый объект модуля , повторно используется существующий объект. Листинг 1 7 .43. sys shelve importer reload . ру

import import l ib import sys import s ys_shelve_importer f i l ename

=

' / tmp /pymotw_impor t_example . shelve '

s ys . path_hoo ks . append ( s ys_she lve_importer . ShelveFi nder ) s y s . path . insert ( O , f i l ename ) p r i nt ( ' Fi r s t import o f "pac kage " : ' ) import package print ( ) print ( ' Re l oading "pac kage " : ' ) import l ib . rel oad ( pa c kage )

Благодаря повторному и спользованию того же объекта существующие ссылки на модуль сохраняются, даже если определения класса или функции изменяются на с тадии :*!грузки. $ pythonЗ s ys_shelve_impor t e r_r e l oad . py F i r s t import of " pa c kage" : she l f added t o import path : / tmp /pymotw_import_example . s helve l ooking for ' pa c kage ' i n / tmp /pymotw_impor t_example . she lve found i t as package . �in i t� l oading s ource for ' package ' from she l f crea t i ng а new modul e obj ect for ' pa c kage ' adding path for package execing s ource . . . pac kage imported

17.2 . sys: насrройка конфиrур8ЦМОНнwх nараммров. специфических A!tff системы

1 205

done Re loading "package " : l ooking for ' pa c kage ' in /tmp /pymotw_import_example . shelve found i t as package . �i n i t� l oading s ource for ' package ' from shel f reus ing modul e from impor t of ' pa c kage ' adding path for package execing s ource . . . package imported done

1 7 .2.6.8. Обработка ошибок и мпорта Если ни одному из искателей не удается найти модущ., основной импортирую­ щий код возбуждает исключение ImportError. Листинг 1 7 .44. sys_shelve_importer_mi s s i nq . py

import s ys import s ys_she l ve_impor t e r f i l ename ' / tmp /pymotw_impor t_example . shelve ' s ys . pa th_hoo k s . append ( s ys_shelve_import e r . Shelve Finde r ) s ys . pa t h . i n s e rt ( O , f i lename ) =

t ry :

import package . modul e З except Impo r t E r r or a s е : print ( ' Fa i l ed t o import :

',

е)

Другим ошибкам, возникающим в п роцессе импорта, разрешено распростра­ няться . $ pyt honЗ s ys_she lve_importe r_mi s s i ng . py shel f added t o import pa th : / tmp /pymot w_import_example . shelve l ooking for ' pa c kage ' in / tmp /pymotw_import_example . she l ve found i t as package . �i n i t� l oading s ource for ' package ' from s hel f crea t ing а new module obj ect for ' pa c kage ' adding path for package exec i ng s ource . . . package imported done l ooking for ' pa c kage . modul e З ' in / tmp /pymotw_impor t_example . shelve not found Fa i l e d to import : No modul e named ' pa c kage . module З '

1 206

1 7.2.6.9.

Данные пакета

Кроме определен ия API для загрузки исполняемого кода Pythoп документ

РЕР 302 определяет дополнительный API , предназначенный для извлечения дан­

ных пакета , которые могут использоваться при распространении файлов данных, документации и других н еобходимых пакету ресурсов , не являющихся кодом. Реализуя метод get_da t a ( ) , загрузчик может предоставить вызы вающим прило­ жениям возможность поддерживать извлечение данных , ассоциированных с па­ кетом , без учета того, каким образом фактически устанавливается пакет (в част· ности, не предполагая , что пакет сохраняется в виде файлов в файловой системе).

_

Листинг 1 7 .45. sys_shel ve imp orter_get_data . ру

import import import import

sys sys_shelve_impor t e r os pkgut i l

f i l ename ' / tmp/pymotw_import_examp l e . shelve ' sys . path hooks . append ( s ys shelve - impo r t e r . ShelveFinder ) sys . path� insert ( O , f i l ename ) =

import package readme_path

=

os . pa th . j o in ( pa c kage . �path� [ O ] , ' README ' )

readme pkgut i l . get_da t a ( ' pa c kage ' , ' README ' ) # Эквивалентно следующей инструкции : # readme = package . �loader� . get_da t a ( readme_path ) print ( readme . decode ( ' ut f - 8 ' ) ) =

foo_path o s . path . j oin ( pa c kage . �path� [ O ] , ' foo ' ) t ry : foo pkgut i l . get_data ( ' pac kage ' , ' foo ' ) # Эквивалентно следующей инструкции : # foo package . �loader� . get_da t a ( foo_path ) except IOError as e r r : p r i nt ( ' ERROR : Could not l oad " fo o " ' , err ) el s e : print ( foo ) =

=

=

Функция get_data ( ) принимает в качестве аргумента путь, базирующийся на модуле или пакете, которому принадлежат данные. Она либо возвращает содер· жимое ресурсного файла в виде байтовой строки , либо возбуждает исключение IOError , если такого ресурса не существует. $ pythonЗ sys_shel ve_importer_get_da t a . py she l f added to import path : / tmp /pymot w_import_example . she lve l o o k i ng for ' package ' in /tmp/pymotw import exampl e . shelve found i t as pac kage . -= init� loading s ource for ' pa c kage ' from shel f

17.2. sys: нас:тройка конфмrурационных параметров, специфических AttR системы

1 207

crea t i ng а new modul e obj ect for ' pa c kage ' adding path for package execing s ource package imported done l ooking for d a t a in / tmp/pymotw import example . shelve for ' /tmp/pymotw_impor t_example . shelve /README ' .



.

package README T h i s is the README for " package " . l ooking for d a t a in / tmp/pymotw_import_examp l e . shelve for ' / tmp/pymotw_import_example . shelve / foo ' ERROR : Could not l oad " fo o " Дополнительные ссылки •

pkgut i l (раздел 1 9. 2). Включает функцию get_data ( ) , предназначенную для извлече­ ния данных из пакета.

1 7 .2.6. 1 0. Кеш объекта импорта Выполнение процедуры поиска по всем перехватчикам всякий раз, когда им­ портируется модуль, может аанимать много времени. В целях экономии времени поддерживается словарь sys . path impor t e r _ cache, устанавливающий соответ­ ствие между значениями пути и именами загрузчиков, которые могут исrюльзо­ вать эти значения для поиска модулей. _

Л истинг 1 7.46. sys_path_impor ter_cache . py

impor t os import sys prefix

=

os . pa t h . abspath ( s ys . pr e fi x )

print ( РАТН : 1 ) for name in s ys . path : name name . replace ( prefix, ' . . . ' ) print ( ' ' , name ) 1

=

p r i nt ( ) pr int ( ' IMPORTERS : ' ) for name , cache_va lue in s ys . path_import e r_cache . i t ems ( ) : i f ' . . ' i n name : name os . path . abspa t h ( name ) name = name . repl ace ( p r e f i x , ' . . . ' ) print ( ' { ) : { ! r } ' . fo rma t ( name , cache_va lu e ) ) =

1 208

Дл я идентификации путей к каталогам файловой системы используются иска­ тели (класс Fi l e Fi nde r ) . Если каталог не подде рживается никаким искателем, то ему соответствует значение None, поскольку его нельзя использовать для импор­ тировапия модулей. В приведенном ниже выводе, отоб р ажающем импортирова­ ние из кеша, результаты усечены форматированием. $ pythonЗ s ys_path_impor t e r_cache . py РАТН : /Users /dh e l lmann/ Document s / PyMOTW/ PythonЗ /pymotw- 3 / s ou rce / s ys . . . / l iЬ /python3 5 . z ip . . . / l iЬ/python З . 5 . . . / l iЬ /pythonЗ . 5 /p l a t - da rwin . . . / l iЬ /pythonЗ . 5 / l ib-dynload . . . / l iЬ /python З . 5 / s i te-pa ckage s IMPORTERS : s ys pa th import e r cache . py : None . . . / l i Ь / pythonЗ . 5/encodings : F i l e Finder ( ' . . . / l i Ь /python З . 5 / encoding s ' ) . . . / l iЬ /pythonЗ . 5 / l i b-dynload : Fi l e F i nder ( ' . . . / l i b /pythonЗ . 5 / l ib-dyn l oad ' ) . . . / l iЬ /pythonЗ . 5 / l ib-dynload : Fi l e Fi nder ( ' . . . / l iЬ /python З . 5 / l i b-dynload ' ) . . . / l iЬ /pythonЗ . 5 / s i t e-package s : Fi l eFinder ( ' . . . / l iЬ /pythonЗ . 5 / s i t e-pa c kage s ' ) . . . / l i Ь /pythonЗ . 5 : Fi l e Finder ( ' . . . / l i Ь /pythonЗ . 5 / ' ) . . . / l iЬ /python З . 5 /p l a t -da rwi n : Fi l e F i nder ( ' . . . / l iЬ /pythonЗ . 5 /p l a t -da rwi n ' ) . . . / l iЬ /pythonЗ . 5 : Fi l eFi nder ( ' . . . / l i Ь /pythonЗ . 5 ' ) . . . / l iЬ /python3 5 . z i p : None . . . / l iЬ /pythonЗ . 5 /p l a t - da rwin : Fi l e Fi nder ( ' . . . / l i Ь /python З . 5 /p l a t -da rwi n ' )

1 7. 2 . 6. 1 1 . Метапуть

Список sys . me t a_p a t h дополнительно расширяет состав потен циальных источников импорта, поскольку искатель просматривает его до просмотра обыч­ ного списка путей s ys . p a t h . API искателя для метапуги тот же, что и для обычно­ го пути, за исю1юче11ием того, что метаискатеш. не ОI'р аничивается одной запи­ сью в sys . pa t h - oн может выполнять пои { } ' . fo rma t ( func_name , arg ) ) return =

==

=

=

==

==

20 21 22 23 24 def Ь ( ) : print ( ' ins ide Ь ( ) ' ) 25 return ' respons e_from_b ' 26 27 28 2 9 de f а ( ) : print ( ' i nside а ( ) ' ) 30 31 val Ь{) 32 return v a l * 2 =

=

17.2. еуа: насqюйка конфмrурацион ных l8P8М8'IJIOВ, сnецифмческих N\ff системы

1 21 5

33 34 3 5 s ys . s e t t ra c e ( t race_c a l l s_and_returns ) 36 а ( )

Для мониторинга событий возврата и з функций используется локальная функ­ ция трассировки. Чтобы отслеживание возвращаемого значения было возмож­ ным, функция t race _cal l s_and_returns ( ) должна возвращать ссылку на саму себя, если обрабатывается событие ca l l . $ python3 s ys_s e t t race_return . py * Call i ns ide * Call i ns ide

t o а on l i ne 2 9 o f s ys_s e t t race_return . py а() t o Ь o n l i ne 2 4 o f s ys_s e t t race_re turn . py

Ь() * ь => respons e_f rom_b * а => respons e_from_b respons e_ from Ь

17

.

2 7 .4. Распространение исключений .

Для мониторинга исключений необходимо отслеживать события excep t i on в локальной функции трассировки. Когда возникает исключение, вызывается пере­ хватчик трассировки с кортежем, содержащим тип исключения, об-ьект исклюолее 11одроб11ую информацию о файле можно получить с помощью функций s ta t ( ) или l s t a t ( ) (аналщ·ична функции s ta t ( ) но не следует по символиче­ ским ссылкам) . ,

Листинг 1 7 . 55. os_s tat . py import o s import s ys import t ime i f l en ( s ys . a r gv ) == 1 : fi l e f i l ename else: f i l e name sys . a rgv [ l ] =

s t a t_i n f o = o s . s t a t ( fi l ename ) p r i nt p r i nt print p r i nt p r i nt p r i nt p r i nt p r int

( ( ( ( ( ( ( (

' o s . s t a t ( { ) ) : ' . forma t ( f i l ename ) ) S i ze : ' , stat info . st s i z e ) ' 7 ' Permi s s i ons : , oct ( s t a t_i n f o . s t_mode ) ) ' Owner : ' , s t a t i n f o . s t ui d ) ' Device : ' , s t a t i n fo . s t dev ) ' Created : 7 , t ime . c t ime ( s t a t - i n f o . s t ct ime ) ) ' L a s t modi f i ed : ' , t ime . ct ime ( s t a t i n f o . s t mt ime ) ) ' Last accessed : ' , t ime . ctime ( s t a t = i n f o . s t =a t ime ) )

2.7.З. os: порtМруемый АОС1'УП к с:р8АСТ88М. сnец.,tфмчес:tсМ м /!ftlf операционных систем

1 22 1

Вывод может быть раз.личным в аависимости от способа установки 11римера. Поэкспериментируйте с этой функ цией, передавая различные имена файлов сце­ нарию o s_ stat . py. $ python 3 os_s t a t . py os . s t a t ( o s_s t a t . py ) : S i ze : 5 93 Pe rmi s s ions : 0 0 1 0 0 6 4 4 Owner : 5 2 7 Dev ice : 1 6 7 7 7 2 1 8 Created : S a t Dec 1 7 1 2 : 0 9 : 5 1 2 0 1 6 La s t modi f i ed : S a t Dec 1 7 1 2 : 0 9 : 5 1 2 0 1 6 La s t acces sed : S a t Dec 3 1 1 2 : 3 3 : 1 9 2 0 1 6

$ python3 os_st at . py i ndex . r st os . s t a t ( i ndex . rs t ) : S i ze : 2 68 7 8 Permi s s ions : 0 0 1 0 0 6 4 4 Owne r : 5 2 7 Dev ice : 1 6 7 7 7 2 1 8 Crea t ed : S a t Dec 3 1 1 2 : 3 3 : 1 0 2 0 1 6 La s t mod i f i ed : S a t Dec 3 1 1 2 : 3 3 : 1 0 2 0 1 6 La s t acces s ed : S a t Dec 3 1 1 2 : 3 3 : 1 9 2 0 1 6

В случае Uпiх·11одобных систем нрава доступа можно изменять с помощь ю функции chmod ( ) , передавая ей режим доступа в виде целого числа. Значения ре· жима доступа можно формировать с помощью констант, определенных в модуле stat. В следующем примере переключается бит разрешения на выполнение файла. Листинг 1 7.56. os_stat_chmod . py impo r t os imp o rt s t a t f i l ename ' o s_sta t_chmod_exampl e . t xt ' i f o s . pa t h . e x i s t s ( f i l ename ) : os . un l i n k ( f i l ename ) wi t h open ( f i l ename , ' wt ' ) as f : f . wr i t e ( ' conten t s ' ) =

# Определить , какие разрешени я уже заданы, исполь зуя модуль s t a t ex i s t i ng_permi s s i ons = s t a t . S_IMODE ( os . s t a t ( f i l ename ) . s t_mode ) i f not o s . access ( f i l ename , os . X OK ) : _ p r i n t ( ' Adding execut e permi s s i on ' ) new_p ermi s s i ons ex i s t i ng_pe rmi s s i ons s t a t . S_IXUSR else : p r i n t ( ' Removing execu t e permi s s i on ' ) # Исполь зовать операцию XOR для отмены ра зрешени я на выполнение ex i s t i ng_pe rmi s s i ons new_permi s s i ons s t a t . S IXUSR =

=

o s . chmod ( f i l ename , new_p e rmi s s i ons )

л

1 222

В этом сценарии предполагается, что во время его выполнения он имеет пол­ номочия, необходимые для изменения режима доступа к файлу. $ pythonЗ os_st a t_chmod . py Adding execut e permi s s i on

Для проверки прав доступа процесса к файлу следует использовать функцию ().

acce s s

Листинг 1 7.57. os access . ру

_

imp o rt os print print print print print

( ( ( ( (

' Te s t i ng : ' , _fi l e_ ' Ex i s t s : ' , o s . acce s s ( fi l e , o s . F_OK ) ) ' ReadaЫ e : ' , os . acce s s ( _f i l e_ , os . R_OK ) ) ' W r i t aЫ e : ' , o s . acce s s ( f i l e , os . W ОК ) ) ' Execut aЫ e : ' , os . acces-;( f i l e , os -:-x_OK ) )

Результаты будут меняться в зависимости от способа установки кода примера, но в целом вывод будет аналогичен приведенному ниже. $ pythonЗ os_acces s . py Tes t i ng : os_a cces s . py Ex i s t s : T rue ReadaЬ l e : True W r i t aЫ e : тrue Execut a Ы e : Fal s e

Библиотечная документация для функции a c c e s s ( ) содержит два предупреж­ дения. Во-первых, нецелесообразно вызывать функцию a c c e s s ( ) для того, что­ бы проверить возможность открытия файла до того, как фактически вызывать функцию open ( ) для этого файла. Между :�тими двумя вызовами суще { } ' . forma t ( l i n k_name , �fi l e� ) ) 0 5 . 5 yml i n k ( f i l e , l in k_name ) 5 t at_i n f o = 0 5 . l 5 t a t ( l in k_name ) p r i n t ( ' Permi 5 5 i on5 : ' , oct ( 5 t a t_i n fo . 5 t _mode ) ) p rint ( ' Point 5 t o : ' , 05 . read l i n k ( l i n k_name ) ) # Очи стка . 0 5 . un l i n k ( l i n k_name )

ГА8118 17. Инструменты среды времени выnомения

1 224

Функция syrnlink ( ) используется для создания символической ссылки, а функ­ ция readl ink ( ) - для чтения ссылки и определения исходного файла, на кото­ рый указывает ссылка. Функция l s t a t ( ) аналогична функции s t a t ( ) , но не сле­ дует по символическим ссылкам.

$

pythonЗ o5_5yrnl i n k 5 . py

Crea t i ng l i n k / tmp / o 5_5 yrnl i nk5 . py -> 05 5 yrnl i n k 5 . py Permi 5 5 i on 5 : 00 1 2 0 7 5 5 Point 5 t o : 0 5_5 yrnl i nk5 . py

1 7 3 5 Безопасная замена существующего файла .

.

.

Операция замены или переименования существующего файла не является идемпотентной и может приводить к переходу приложения в состояние гонки. Функции rename ( ) и replace ( ) реализуют безопасные алгоритмы этих действий, по возможности используя атомарные операции в случае РОSIХ-совместимых систем. Листинг 1 7 .60. os_rename_replace . py import g l ob impo r t 0 5

wi t h open ( ' rename_5 t a r t . txt ' , ' w ' ) а 5 f : f . wr i t e ( ' 5 t a r t ing а 5 rename_5 t a r t . t xt ' ) print ( ' S t a rt i ng : ' , g l ob . g l ob ( ' rename * . tx t ' ) ) o 5 . rename ( ' rename_5 t a r t . txt ' ,

' rename_f i n i 5 h . t xt ' )

pr i n t ( ' Af t er rename : ' , g l ob . g l ob ( ' rename * . txt ' ) ) with open ( ' rename_f i n i 5 h . t xt ' , ' r ' ) а 5 f : p r i nt ( ' Cont ent 5 : ' , repr ( f . read ( ) ) ) wi th open ( ' r ename_new_cont ent 5 . txt ' , ' w ' ) а 5 f : f . wri t e ( ' end i n g with cont ent5 o f rename_new_content 5 . t x t ' ) 05 . replace ( ' rename_n ew_content 5 . t xt ' ,

' rename_fin i 5 h . txt ' )

with open ( ' rename_f i n i 5 h . t xt ' , ' r ' ) а 5 f : p r i nt ( ' After rep l a ce : ' , repr ( f . read ( ) ) ) for name i n g l ob . g l ob ( ' rename * . t xt ' ) : 0 5 . unl i n k ( name )

В большинстве случаев функции rename ( ) и replace ( ) способны работать с использованием различных файловых систем. Переименование файла может оказаться неуспешным, если он перемещается в другую файловую систему или ко­ нечный файл уже существует.

17.З. оа: nо�nмруемыА АОС1У11 к средс1118М, сnецмфмчес:кмм Ntlf операаоtОННЫХ СИС18М

1225

$ pythonЗ os_rename_repl ace . py S t a rt i ng : [ ' rename_s t a rt . t xt ' ] A f t e r rename : [ ' rename_ f i n i s h . t xt ' J C o n t e nt s : ' s t a r t i n g as rename_s t a r t . t xt ' A f t e r repl a ce : ' endi ng with conten t s o f rename new content s . t x t '

1 7 З б Определение и изменение владельца процесса .

.

.

Рассматриваемый далее набор функций, предоставляемых модулем o s , ис­ пользуется для определения и изменения идентификаторов владельцев процес­ сов. Эти функ ц ии чаще всего применяются авторами демонов или специальных системных программ, для которых желательно изменить права доступа, а не вы­ полняться от имени суперпользователя root. В данном разделе не делается ника­ ких попыток объяснения сложных вопросов, касающихся системы безопасности U нiх, а также владения процессами и организации работы с ними. Для получения более подробной информации по этому вопросу следует обратиться к дополни­ тельным ссылкам, приведенным в конце раздела. В следующем примере сначала отображается информация о реальных и эф­ фективных пользователе и группе для процесса, а затем эффективные значения изменяются. Эти действия аналогичны тем, которые выполнял бы демон, запу­ щенный от имени пользователя root во время первоначальной за грузки системы, для понижения уровня своих привилегий и выполнения от имени другого пользо­ вателя. П римечание Прежде чем выполнять этот пример, измените значения констант TEST_GI D и TEST_U I D

та ким образом, чтобы о н и соответствовали реальному пользователю, определенному в системе.

Листинг 1 7.61 . os_process_user_example . py import os TEST GI D TEST U I D

= =

502 502

de f show user i n fo ( ) : ( 1 / { } ' . format ( pr i nt ( ' Us r ( a ctual / e f fect i ve ) os . g e t u i d ( ) , os . geteuid ( ) ) ) { 1 / { } ' . format ( p r i n t ( ' Group ( a ctua l / e f fect i ve ) os . get g i d ( ) , os . ge t e g i d ( ) ) ) p r i n t ( ' Actual Groups : ' , os . g e t g roup s ( ) )

e

pr i n t ( ' BEFORE CНANGE : ' ) show_user_i n f o ( ) pr i nt ( )

1 226

fA888 17. Инс:rрумеН1Ы среды времени ВЫIЮАН8НМR

t ry : o s . s e t e g i d ( TEST_G I D ) except OSError : p r i nt ( ' ERROR : Could not change e f fect ive g r oup . ' Rerun as root . ' ) else : p r i n t ( ' CНANGE GROU P : ' ) s how_u ser_i n f o ( ) p r i nt ( )

'

t ry : o s . seteuid ( TEST UI D ) _ except OSError : pr i n t ( ' ERROR : Cou l d not change e f fect i ve user . ' Rerun as root . ' ) else : p r i nt ( ' CНANGE USER : ' ) show_use r_i n f o ( ) pri nt ( )

'

При выполнении этого сценария па компьютере Мае, работающем под управ­ лением OS Х, с использованием значения 502 для идентификаторов пользователя и группы были получены следующие результсlты. $ python3 os_process_u s e r_examp l e . py BE FORE CHANGE : User ( a ctua l / e f fecti ve ) : 5 2 7 / 5 2 7 Group ( a ctual / e f fect ive ) : 5 0 1 / 5 0 1 Act u a l Groups : [ 50 1 , 7 0 1 , 4 0 2 , 7 0 2 , 5 0 0 , 1 2 , 6 1 , В О , 9 В ,

399, 33, 100, 204 , 395]

39В ,

ERROR : C ou l d no t change e f fect ive g r oup . Re run a s root . ERROR : Coul d not change e f fect ive user . Re run as root .

Значения идентификаторов не удалось изменить, 1юскол1.ку процесс, выпол­ няющийся не от имени пользователя root, не может изменить идентификатор своего эффективного владельца. Любая попытка установить для идентификатора эффективного пользователя или группы значение, отличное от идентификатора текущего пользователя, приводит к возбуждению исключения OSErr or. Другое дело - выполнение этого же сценария с помощью команды s udo , запускающей его с привилегиями root. $ sudo python3 os_proces s_u s e r_exampl e . py BEFORE CНANGE : : О / О User ( a ctual / e f f e c t i ve ) Group ( actual / e f fect i ve ) : О / О Act u a l Groups : [ О , 1 , 2 , 3 , 4 , 5 , В , 9 , 1 2 , 2 0 , 2 9 , 6 1 , В О ,

7 02 , 33, 9В , 1 0 0 , 204 , 395, 39В , 3 9 9 , 7 0 1 ]

17.3. 08: noprмpyeмыii доступ к cpttACТRM, сnецифическим IWt оnерационных смсrем CHANGE GROU P : : U s e r ( a ctual / e f fect i ve ) G r oup ( a ctual / e f fect i ve ) : Actual Groups [ 0 , 1, 2, 702, 33, 98, 100, 204 , 395, C НANGE USER : U s e r ( a ctua l / e f fect ive ) : G r oup ( a ctual / e f fect i ve ) : Actual G r oups [ 0 , 1, 2, 7 02 , 3 3 , 9 8 , 1 0 0 , 2 0 4 , 3 9 5 ,

1 227

О / О О / 502 3, 4, 5, 8, 9, 398, 399, 701]

12,

20,

29,

61 , 8 0 ,

12,

20,

29,

61, 8 0 ,

О / 502 О / 502 3, 4, 5 , 8, 9 , 398, 399, 701]

В этом случае, поскольку сценарий запускается от имени суперпользователя, он может изменить идентификатор sффективного пользователя или группы для процесса. Коль скоро идентификатор UID эффективного пользователя изменен, полномочия доступа для процесса 01раничиваются разрешениями, установлен­ ными для данного пользователя. Поскольку непривилегированный пользователь не може'l' изменить свою принадлежность к группе, программа должна изменить сначала идентификатор своей группы и только после этого - идентификатор пользователя.

1 7.3.7. Управление окружением процесса Еще одним средством операционной системы, которое модуль o s предоставля­ ет программам, является окружеиие (также - среда) . Переменные, установленные в окружении , доступны в виде строк, которые можно читать с помощью объекта os . envi ron или функции get env ( ) . Обычно переменные среды используются в качестве конфигурационных параметров, таких как пути поиска, расположения файлов и флаги отладки. В следующем примере показано, как можно получить пе­ ременную среды и передать через нее значение дочернему процессу. Лисrинr 1 7.62. os_envi ron_example . py import os print ( ' I n i t i a l va lue : ' , os . environ . qet ( ' T ESTVAR ' , None ) ) p r i n t ( ' Ch i l d process : ' ) os . s y s t em ( ' echo $TESTVAR ' ) os . envi ron [ ' TESTVAR ' ]

=

' TH I S VALUE WAS CHANGED '

print ( ) p r i n t ( ' Chanqed value : ' , os . env i ro n [ ' T E STVAR ' ] ) p r i n t ( ' C h i l d proce s s : ' ) os . s y s t em ( ' echo $TESTVAR ' ) del os . envi ron [ ' T ESTVAR ' ] print ( ) pr int ( ' Removed va lue : ' , os . environ . get ( ' TESTVAR ' , None ) ) p r i n t ( ' Chi l d proce ss : ' ) os . s y s t em ( ' echo $TESTVAR ' )

ГА888 17. Инсrрументы cpep,w времени ..,llONt8HМR

1 228

Объект o s . envi ron следует принятым в Руtlюн стандартам API отображений для получения и установки значений. Изменен ия в объекте o s . envi ron экспорти­ руются в дочерние процессы. $ python3



05_envi r on_exarnpl e . py

I n i t i a l value : None C h i l d proce5 5 : Cha nged va l u e : THI S VALUE WAS CНANGED C h i l d proce 5 5 : THI S VALUE WAS CНANGED Rernoved va l ue : None C h i l d proce5 5 :

1 7 .3.8. Управление рабочим каталогом процесса В операционных системах с иерархическими файловыми системами существу­ ет понятие текущего рабач,его -каталога - каталога файловой системы, используе­ мого процессом в качестве начального при обращении к файдам с использовани­ ем относительных путей. Текущий рабочий каталог можно получить с помощью функции get cwd ( ) и изменить с помощью функции chdi r ( ) . Листинг 1 7.63. os_cwd_example . py import 0 5 p r i n t ( ' S t a r t i ng : ' , 0 5 . getcwd ( ) ) p r i n t ( ' Movi ng up o ne : ' , 05 . pardi r ) o 5 . chdi r ( o 5 . pardi r ) p r i n t ( ' After move : ' , 05 . get cwd ( ) )

Строки o s . curdi r и o s . pardi r обеспечивают портируемый способ обраще­ ния к текущему и родительскому каталогам соответственно. $ python3 o5_cwd_examp l e . py S t a r t ing : . . . /pyrnotw- 3 / 5 ource / 0 5 Mov i n g u p one : . . A f t e r rnove : . . . /pyrnotw- 3 / 5 ource

1 7.3.9. Выполнение внешних команд П реду преждение

Многие из функций для работы с процессами имеют ограниченную портируемость. Если нужен более последовательный подход, обеспечивающий работу с процессами платфор­ монезависимым способом, воспользуйтесь возможностями модуля 5 ubproce 5 5 (см. раз­ дел 1 0. 1 ).

17.3. as: nорrмруемыА достуn к средст88М, сnеаutфмчес:ким /lftR операционных смсrем

1 229

Самым простым способом выполнения независимой команды, вообще не требующим никакого взаимодействия с ней , является использование функции system ( ) . Эта функция получает единственный строковый аргумент, определяю­ щий командную строку, которая должна быть выполнена подпроцессом, запуска­ ющим командную оболочку. Листинг 1 7.64. os_sys tem_example . py import 05

# Проста я кома нда 05 . 5 y 5 t em ( ' pwd ' )

Функция system ( ) возвращает значение кода выхода из оболочки , в которой выполняется программа, упакованное в 1 6-битовое число. Старший байт этого числа представляет код завершения программы , а младший - номер сигнала, пре­ рвавшего выполнение процесса, или нуль. $ python3



o 5_5 y5t em_examp l e . py

. . . /pymot w - 3 / 5 ource / 0 5

П оскольку команда передается для обработки непосредственно командно й оболочке, она может включать любой допустимый для оболочки синтаксис, в том числе универсализацию имен файлов и переменные среды. Листинг 1 7.65. os_sys tem_shell . py import 05

# Кома нда с р а сширением оболочки 0 5 . 5 y 5 t em ( ' echo $TMPD I R ' )

Переменная среды $ TMPDIR в этой строке расширяется во время выполнения оболочкой командной строки. $ python3



o 5_5 y 5 t em_5hel l . p y

/ va r / fol de r 5 / 5 q / 8 g k 0wq8 8 8 x l gg z 0 0 8 k 8 d r 7 1 8 0 0 0 0hg /T /

Если команда н е запущена явным образом для вьшолнения в фоновом режиме, то вызов функции system ( ) блокируется до ее полного завершения. Стандартные потоки inpu t , output и error дочернего процесса по умолчанию связываются с соответствующими потоками , принадлежащими вызыва ю щему коду, по их мож­ но перенаправить, используя синтаксис командной оболочки . Листинг 1 7. 66. os_sys tem_background . py import 0 5 imp o r t t ime p r i nt ( ' Ca l l i n g . . o 5 . 5 y 5 t em ( ' da t e ;

.

•)

( 5 l e ep 3 ; dat e ) & ' )

1 230 print ( ' S l eepi n g t ime . 5 l eep ( 5 )



.

.

')

Здесь мы вторгаемся в тонкости работы командной оболочки, однако для того, чтобы сделать то же самое, существуют более удобные способы. $ python3 -u o 5 _ 5 y 5 t em_ba ckground . py C a l l ing S a t Dec 3 1 1 2 : 3 3 : 2 0 EST 2 0 1 6 S l eep i n g S a t Dec 3 1 1 2 : 3 3 : 2 3 EST 2 0 1 6 .







.



1 7. 3. 1 0. Создание процессов с помощью вызова os . :fork ( ) Функции fork ( ) и ехес ( ) из POSIX (доступны на платформах Мае OS Х, Li11ux и других U11iх-11одобных платформах) предоставляются модулем o s . Обсуждению надежных способов использования этих функций посвящены целые книги, поэ­ тому для получения более подробных сведений, •1ем те, которые представлены в этом разделе, поищите соответствующую ю1и1-у. Для создания нового процесса как клона текущего процесса нужно использо­ вать функцию fork ( ) . Листинг 1 7.67. os_fork_example . py import 0 5 pid " 05 . fork ( ) i f pid : p r i n t ( ' Ch i l d proce5 5 i d : ' , p i d ) el5e : p r i n t ( ' I am the chi l d ' )

Вывод будет меняться в зависимости от состояния системы во время выполне­ ния примера, но в целом он будет подобен приведенному ниже. $ python3 -u 0 5 _ f o r k_examp l e . py Chi l d proce 5 5 i d : 2 9 1 9 0 I ат t h e ch i ld

После ответвления нового процесса существуют уже два процесса, выполняю­ щих один и тот же код. Чтобы определить, какой процесс выполняется - роди­ тельский или дочерний, программа должна проверить значение, возвращаемое функцией f o r k ( ) . Если оно равно О , значит, текущий процесс является дочер­ ним. В противном случае программа выполняется в родительском процессе, а возвращенное функцией f o r k ( ) значени е является идентификатором дочернего процесса.

17.З. ое: ПОР'fМРУ8М ЫЙ АОС1УП к ср8АС188М, сnецифическмм Afllf операционных сметем

1 23 1

Листинг 1 7 .68. o s_ki l l_example . py import os impo r t s i gna l imp o rt t ime def s i gnal_us r l ( s i gnum, frame ) : " Фун кци я обр а т н ого вы з ов а , срабатывающа я при получ е н ии сиг н ала " pid os . g e t p i d ( ) p r i n t ( ' Re ce ived USRl i n proce s s { } ' . format ( pi d ) ) =

p r i n t ( ' Fo r k i n g . . . ' ) chi l d_pi d os . fork ( ) i f ch i l d_p i d : print ( ' PARENT : Paus i n g before send i ng s i gn a l . . . ' ) t ime . s l eep ( l ) print ( ' PARENT : S i gna l i ng { } ' . forma t ( chi l d-p i d ) ) o s . k i l l ( ch i l d_pi d , s i gn a l . S I GUSRl ) else : print ( ' CH I LD : S e t t ing up s i gn a l handl e r ' ) s i gnal . s i gn a l ( s i gna l . S I GUSRl , s i gna l_us r l ) print ( ' CH I L D : Paus i n g t o wa i t for s i gnal ' ) t ime . s l e ep ( S ) =

Родительский процесс может посылать сигналы дочернему, используя функ­ цию ki l l ( ) и модуль s i gnal (см. раздел 1 0.2) . Прежде всего следует определить обработчик, который будет вызываться при получении сигнала, затем вызвать функцию fork ( ) и сделать короткую паузу в родительском процессе перед от· правкой сигнала USRl с помощью функции ki l l ( ) . В данном примере короткая пауза используется для того, чтобы предоставить дочернему процессу возмож­ ность установить обработчик сигнала. Реальному приложению вызов функции s l eep ( ) не понадобится. В дочернем процессе следует установить обработчик сигнала и сделать небольшую паузу, чтобы предоставить родительскому процессу достаточно времени для отправки сигнала. $ python3



os _ k i l l_examp l e . py

For k i ng . . . PARENT : Pausing be fore sending s i gnal . . . CHI LD : S e t t i n g up s i gnal handl e r CHI LD : Paus i ng t o wa i t for s i gn a l PARENT : S i gn a l ing 2 9 1 9 3 Recei ved US Rl i n process 2 9 1 9 3

Простой способ обработки отдельного поведения в дочернем процессе заклю­ чается в проверке нозвращаемого зна•1ения функции fork ( ) и использовании оператора ветвления. Для более с.ложного поведения может потребоваться бо­ лее высокая степень разделения кода, чем та, которую может обеспечить простое ветвление. В других случаях может потребоваться обертка для программы. В обе­ их этих ситуациях для выполнения другой программы можно воспользоваться од· ной из функций семейства ехес * ( ) .

Гмве 17. Мнсrруменrы среды времени выnсwtенмя

1 232 Лисrинг 1 7 69 o s_ехес example . ру .

.

_

import os chi l d_pi d os . fork ( ) i f chi l d_pi d : os . wa i t p i d ( chi l d_p i d , 0 ) else : o s . exec l p ( ' pwd ' , ' pwd ' , ' - Р ' ) =

Если программа выполняется функцией ехес ( ) , код этой программы заменяет код, выполняющийся в существующем процессе.

$

pythonЗ o s_exec_examp l e . py

. . . /pymo tw- 3 / s ource / os

В зависимости от того, в какой форме доступны аргументы, должны ли путь и окружение родительского процесса копироваться в дочерний, а также от ряда других факто1юв, могут применяться различные разновидности фуню�ии ехес ( ) . Во всех этих случаях первым аргуме нтом является путь или имя файла, а осталь­ ные ар1-ументы управляют тем, как выполняется программа. Аргументы переда­ ются либо через командную строку, либо посредством изменения окружения про­ цесса (см. описание объекта 0 5 . envi ron и функции 0 5 . ge tenv ( ) ) . За более под­ робной информацией следует обратиться к документации библиотеки.

1 7 . 3 . 1 1 . Ожидание завершения дочерних процессов Для обхода ограничений Pytlюn, связанных с выполнением потоков и гло­ бальной блокировкой интерпретатора, многие интенсивные в вычислительном отношении программы используют несколько процессов. Когда для выполнения нескольких задач запускается несколько процессов, основной процесс должен вы­ жидать, пока один или несколько процессов не завершатся, прежде чем запустить новые, во избежание перегрузки сервера. Для этой цели можно использовать функцию wai t ( ) и родственные ей функции, в зависимости от обстоятельств. Если не имеет значе ния, какой из дочерних процессов завершится первым, следует использовать функцию wai t ( ) . Она возвращает управление, как только завершится любой из дочерних процессов. Лисrинг 1 7.70. o s_wai t_example . py import os import os import sys import t ime for i i n range ( 2 ) : p r i n t ( ' PARENT { } : For k i n g { } ' . forma t ( o s . getpid ( ) , i ) ) wo rker_p i d os . fork ( ) i f not wo r ker_p i d : p r i n t ( ' WORKER { } : S t a r t ing ' . fo rma t ( i ) ) time . s l eep ( 2 + i ) print ( ' WORKER { } : F i n i s h i n g ' . fo rma t ( i ) ) =

s ys . exi t ( i ) for i in range ( 2 ) : print ( ' PARENT : Wa i t i n g for { } ' . fo rma t ( i ) ) done os . wa i t ( ) p r i n t ( ' PARENT : Ch i l d done : ' , done ) =

Функция wai t ( ) возвращает идентификатор процесса и код завершения, упа­ кованный в 1 6-битовое значение. Младший байт представляет номер сигнала, прекратившего выполнение процесса, а старший - код состояния , возвращен­ ный процессом по его завершении. $ p ythonЗ -u os _wa i t_exampl e . py PARENT 2 9 2 0 2 : For king О PARENT 2 9 2 0 2 : Forki ng 1 PARENT : Wa i t i ng for О WORKER О : S t a r t i ng WORKER 1 : S t a r t i ng WORKER О : Finishing PARENT : Ch i l d done : ( 2 92 0 3 , 0 ) PARENT : W a i t i n g for 1 WORKER 1 : Fi n i shing PARENT : Chi l d done : ( 2 92 0 4 , 2 5 6 )

Дл я ожидания завершения конкретного процесса следует использовать функ­ цию wai tpid ( ) . Листинг 1 7 . 7 1 . os_wai tpid_example . py import o s imp o r t s y s impo r t t i me workers [] f o r i in range ( 2 ) : p r i n t { ' PARENT { } : Forking { } ' . fo rma t ( o s . getpid ( ) , i ) ) worker_pi d os . for k ( ) i f n o t worker_p i d : p r i n t ( ' WORKER { } : S t a r t i ng ' . forma t ( i ) ) t ime . s l e ep ( 2 + i ) p r i n t ( ' WORKER ( } : Fi n i s h ing ' . f o rma t { i ) ) s ys . exi t ( i ) wo rkers . append ( wor ker_pi d ) =

=

for p i d i n wo r kers : p r i n t { ' PARENT : Wa i t ing for ( } ' . format ( pid ) ) done = os . wa i tp i d { p i d , 0 ) p r i n t ( ' PARENT : Chi l d done : ' , done )

Функция wai tpid ( ) , которой передан идентификатор целевого процесса, бу­ дет блокироваться до тех нор, пока процесс не завершится.

Гl\888 17. Иисrрумееnы сре»1 времени выnо.vtенмя

1 234

$ python3

-u

os_wa i tp i d_ex amp l e . py

PARENT 2 92 1 1 : For ki n g о PARENT 2 9 2 1 1 : Fo r ki ng 1 PARENT : Wa i t i ng for 2 9 2 1 2 WORKER О : S t a r t i n g WORKER 1 : S t a r t i ng WORKER О : Fi n i s h i ng PARENT : Chi l d done : ( 2 9 2 1 2 , 0 ) PARENT : Wa i t ing for 2 92 1 3 WORKER 1 : Fini s hi ng PARENT : Chi l d done : ( 2 9 2 1 3 , 2 5 6 )

Функции wai t З ( ) и wa i t 4 ( ) работают аналогичным образом, однако возвра­ щают более подробную информацию о дочернем процессе, включающую иденти­ фикатор процесса, код завершения и информацию о потреблении рее­ шенным количеством одновременно открытых файлов, устанавливая для него слабое ограничение на уровне ниже того, который предусмотрен по умолчанию. $ python3 resour ce_se t rl imi t_ n o f i l e . py S o f t l imi t S o f t l imit ra ndom has [ Errno 2 4 ]

s t a r t s as : 7 1 6 8 changed t o : 4 fd 3 Тоо many open f i l e s : =

' / dev/ nu l l '

17.6. resource: уnра11МНие системными ресурсами

1 243

Также полезно ограничивать количество времени CPU, вьщеляемое процессу для выполнения, чтобы не допустить чрезмерно длительного владения этим ре­ сурсом. Если длительность выполнения процесса превышает установленный пре­ дел, ои получает сигнал S I GXCPU. Листинг 1 7 .81 . resource_ setr l imi t_cpu . ру

import import import import

resource sys s i gn a l t ime

# Установить обра ботчи к сигнала , ув едомляющий # о пре вышении выделенного лимита проце с с орного времени de f t i me_expi red ( n , s t a c k ) : p r i n t ( ' EX P I RE D : ' , t ime . ct ime ( ) ) ra i s e Sys temEx i t ( ' ( t ime ran out ) ' ) s i gnal . s i gnal ( s i gnal . S I GXC PU , time_expi red ) # Настроить лими т времени C PU s oft , hard resource . ge t r l imi t ( resource . RLIMIT_C PU ) print ( ' S o f t l imi t s t a r t s a s : ' , soft ) =

resource . se t r l imi t ( resource . RLIMI T_C PU ,

( 1 , ha r d ) )

so ft , hard res ource . ge t r l imi t ( resource . RLIMIT CPU ) _ print ( ' S o f t l imi t changed t o : ' , s o f t ) print ( ) =

# Израсходовать некоторое количество времени C PU # для проведения дли тель ных вычислений в ци кле print ( ' S t a r t i ng : ' , t ime . ct ime ( ) ) for i i n range ( 2 0 0 0 0 0 ) : for i in range ( 2 0 0 0 0 0 ) : v = i * i # Эта инс трукци я не будет достигнута print ( ' Ex i t in g : ' , t ime . ct ime ( ) )

Обычно обработчик сигнала сбрасывает на диск содержимое всех открытых ф айлов и закрывает их, 110 в данном случае он всего лишь выводит сообщение и осуществляет выход из программы. $ python 3 resource_se t r l imi t_cpu . py S o ft l imi t s t a r t s as : 92 2 3 3 7 2 0 3 6 8 5 4 7 7 5 8 0 7 S o f t l imi t changed to : 1 S t a r t i n g : S un Aug 2 1 1 9 : 1 8 : 5 1 2 0 1 6 EX PIRED : Sun Aug 2 1 1 9 : 1 8 : 5 2 2 0 1 6 ( t ime ran out )

Гмва 17. ИнсrрумеН1Ъ1 среды времени выnсwtенмя

1 244 Дополнительные ссылки •

Раздел документации стандартной библиотеки, посвященный модулю resource27 •



s i gn a l (раздел 1 0.2). Подробное описание процедуры регистрации сигнала.

1 7.6.

gc :

сборщик мусора

Модуль gc предоставляет базовый механизм управления памятью в Pytlюn автоматическую сборку мусора. Он включает функции, позволяющие принуди­ тельно вызывать сборщик мусора и исследовать известные системе объекты, ко­ торые либо намечены для удаления, либо связаны циклическими ссыл ками и не могут быть освобождены.

1 7 .6. 1 . Отслеживание ссылок Анализируя существующие между объектами входящие и исходящие ссылки, модуль gc способен выявлять наличие циклических ссылок в сложных структу­ рах данных. Если речь идет о цикли•1еских ссылках в изв =

В следующем примере для обнаружения циклических ссылок выполняется обход всех объектов с применением алгоритма поиска "в ширину" (l>rea gc : c o l l e c t a Ы e CleanupGraph ( fou r ) . del () () CleanupGraph ( f i ve ) . de l Done Re t a i ned : Graph ( one ) Ох 1 0 1Ье7 2 4 0 Re t a i ned : Graph ( two ) Ох 1 0 1Ье 7 2 е 8 Re t a i ned : C l e anupGraph ( four ) Ох 1 0 1Ье 7 З с 8 Ret a i n ed : C l eanupGraph ( f i ve ) Ох 1 0 1Ье 7 4 0 0

Для простоты определен флаг всех остал hных nпций. Листинг 1 7.90. gc_debug_leak . py impo rt g c flags

=

g c . DEBUG LEAK _

g c . s e t_debug ( fl a g s )

c l a s s Graph : def

init ( se l f , name ) : s e l f . name name =

DEBUG LEAK, _

представляющий комбинацию

17.6. gc: сборщик мусора se l f . next

=

1 257

None

de f s e t_n ext ( s el f , next ) : se l f . ne xt next =

de f

rep r ( s el f ) : return ' { } ( { } ) ' . format ( sel f . class name _

_

, s e l f . name )

c l a s s C l e a nupGr aph ( Graph ) : de f

del ( self) : p r i nt ( ' { } . del_ ( ) ' . format ( s e l f ) ) _

# Создать циклический граф one = Graph ( ' one ' ) two Graph ( ' two ' ) one . se t_ne x t ( two ) two . set ne x t ( one ) _ =

# Создать другой , независимый узел three Cl eanupGraph ( ' three ' ) =

# Создать цикличе с кий граф с помощь ю финализа тора four CleanupGraph ( ' four ' ) f i ve = Cl eanupGraph ( ' fi ve ' ) fou r . set_next ( f ive ) f i ve . set_next ( fou r ) =

# Удалить ссыл ки на узлы графа в простра нстве име н этого модул я one = t wo t h ree four five None =

=

=

=

# Принудитель н а я сборка мусора p r i n t ( ' Co l l ec t i ng ' ) g c . col lect ( ) p r i n t ( ' Done ' ) # Выве сти информацию об оставшихся объектах for о i n g c . ga rbag e : i f i s i n s t a n ce ( o , Graph ) : p r i nt ( ' Retai ned : { } O x { : x } ' . fo rma t ( o , i d ( o ) ) ) # Сбро сить фла г и о тладки перед выходом в о из бежание дампа # чре змерно боль шого количества информации, что сделало бы # пример ме нее пон ятным g c . se t_debug ( O )

Имейте в виду, что флаг DEBUG_LEAK автоматически включает флаг DEBUG_ SAVEALL, в связи с чем будут сохранены даже те объекты, на которые отсутствуют ссылки и которые в обычных условиях были бы затребованы сборщиком мусора и в конечном счете удалены.

Гм ва 17. ИнсrруменtЫ CJ18AW времени ВЫПОАНения

1 258 $ pythonЗ -u g c._debug_l ea k . py

C l eanupGraph ( three ) · �del� ( ) Collecting gc : col l e ctaЫ e g c : col l e c t a Ы e g c : col l e c t a Ы e g c : col l e c t aЫ e g c : col l e ct a Ы e gc : col l e c t aЫe < Cle a nupGraph О х 1 0 1 З е 7 4 0 0 > g c : col l e c t a Ы e g c : col l e c t a Ы e C l e anupGraph ( four ) · �del� ( ) C l eanupGraph ( five ) . del () Done Re t a i ned : Graph ( one ) Ох 1 0 1 З е 7 2 4 0 Retai ned : Graph ( two ) Ох 1 0 1 З е 7 2 е 8 Retai ned : C l ea nupGraph ( four ) О х 1 0 1 З е 7 3 с 8 Re t a i ned : C l e a nupGraph ( f i ve ) О х 1 0 1 Зе 7 4 0 0 Дополнительные ссылки •

Раздел документации стандартной библиотеки, посвященный модулю g c28 •



Замечания относительно портирования программ из Pythoп 2 в Pythoп 3, касающиеся модуля gc (раздел А.6. 1 6).



wea k r e f (раздел 2.8). Модуль wea kref позволяет создавать ссылки на объекты без увеличения их счетчиков ссылок, и поэтому они моrут удаляться сборщиком мусора. Supportiпg Cyc/ic Garbage Co//ectioп29• Раздел дополнительной документации Pythoп, по­



священный С API . •

How does Python manage тетоrу? (Fredrik Luпdh) 30 . Статья, посвященная управлению памятью в Pythoп.

1 7 .7 .

управление конфигурацие й интерпретатора во время компиляции sys conf iq:

Средства, входящие в модуль sysconfig, были перенесены и з модуля di s tut i l s с целью выделения их в виде независимого модуля. Этот модуль вклю­ чает функции, которые определяют настройки, используемые для компиляции и установки текущего интерпретатора.

1 7. 7 . 1 . Конфигурационные переменные Доступ к конфигурационным параметрам времени сборки предоставляют две функции: get_ config_vars ( ) и get_config_va r ( ) . Функция ge t _confi g_ 28 h t tps : / /doc s . python . or g / 3 . 5 / l ibra r y / g c . h tml 2 9 h t tps : / / doc s . python . o r g / 3 / c-api / gc support . html 3 0 h t tp : / / e f fbot . org /pyfaq/how-does-python-manage-memo r y . h tm

17.7. sysconftg: уnравмние конфиrурацией ИtnерПреТ111Ор8 80 время КОМПIWЩИИ

1 259

var s ( ) возвращает словарь, сопоставляющий имена конфигурационных пере­ менных с их значениями. Листинг 1 7.91 . sys config_ge t_config_vars . ру import sysconfi g con f i g va lues = s y s c o n f i g . get - con f i g vars ( ) print ( 1 Found { } confi gurat i o n s et t i ng s ' . forma t ( l e n ( con f i g_va lues . keys ( ) ) ) ) p r i n t ( ' \ nS ome h i gh l i ght s : \ n ' ) p r i n t ( ' I n s t a l l a t i on prefixes : ' ) p r i n t ( ' prefix= { p r e f i x } ' . format ( * * con f i g_va lue s ) ) print ( ' exec_p r e f i x= { exec_p re f i x } ' . fo rmat ( * * co n f i g_va lue s ) ) p r i n t ( ' \ n Ve r s i on i n fo : ' ) print ( ' ру ver s i on= { py ve r s i on } ' . format ( * * con f i g va l ue s ) ) p r i n t ( ' py v e r s i o n sho� t= { py_ve r s i on_ short } ' , f or ma t ( _ * * co n f i g_va l ue s ) ) p r i n t ( ' py_ve r s i o n_nodot= { py_ve r s i on_nodot } ' . forma t ( * * co n f i g_va lue s ) )

=

print print pri n t print print

( ( ( ( (

' \ n Base di rectori e s : ' ) ' base= { ba s e } ' . format ( * * con f i g_values ) ) ' p l atbase= { pl a tbase } ' . forma t ( * * c o n f i g va l ue s ) ) ' userba s e • { u s e rbase } ' . forma t ( * * c o n f i g values ) ) ' s rcdir= { s rcdi r } ' . forma t ( * * con f i g_va lues ) )

=

p r i n t ( ' \ n Compi l e r and l inker fl a g s : ' ) print ( ' LDFLAGS= { LDFLAGS } ' . format ( * * c o n f i g_va l ue s ) ) BAS EC FLAGS= { BAS EC FLAGS } ' . f ormat ( * * con f i g_va l ues ) ) print ( ' print ( ' Py_ENABLE_S HARE D= { Py_ENABLE_SНARED } ' . forma t ( * * config_va lue s ) )

Уровень детализации, доступный через API sysconfig, зависит от платфор­ мы, на которой выполняется программа. В случае РОSIХ- " , l i ne 1 , i n =

ГА888 18. Инструме1nы R3ЫIC8

1 280 NameEr ror : name > » d i s . dis ( ) 1 --> О 3 6 7 10 13 >>>

' i ' i s not def i ned LOAD NАМЕ LOAD_CONST BINARY ADD STORE NАМЕ LOAD CONST RETURN VALUE

о (i) о (4) о (i) 1 ( None )

Стрелка ( - - > ) после номера строки указывает на опкод, в котором произошла ошибка. Переменная i не определена, 11оэтому связанное с ней значение не мо­ жет быть загружено в стек. Программа также может выводить и11формацию об активном стеке вызовов, передавая его непосред(�твснно функции distb ( ) . В с.ледующем примере иденти­ фицируется исключение Di videByZero, но поскольку формула включает две опt.'­ рации деления, не всегда очевидно, какой именно элемент имеет нулевое значение. Листинr 1 8.26. di s_traoeЬaok . py 1 # ! / u s r /b i n / env python3 2 # encoding : ut f - 8 3 1 4 i о 5 j 6 k 3 7 8 try : 9 result k * (i / j ) + ( i / k) 1 0 except : 11 import di s import s y s 12 13 ex c_t yp e , exc_va l u e , exc_tb = sys . exc_info ( ) 14 d i s . d i s tb ( exc_tb ) =

=

Ошибку легко локализовать, если она загружена в стек в дизассемблированном виде. Некорректная операция указана посредством стрелки ( - - > ) , а предыдущая строка 11омещает в стек значение j . $ python3 di s_t r a ceba c k . py 4

о LOAD_CONST 3 STORE NАМЕ

о (1) о (i)

5

6 LOAD CONST 9 STORE NАМЕ

1 (0) 1 (j )

6

1 2 LOAD CONST 1 5 STORE NАМЕ

2 (3) 2 ( k)

8

1 8 S ETUP ЕХСЕ РТ

9

2 1 LOAD NАМЕ

2 6 ( to 4 7 ) 2

( k)

1291 24 27 30 31 32 35 38 39 40

-- >

LOAD NАМЕ LOAD NАМЕ В I NARY T RU E DIVI DE В I NARY MULT I PLY LOAD NАМЕ LOAD NАМЕ BINARY TRUE DI VI DE BINARY ADD STORE NАМЕ

о (i) 1 (j )

о (i) 2 ( k)

3 ( resul t )

. . . продолжение вывода . . .

1 8.3.6. Анализ производительности циклов Модуль dis способен помочь не только при отладке ошибок, но и при реше­ нии проблем с производительностью. Исс.ледование дизассемблированного кода особенно полезно в случае критических циклов, когда количество инструкций не­ велико, но они транслируются в неэффективный набор байт-кодов. В пользе ди­ зассемблирования нетрудно убедиться на примере исследования нескольких раз­ личных реализаций класса D i c t i onary, читающего список слов и группирующего их по первой букве. Листинг 1 8.27. di s_te s t_loop . py import import import import

dis з уs tex twrap t ime i t

modul e_name sys . a rgv [ l ] module �impo rt� ( modul e_name ) D i c t i onary modu l e . D i c t i onary =

=

=

d i s . di s ( Di c t i onary . l oad_da t a ) print ( ) t time i t . Time r ( 'd Di c t i onary ( words ) ' , textwrap . dedent ( " " " from { modu l e_name } import D i c t i onary wo rds [ l . strip ( ) for 1 i n open ( ' / u s r / s h a re /d i c t /words ' , =

=

=

' rt ' )

" " " ) . fo rma t ( modul e_name=modul e_name ) i te r a t i o n s 10 p r i n t ( ' T IME : { : 0 . 4 f } ' . fo rma t ( t . t ime i t ( i t e r a t i ons ) / i t e ra t i ons ) ) =

Чтобы протестировать каждый из вариантов реализации класса Dict ionary, используем приложение d i s test loop . р у , начав с самого простого, но медлен­ ного варианта. _

_

ГАава 18. ИнсwрумеtПЫ RЭЫКI

1 292 Листинг 1 8.28. di s_slow_loop . py 1 # ! /u s r /bi n/env pytho n 3

2 # encodi ng : u t f - 8

3

4 5 c l a s s Di c t i onary :

6 7

8 9

10 11

12

13

14 15

16

def

ini t ( se l f , words ) : s el f . by_l e t t e r {} s el f . l oad_da t a ( words ) =

def l oad_da t a ( s e l f , words ) : for word i n word s : t ry : s e l f . by_l e t t e r [ wo rd [ O ] ] . append ( wo rd ) except KeyErro r : s e l f . by_l e t te r [ word [ O ] ] [word] =

Ниже приведен ы резуJJьта1ъ1 тестирования этой версии, отображающие ди­ зассемблированный код программы и время, которое ушло на ее выполнение. $ p y t hon3 d i s_t e s t_l o op . py di s_s l ow_l oop 12

>>

О 3 6 7 10

SETUP LOOP LOAD FAST GET I T ER FOR I T ER STORE FAST

13

1 3 S ETUP ЕХСЕ РТ

14

1 6 LOAD FAST 1 9 LOAD ATT R 2 2 LOAD_FAST 2 ( word ) 2 5 LOAD CONST 1 ( 0 ) 2 8 B I NARY SUBSCR 2 9 B I NARY SUBSCR 3 0 LOAD ATT R 3 3 LOAD FAST 3 6 CALL FUNCT I ON

keyword pa i r )

15

>>

3 9 РОР ТОР 4 0 РОР BLOCK 4 1 JUMP ABS OLUTE 4 4 DU P ТОР 4 5 LOAD GLOBAL 4 8 COMPARE ОР 5 1 РОР- JUМP- I F- FALS E 5 4 РОР ТОР 5 5 РОР ТОР 5 6 РОР ТОР

83

( to 8 6 ) 1 ( words )

75 2

( to 8 5 ) ( word )

28

( to 4 4 )

о ( s el f ) о ( b y_ l e t t e r )

1

( append ) ( word ) 1 ( 1 po s i t i ona l , о

2

7 2 ( KeyErro r ) 1 0 ( except i on rna t c h )

81

18.3. dls: диавс:семб.\ироеенме беiiт-коА& Pyttюn 57 60 63 66 69 72 75 76 77 78 81 82 85 86 89

16

>> >> >>

LOAD FAST BUI L D L I S T LOAD FAST LOAD ATT R LOAD FAST LOAD CONST B I NARY SUBSCR STORE SUBSCR РОР ЕХСЕРТ JUMP ABSOLUTE END FINALLY JUМP ABS OLUTE РОР BLOCK LOAD_CONS T RETURN VALUE

1 293 2 1 О О 2

1

( word ) ( se l f ) ( by_l ette r ) ( word )

(0)

7 7 О ( None )

T I ME : 0 . 0 5 6 8

Как следует и з этих результатов, для загрузки 235886 слов в экземпляр словаря /u s r / share /dict/words в системе OS Х программе di s_s l ow_l oop . py потребо­ валось 0,0568 секунды. Это не так уж плохо, но, как п о казало дизассемблирова­ ние, цикл вып олняет лишнюю работу. При входе в цикл в опкоде 1 3 программа настраивает контекст исключения (SETUP_ЕХСЕРТ) . Затем для поиска элемента se l f . by_letter [ word [ O ] ] используются шесть опкодов, прежде чем слово при­ соединится к списку. Если причиной возникновения исключения является тот факт, что элемент wo rd ( О ] еще пе находится в словаре, то обработчик исю1юче­ ний проделывает ту же самую работу, чтобы определить wo rd [ О ] (три опкода) и задать в переменно й sel f . by_ let ter [ word [ О ] ] новый список, содержащий дан­ ное сло во. Один из способов, позволяющ их избавиться от настройки исключения , заклю­ чается в предварительном заполнении атрибута s e l f . by_ letter списками, по одн о му для каждой буквы алфавита. Это означает, что поиск списка для нового слова всегда будет успешным и завершаться сохранением слова. Листинг 1 8.29. di s_fas ter_loop . py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

* ! / u s r /bin / env python3 * encodi ng : u t f - 8 import s t r ing

c l a s s D i c t i on a ry : def

init ( s e l f , wo rds ) : se l f . by_l e t te r = { letter : [ ] for l e t t e r i n s t r i ng . a s c i i_l etters s e l f . l oad_da t a ( wo rds )

def l o a d_dat a ( se l f , words ) :

ГАа 88 18. Инсrрум8Н1'Ы 1131111СВ

1 294 17 18

f o r wo rd i n words : se l f . by_ l et t e r [ wo rd [ O ] ] . append ( wo rd )

В результате внесения изменений количество соответствующих опкодов уда­ ется уменьшить наполовину, но результирующее время снижается всего лишь до 0,0567 секунды. Очевидно, что обработка исключений вносит дополнительные накладные расходы, хотя они не очень велики. $ python3 d i s_t e s t_l oop . py di s_fa s t e r_l oop 17

>>

18

о 3 6 7 10

SETUP LOOP LOAD FAS T 1 ( wo rds ) GET ITER FOR I TER STORE FAST

13 16 19 22 25 26 27 30 33

LOAD FAST LOAD ATTR LOAD FAST LOAD CONST BI NARY SUBSCR BI NARY SUBSCR LOAD ATTR LOAD FAST CALL FUNCT I ON

36 37 40 41 44

РОР ТОР JUMP ABS OLUTE РОР BLOCK LOAD CONST RETURN VALUE

38 ( to 4 1 )

3 0 ( to 4 0 ) 2 ( wo rd ) о о 2 1

(self) ( b y_let t e r ) ( wo rd ) (0)

1 ( appe nd ) 2 ( wo rd ) 1 ( 1 posi t i ona l , о

keyword pa i r )

>> >>

7 о ( None )

T I ME : 0 . 0 5 6 7

Производителыюсть можно дополнительно повысить, если вынести поиск для sel f . by_letter за пределы цикла. Листинг 1 8. 30. dis _faste s t_loop . py

1 2 3 4 5 6 7 8 9 10 11 12 13 14

# ! / u s r / b i n / env python3 # encodi ng : ut f- 8 import c o l l e ct i ons

c l a s s D i c t iona ry : def

init ( s e l f , words ) : se l f . by_l e t t e r ; col l e c t i on s . defaul tdict ( l i s t ) s e l f . l oad_da t a ( wo rds )

def load_da ta ( s e l f , words ) : by_l e t t e r s e l f . by_l e t t e r

18.Э. dls: АМ311ссем61\мрование байт-кода Python 15 16

1 298

f o r word i n words : by_l e t t e r [word [ D ] ] . append ( wo rd )

Теперь опкоды 0-б определяют значение атрибута s e l f . b y_letter и сохраня­ ют его в локальной переменной by_letter. Использование локальной перемен­ ной требует всего лишь одно1·0 опкода вместо двух ( инструкция 22 использует команду LOAD FAST для помещения словаря в стек ) . После внесения этого измене­ ния время выполнения программы сократилось до 0,0473 секунды. _

$ python3 di s_t e s t_loop . py di s_f a s t e s t l oop 14

D LOAD FAST 3 LOAD АТТR 6 STORE FAST

15 >>

9 12 15 16 19

SETUP LOOP LOAD FAST GET ITER FOR ITER STORE FAST

1 6 2 2 LOAD FAST 2 5 LOAD-FAST 2 8 LOAD CONST 3 1 B I NARY SUBSCR 3 2 ВI NARY SUBSCR 3 3 LOAD ATTR 3 6 LOAD FAST 3 9 CALL FUNCT I ON keyword pa i r ) 4 2 РОР ТОР 4 3 JUMP ABSOLUTE >> 4 6 РОР BLOCK >> 4 7 LOAD CONST T IME : 0 . 0 4 7 3

о ( sel f ) о ( by_l e t t e r ) 2 ( by_l e t t e r ) 35 ( to 4 7 ) 1 ( wo rd s ) 2 7 ( to 4 6 ) 3 ( wo rd ) 2 ( by_ l e t t e r ) 3 ( word ) 1 (0) 1

( append ) 3 ( word ) 1 ( 1 p o s i t i onal , о

16 о ( None )

5 0 RETURN VALUE

Дополнительная оптимизация , предложенная Брэндоном Роудсом, заключа­ ется в то м , чтобы полностью исключить из этш·о кода Руtlюн-всрсию цикла for. Использование функции i tertool s . groupby ( ) для упорядочения входных дан­ ных позволяет вынести итерации в С. Этот переход безопасен , поскольку извест­ но, что входные данные поступают отсортированными. Если бы это было пе так, то программа должна была бы предварительно сортировать их. Листинг 1 8.31 . di s_eliminate_loop . py

1 # ! / u s r / bi n / env python3 2 # encoding : u t f - 8 3 4 irnport ope rat o r 5 import i t e rt ool s 6

ГА888 18. MHC'IPYМetnbl Я3ЫIС8

1 296 7

В c l a s s D i c t i ona ry : 9 init ( s e l f , words ) : 10 def s e l f . by_l e t t e r = { } 11 s e l f . l oad_dat a ( wo rds ) 12 13 def l oad_da t a ( s e l f , word s ) : 14 15 # У по рядочить сло ва grouped = i te r t o o l s . g roupby ( 16 words , 17 18 key=ope rat o r . i t emget t e r ( O ) , ) 19 20 # Сохра н ить упорядоче нны е н а боры слов s el f . by_l e t t e r { 21 g roup [ O ) [ О ) : g roup 22 for g roup in g rouped 23 24 =

Версия , использующая модул ь i t ertoo l s , выполняетс я всего лишь 0,0332 се· кунды, что составляет п риблизительно 60% от времени выполнения пер во на чального варианта программы.

·

$ python3 d i s_t e s t_l oop . p y d i s_e l imi n a t e_loop 16

17

18

о LOAD GLOBAL

о ( i tertool s )

3 LOAD ATTR

1 ( g roupby )

6 LOAD FAST 9 LOAD CONST

1 ( wo rds ) 1 ( ' key ' )

12 15 18 21

LOAD LOAD LOAD CALL

GLOBAL ATTR CONST FUNCT I ON

2 3 2 1

( ope rat o r ) ( i t emge t t e r ) (0) ( 1 po s i t i ona 1 , о

keyword pa i r ) 2 4 CALL FUNCT I ON

2 5 7 ( 1 posi t i ona l ,

1

keywo rd pa i r ) 2 7 STORE FAST

2 ( g rouped )

3 0 LOAD CONST 21 3 ( a t Ох 1 0 1 5 1 7 9 3 0 , f i l e " . . . /d i s_e l iminate_l oop . p y " , l i ne 2 1 > ) 3 3 LOAD CONST 4 ( ' Di c t ionary . l oad_da t a . < l o c a l s > . ' ) 3 6 МАКЕ FUNCT I ON О 23

3 9 LOAD FAS T 4 2 GET ITER 4 3 CALL FUNCT I ON -

keywo rd pa i r ) 4 6 LOAD FAST 4 9 STORE ATTR

2

( g rouped )

1

( 1 posi t i on a l , о

о ( se l f ) 4

( b y_l e t t e r )

1207

18.З. dls: АИ38СС8М&мрование баЙ1ЧССАВ Python 52 LOAD CONST 55 RETURN VALUE

О ( N one )

T I ME : 0 . 0 3 3 2

1 8.З. 7. Оптимизация, выполняемая компилятором Дизассемблирование скомпилированного исходного кода предоставляет воз­ можность ознакомиться с некото р ыми видами оптимизации, выполняемой ком­ пилятором. Например, литеральные выражения по возможности свертываются в процессе компиляции. Листинr 1 8.32. di s_cons tant_folding . py 1 # ! /us r/bin/env pyt hon3 2 # encoding : ut f - 8 3 4 # Свертыв аютс я 1 + 2 5 i 3.4 * 5.6 6 f 7 s ' He l l o , + 1 World ! ' 8 9 # Не с вертывают с я 10 I i * 3 * 4 11 F f / 2 / 3 12 s s + ' \ n ' + ' Fanta s t i c ! ' 1

==

==

Ни одно из значений в выражениях в строках 5-7 не может изменить способ выполнения операции, поэтому результат каждого из выражений может быть вы•шслен на стадии компиляции и свернут в одну инструкцию LOAD_CONST. В от­ личие от этого выражения в строках 1 0-12 включают переменные, а поскольку переменная может ссылаться на объек1; перегружающ ий оператор, вы•шсление выражения должно быть отложено до стадии выполнения. $ python3

-m

dis di s_cons t ant_fo lding . py

5

о LOAD CONST 3 STORE NАМЕ

11 ( 3 ) о (i)

6

6 LOAD CONST 9 STORE NАМЕ

12 ( 1 9 . 04 ) 1 ( f)

7

1 2 LOAD CONST 1 5 STORE NАМЕ

10

18 21 24 25 28 29

LOAD NАМЕ LOAD CONST B I NARY MULT I PLY LOAD CONST B I NARY MULT I PLY STORE NАМЕ

1 3 ( ' He l l o , Wo rl d ! ' ) 2 (s) о (i) 6 (3)

7 (4) 3 (I)

ГА888 18. Инс:rрумеиrы NЫКВ

1 298 11

12

32 35 38 39 42 43

LOAD NАМЕ LOAD CONST BINARY TRUE D I V I DE LOAD CONST B I NARY TRUE DIVI DE STORE NАМЕ

46 49 52 53 56 57 60 63

LOAD NАМЕ LOAD CONST BINARY ADD LOAD CONST B I NARY ADD STORE NАМЕ LOAD CONST RETURN VALU E

1 (f) 1 (2) 6 (3) 4

( F)

2 (S) в ( ' \n ' ) 9 ( ' Fa n t a s t i c ! ' ) 5 (S) 1 0 ( None )

Дополнительные с сыл ки •

Раздел документации стандартной библиотеки, посвященный модулю d i s9 • Содержит список инструкций байт-кода 1 0 •



lncludelopcode. h . Файл исходного кода для интерпретатора CPythoп, содержа щий инструкции байт-кода.



Python Essential Reference, Fourth Edition, Ьу David М. Beazley. Сайт thoma s . a p e s t a a r t . org: Python DisassemЬ/y1 1 • Краткое обсуждение различий между версиями Pythoп 2.5 и 2.6 в отношении сохранения значений в словарях. Why is looping over range() in Python faster than using а while /оор ? 1 2 П риведенный на





сайте StackOverflow сравнительный анализ двух п римеров использования цикл ов с привлечением их дизассемблированных байт-кодов. •

Decorator for Binding Constants at Compile Тime (Python Recipe) (Raymoпd Hettiпger, Skip Montaпaro) 13 • Декоратор функций, переписывающий байт-код функции посредством вставки констант, чтобы избежать поиска имен во время выполнения.

1 8 .4.

i n spe c t :

инспектирование активных

объектов

Модуль inspect предоставляет функции, позволяющие получать сведения об активных объектах, включая модули, KJiaccы, экзем пляры, функции и методы. Функции, входящие в состав это1·0 модуля, могут применяться для извле•1е11ия оригинальною исходного кода функции, просмотра ар1уме11тов метода в стеке и извлечения информации, которую можно ис пользовать для создания библиотеч­ ной документации исходного кода. 9

https : / / docs . pyt hon . o r g / 3 . 5 / l ibrary / d i s . html 1 0 https : / / do c s . python . o rg / 3 . 5 / l ibrary / di s . h tml #python-byt e c ode - i n s t ruct i on s 1 1 h t t p : / / t homa s . a p e s t a a r t . o rg / l o g / ?p= 9 2 7 1 2 http : / / s t a ckove r f l ow . com / que s t i ons / 8 6 92 2 9 / why- i s - l ooping-ove r - r a nge - i n ­ python - f a s t e r t h a n-using-a-wh i le - l oop 1 3 http : / / code . a c t i ve s t a t e . com / recipes / 2 7 7 9 4 0 /

18А. lмpect: инсnекnерование вкntвных о61оектов

1 289

1 8.4. 1 . Образец модуля для примеров В примерах из этого раздела используется ф айл модуля exarnple . ру. Листинг 1 8.33. example . ру # Th i s coпunent appears f i rst # a nd spans 2 l i n e s . # T h i s c oпunent does not s how up in the output of g e t c oпunent s ( ) . " " " Samp l e f i l e to s e rve as the ba s i s for i n spect examp l e s . "" "

de f modu l e_l eve l_fun c t i on ( a rg l , arg2= ' defau l t ' , * a rg s , * * kwa rgs ) : " " " T h i s funct i on i s d e c l a red i n t h e modul e . " " " l o ca l_va riaЫ e argl * 2 return l ocal variaЫe =

class A ( obj ec t ) : " " " T he А c l a s s . " " " de f

init ( s e l f , name ) : s e l f . name name =

def ge t_name ( s e l f ) : " Returns the name of the i n s t a nce . " return s e l f . name

i n s tance of а

A ( ' sampl e_i nstance ' )

class В (А) : " " " T h i s i s the В c l a s s . I t i s de rived from А .

# T h i s me thod i s not p a r t o f А . d e f do_something ( se l f ) : " " " Does s ome wo r k " " " de f get name ( s e l f ) : " Ov rrides ve r s i on from А" return ' В ( ' + s e l f . name + ' ) '

e

1 8.4.2. Инспектирование модулей Первым типом инспектирования, который м ы рассмотрим, будет сбор ин фор­ мации об активных объектах. Функция getmemЬers ( ) позволяет получать сведе· ния об атрибугах объектов. Тип элементов, которые могут возвращаться, зависит

1 300

от типа сканируемого объекта. Модули могут содержать классы и функции, клас­ сы могут содержать методы и атрибуты и т.д. Аргументами функции getmemЬe r s ( ) являются инспектируемый объект ( мо­ дуль, класс или экасмпляр) и необяаатсльная функция-предикат, используемая для фиJн>rрации вознращаемых объектов. Возвращаемым значением является список кортежей, содержащих два значения: имя и тип элемента. Модуль inspect включает несколько функций-предикатов с именами наподобие i smodule ( ) , i s c l a s s ( ) и т.п. Листинг 1 8.34. inspect_getmemЬers_module . ру import i n spe c t import examp l e f o r name , da t a i n i n spect . getmemЬe r s ( exampl e ) : i f name . s t a r t sw i t h ( ' ') : cont i nue p r i nt ( ' { } : { ! r } ' . forma t ( name , dat a ) )

В этом примере программа выводит элементы модуля example. Модули имеют 11ескол1.ко закрытых атрибутов, которые испольэуются в качестве части реализа­ ции импорта, а также набор _bui l t in s_. Все они игнорируются в вы воде дан1юго примера, поскольку не являются частью собственно инспектируемого моду­ ля , а ИХ СПИСОК ДОВОЛЬНО ДЛИННЫ Й . $ pythonЗ i n spe c t_getmemЬe rs_modu l e . py :

< c l a s s ' examp l e . A ' > < c l a s s ' examp le . B ' > i n s ta n ce_o f_a : module level fun c t i o n : < func t i on module - l eve l - func t i on a t Ох 1 0 1 4 8 Ь с 8 0 > А

В :

Аргумент predi cate можно ис11ольаова·1ъ для фильтрации воэвращаемых объ­ ектов. Листинг 1 8.35. inspect_getmemЬers_module_clas s . ру import i n spe c t import examp le for name , da t a in i n spect . ge tmemЬe r s ( examp l e , i n spect . i s c l a s s ) : p r i nt ( ' { } : { ! r } ' . forma t ( name , d a ta ) )

'Iеперь в вывод включены лишь классы. $ pyt honЗ i n spec t_ge tmemЬers_modul e_cl a s s . py А В

< c l a s s ' e xamp l e . A ' > < c l a s s ' example . B ' >

18.4. lnspect: мнсnектмроеанме акntвнwх о6ьекrов

1301

1 8.4.З. Инспектирование классов Функция getmemЬers ( ) позволяет инспектировать классы точно так же, как и модули, хотя типы элементов в этом случае будуг другими. Листинг 1 8.36. inspect_getmemЬers_clas s . ру

import i nspe ct from pp rint import pprint import examp l e pp r i n t ( i n spe c t . ge tmemЬe rs ( examp l e . A ) , width=6 5 )

Поскольку ф ильтрация не применяется , тоды и другие члены класса.

в вы

воде отображаются атрибуты, ме­

$ pythonЗ i n spec t_ge tmemЬe rs_c l a s s . py [ ( ' _c l a s s_ ' , < c l a s s ' t ype ' > ) , (' de l a t t r ' < s l o t wrapper ' de l a t t r ' o f ' obj e c t ' obj e c t s > ) , ( ' dict mappi ngproxy ( { ' d i c t ' : < a t t ribute ' dict ' of ' А ' obj e c t s > , doc ' : ' The А c l a s s . ' , init ' : , module ' : ' ex amp l e ' , we a kre f ' : < a t t ribute ' wea kre f ' of ' А ' obj e c t s > , ' ge t_name ' : < func t i on A . get_name a t Ох 1 0 1 с 9 9 5 9 8 > } ) ) , ( ' _d i r_ ' , ) , (' doc ' , ' The А c l a s s . ' ) , ( ' _e q_ ' , < s l o t wrapper ' _eq_ ' o f ' obj e c t ' obj e c t s > ) , ( ' _ forma t_ ' , ) , ( ' _ge_ ' , < s l o t wrappe r ' _ge_ ' o f ' obj e ct ' obj e c t s > ) , ( ' _get a t t r ibute_ ' , < s l o t wrappe r ' get a t t r i bute- ' o f ' obj e ct ' obj e c t s > ) , gt , o f ' obj ect ' obj e c t s > ) , (' gt ' , < s l o t-; rappe r ' ( ' _hash_ ' , < s lot wrapper--;-_ha s h_ ' o f ' obj ect ' obj e c t s > ) , (' init ' , < func t i on А . init a t О х 1 0 1 с 9 9 5 1 0> ) , le ' < s l o t wrappe r ' le ' o f ' obj e c t ' obj e c t s > ) , (' (' lt • , < s l o t wrappe r ' lt , of ' obj e c t ' obj e c t s > ) , ( ' modu l e ' , ' examp l e ' ) , (' ne ' , < s l o t wr appe r ' ne ' o f ' obj e c t ' obj e c t s > ) , (' new_ ) , (' reduce ' , ) , (' reduce_ex ' , ) , repr ' , < s l o t wrappe r ' _repr_ ' o f ' obj e c t ' ob j e c t s > ) , (' (' setattr

= =

r/1888 18. Инсrруменrw наыка

1302

< s l o t wrapper ' � s e t a t t r� ' o f ' obj e c t ' obj e c t s > ) , (' s i ze o f ' , ) , ( ' _ s t r_ ' , < s l o t wrapper ' _ s t r_ ' o f ' obj e c t ' obj e c t s> ) , (' subc l a s shoo k ' ) , (' we a kref ' , < a t t ribute ' wea k r e f ' o f ' А ' obj e c t s > ) , ( ' ge t_name ' , < func t i on A . ge t_name a t Ох 1 0 1 с 9 9 5 9 8 > ) ]

Чтобы найти методы класса, следует использовать предикат i s function ( ) . Предикат i smethod ( ) распознает только ) , ( ' get_name ' , < fu n c t i o n A . ge t_name a t Ox 1 0 1 3 9 d 5 9 8 > ) ]

Вывод для класса В нключает переопределенный метод get name ( ) , а также новый метод и метод _ ini t_ ( ) , унаследованный от класса А. _

Листинг 1 8.38. in spect_getmemЬers_class methods Ь . ру _

_

import i n spect f rom pp r i n t import ppr i n t import examp l e pp r i n t ( i nspec t . ge tmemЬe r s ( exampl e . B , i n spe c t . i s funct i on ) )

Методы, унаследованные от класса А, такие как _ ini t_ ( ) , идентифицируют­ ся как методы класса В . $ pythonЗ i nspe c t_ge tmemЬe rs_c l a s s_me thods_b . p y [ ( '

init ' , < fu n c t i o n А . init at Ox 1 0 1 2 9d 5 1 0 > ) , ( ' do_ some thing ' , < functi o;;: B . do_s omething a t O x 1 0 1 2 9d 6 2 0 > ) , ( ' ge t _name ' , < func t i on B . ge t_name a t Ох 1 0 1 2 9dб а 8 > ) ]

1 8.4.4. Инспектирование экземпляров Инспектирование экземпляров работает точно так же, как инспектирование других объектов.

18.4. lnspect: мнсnекrированме вкrивных оба.екrов

1 303

Листинг 1 8.39. inspect_getmemЬers ins tance . ру _

import i n spect f r om pprint impor t ppr i n t import examp l e а = examp l e . A ( name= ' i nspect_ge tmemЬers ' ) ppr i n t ( i nspect . getmemЬers ( a , inspect . i smethod ) )

Предикат i smethod ( ) распознает в образце экземпляра два связанных метода класса А. $ pythonЗ i n s pect_ge tmemЬ e r s_i n s t a nce . py [(' init ', х 1 0 1 аЫЬа 8 » ) ( ' ge t_name ' , х 1 0 1 аЫЬа 8 » )

, ' keywo rd ' : ' changed va l ue o f a r gume nt ' , ' kwonl y ' : ' mu s t Ье named ' , ' l imit ' : О , ' l ocal_va r i aЬle ' : ' ' }

Функция s tack ( ) обеспечивает доступ ко всем фреймам стека: от текущего до фрейма первого вызывающего объекта. Следующий пример аналогичен предыду­ щему, за исключением того, что вывод информации о стеке откладывается до тех пор, пока не будет достигнут предел глубины рекурсии. Листинг 1 8. 5 3 . inspect_stack . py import i nspect import ppr i n t de f show_s t a c k ( ) : for leve l in inspect . s t a c k ( ) : prin t ( , { ) [ { } ] \ n - > { ) ' f o rma t ( l e ve l . f rame . f_code . co_ fi l e n ame , l evel . l i neno , l e ve l . code_context [ l eve l . inde x ] . s t r ip ( ) , )) ppr i n t . pp r i nt ( leve l . frame . f_l o ca l s ) print ( ) •

de f recurse ( l imi t ) : l ocal variaЫe = 1 1 * l imit i f l imi t < t i t l e > PyMOTW Templ a t e< / t i t l e > < /head>

< h l >Example Templa te< / h l >

Thi s i s

а

sample d a t a f i l e . < /p>

< /body> < /html >

В следующей программе функция ge t_da ta ( ) исrюльзуется для извлечения и вывода содержимого шаблона. Листинг 1 9.20. pkgutil_get_data . py

import pkgu t i l template p kgut i l . ge t_da t a ( ' pkgwi thda ta ' , ' t empl a t e s /ba se . html ' ) print ( template . de code ( ' ut f- 8 ' ) ) =

Аргументами функции get_data ( ) являются имя пакета, заданное с использо­ ванием точечной нотации, и имя файла, заданное относительно верхнего уровня пакета. Функция возвращает байтовую последовательность, поэтому перед выво­ дом на печать она декодируется из формата UTF-8. $ pythonЗ pkguti l_ge t_da ta . py < ! DOCTYPE HTML PUBLIC " - / / I ETF/ / DT D HTML / /EN" > < t i t l e > PyMOTW Templ a t e< / t i t l e> < /head>

Example Temp l a t e < / h l >

Th i s i s < /body> < / html >

а

s ample data f i l e . < /p>

1 328

ГА888 2.9. МОАJАИ и nакеты

Функция get _da ta ( ) распознает формат дистрибуrивов, поскольку исполь­ зует для доступа к содержимому пакетов перехватчики импорта ( расширения), определенные в документе РЕР 302. Можно использовать любой загрузчик, кото­ рый предоставляет такие расширения, включая импортер ZIР-архивов из модуля z i p f i le (раздел 8.5) . Листинг 1 9.21 . pkgutil_get_data_zip . py

impo rt pkgu t i l import z ipfile import sys # Созда ть Z I Р-файл с кодом из текущего каталога и шаблон, # исполь зуя имя , которое не встреча е т ся в локаль ной файловой # системе with z ipfile . PyZipFi l e ( ' pkgwithdata i n z i p . z ip ' , mode= ' w ' ) as z f : z f . writepy ( ' . ' ) z f . write ( ' pkgwi thda t a / temp l a t e s /ba s e . html ' , ' p kgwi thdat a / t empl a t e s / fromzip . html ' , ) # Доба вить Z I Р-файл в путь импорта s ys . pa t h . insert ( O , ' pkgwi thdata i nz i p . z ip ' ) # Импортировать паке т pkgwi thda ta , чтобы продемонстрировать , # что он взят из Z I Р-архива import pkgwithdata print ( ' Loading pkgwi thda ta frorn ' , pkgw i t hdata . f i l e # Вывести на экран тело шаблона print ( ' \ nTernplate : ' ) data = p kgut i l . ge t_da ta ( ' p kgwi thdata ' , ' ternplate s / from z ip . html ' ) print ( data . de code ( ' ut f - 8 ' ) )

В этом примере с помощью метода PyZipFi le . w r i tepy ( ) создастся ZIР-архив, содержащий копию пакета p kgw i thda t a , которая включает переименованную версию файла шаблона. Затем этот архив добавляется в пуrь импорта, после чего шаблон загружается с помощью модуля p kgut i l и выводится на экран. Более под­ робно метод wr i tepy ( ) обсуждался при рассмотрении модуля z ip f i l e (см. раз­ дел 8.2) . $ pythonЗ pkguti l_ge t_da ta_ z ip . py Loading pkgwi thda ta from p kgwi thdata i n z ip . z ip/pkgwi thda t a /�ini t� . pyc Templ a t e : < ! DOCTYPE HTML PUBL I C " - / / I ETF/ / DT D HTML/ /EN " > < head> < t i t l e > PyMOTW Ternpl a t e < / t i t le> < / head>

< h l >Exarnple Templ a t e < / h l >

19.З. zlpimport: :sarppca КОА8 Python из ZIР-ерхивов

1 329

T h i s i s а s ample data f i l e . < /p> < / body> < / h tml > Дополнительные ссылки • • •





Раздел документации стандартной библиотеки, посвященный модулю pkgut i l 5 • vir tua lenv (lап Bickiпg)6 . Сценарий виртуальной среды. di s t u t i l s . Предлагаемые стандартной библиотекой Python инструменты для созда­ ния пакетов. se tuptool s 7 . Следующее поколение инструментов, предназначенных для создания пакетов. РЕР 3028 . /mport Hooks.



zipfi l e (раздел 8.2). Создание ZIР-архивов, пригодных для импорта.



zipimport (раздел 1 9.З). Средство импортирования пакетов из ZIР-архивов.

1 9 . 3.

загрузка кода Python и з ZI Р-архивов z ip impo r t :

Модуль z ipimpo rt реализует класс z ipimporter, который можно использо· ват1, для поиска и загрузки модулей Pyt lюn из ZIР-архивов. Класс z ipimpo r t e r ре­ ализует API расширений ( перехватчиков импорта) , специфицированный в доку­ менте РЕР 302; именно так работает .еgg-формат Руtlюп . Необходимость в явном использовани и модуля z ipimport возникает лишь в редких случаях, поскольку модули можно импортировать непосредственно из ZIР-архива, включив его в список путей sys . ра th. Тем не менее знакомство с API импортера поможет программисту изучить доступные возможности и понять, как работает модуль impor t . Знание того, как работает ZIР-импортер, может так­ же пригодиться при отладке распространения пакетов приложений в виде ZIР­ архивов, созданных средствами класса zipf i l e . PyZ ipFile.

1 9.3. 1 . Пример В следующих примерах для создания образца ZIР-архива, содержащего не­ скол1,ко модулей Python , используется код, который приводился при обсуждении модуля z ipfi l e (см. раздел 8.2) . Листинг 1 9.22. zipimport make example . ру _

_

import s ys import zipf i l e

5 6 7 8

h t tps : / /docs . python . org / 3 . 5 / l ibrary /pkgut i l . h tml ht tp : / /pypi . python . org /pypi /vi rtua l env h t tps : / / s e t uptools . readthedocs . io/en/ l a te s t / www . python . o rg / dev/peps /pep- 0 3 0 2

Гма 19. МодуАМ и П8К81ЪI

1 330 if

·

name == ' ma i n ' -Z f z i p f i l e . PyZ ipFi l e ( ' z ipimpor t_exampl e . z ip ' , mode = ' w ' ) t ry : z f . wr i tepy ( ' . ' ) z f . wr i t e ( ' z i pimpor t_get_s ource . py ' ) z f . wr i t e ( ' examp l e_package / README . txt ' ) f i na l l y : z f . cl ose ( ) for name in z f . name l i s t ( ) : p r i nt ( name ) =

Прежде чем приступить к выполнению остальных примеров, выполните сце­ нарий z ipimport _make example . ру для создания ZIР-архива всех модулей , содер­ жащихся в каталоге примеров, и необходимых тестов ых данных к ним. _

$ pythonЗ z ip impo rt _ma ke_examp l e . py _i n i t_ . pyc examp l e_pa ckage / _ i n i t_ . pyc z ip impo rt_find_modul e . pyc z ip import_ge t_code . pyc z i p import_ge t_da t a . pyc z ip impo rt_ge t_dat a_no z ip . pyc z ip impor t_ge t_da ta_z ip . pyc z i p impor t_ge t_s ource . pyc z ip import_i s_pa ckage . pyc z ip import_lo ad_module . pyc z i pimport_ma ke_e x amp l e . pyc z i p import_get_sou rce . py examp l e_package / README . txt

1 9.3.2. Поиск модуля При условии , что предоставлено полное имя модуля, метод f ind_module ( ) пытается найти этот модуль в ZIР-архиве. Листинг 1 9 .23. zipimport find_module . ру _

import z ip import importer

=

z i pimpo r t . z ipimporter ( ' z ip import_exampl e . z i p ' )

for modul e _name i n [ ' z ipimp o rt_f i nd_modu l e ' , ' not_there ' ] : p r i nt ( modul e_name , ' : ' , impor t e r . f i nd_modul e ( modul e_name ) )

Если модуль успешно найден, возвращается экземпляр z ipimporter. В против­ ном случае возвращается значение None. $ python З z ip impo rt_fi nd_modu l e . py z i pimport_find_modu l e : < z ip impo r t e r obj ect " z i p impor t_examp l e . z ip " > not there : None

19.З. zlptmport: 38f'PV3К8 К11W1 Python иэ ZIР-ерхивов

1331

1 9.3.3. Доступ к коду

Метод get_code ( ) загружает объект кода для модуля , импортируемого из ар­ хива. Л истинг 1 9.24. z ipimport_get_code . py import z ip impor t importer z ip import . z i pimpo r t e r ( ' z ipimpor t_examp l e . z ip ' ) code importe r . get_code ( ' z i pimport_ge t_code ' ) pr i n t ( code ) =

=

Об1.ект кода - это не то же самое, что объект модуля, но используется для соз­ дания последнего. $ python З z ipimpor t_ge t_code . py < code obj ect at О х 1 0 1 2 Ь 4 а е 0 , f i l e " . / z ipimport_get _code . py " , l i ne 6 >

Чтобы загрузить код в качестве модуля , пригодного дл я использования, следу­ ет вызвать метод l oad_module ( ) . Л истинг 1 9.25. z ipimport_load_module . ру import z ip impor t importer z ipimport . z ipimpor t e r ( ' z ip impor t_e xamp l e . z ip ' ) module import e r . load_modul e ( ' z ip import_ge t_code ' ) p r i nt ( ' Name : ' , module . name ) p r i nt ( ' Loader : ' , module:- loader p r i n t ( ' Code : ' , modul e . code ) =

=

Результат представляет собой объект модуля , сконфигурированный так, как если бы код был загружен средствами обычного импорта. $ pythonЗ z ipimport_load_modu l e . py < code obj ect a t Ох1 0 0 7 Ь 4 с 0 0 , f i l e " . / z ipimpor t_ge t_code . py " , l i ne 6 > Name : z ip impor t_ge t_code Loade r : < z ipimporter obj ect " z ip import_examp l e . z ip " > Code : < c ode ob j e ct a t О х 1 0 0 7 Ь 4 с 0 0 , f i l e " . / z i p impor t_ge t_code . py " , l ine 6 >

1 9.3.4. Исходный код

Как и в случае модуля inspect (см. раздел 1 8.4) , существует возможность извле­ чения исходного кода модуля из ZI Р-архива с помощью модуля z ipimpo r t , если архив включает этот исходный код. В следующем примере в файл z ip impor t _ example . z ip добавляется лишь исходный файл z ipimport_ge t_source . ру. Все остальные модули добавляются только в виде .рус-файлов.

Гмва 19. Мо,QАМ м пакеты

1 332 Листинг 1 9 . 26. zipimport get_source . ру _ import z ip import modu l e s = [ ' z ip impo rt_get code ' , ' z ip impo rt_get_source ' ,

impor t e r = z i p import . z ipimporter ( ' z i pimpo rt_example . z ip ' ) for modu l e name in modu l e s : source importer . get_source ( module_name ) print ( ' = ' * В О ) p r i n t ( modul e_name ) print ( ' = ' * В О ) p r i n t ( source ) print ( ) =

Если исходный код для модуля недоступен, метод get_source ( ) возвращает з11ачс11ие None. $ python З z i p import_get s ource . p y z i pimpor t_get_code =========�========== = ======== ========= ============�===== = === None

z i pimp o r t_get_sou rce # ! / us r /b i n /env pythonЗ # # Copyright 2 0 0 7 Doug Hel lma nn . # " " " Re t r i eving the source code for а modul e w i t h i n а z ip a rchive . ,. " " # e nd_pymotw_heade r import z ip import modu l e s [ ' z ipimpo rt_get_code ' , ' z i p impo r t_ge t_source ' , =

importer = z i p import . z ipimporter ( ' z i p impor t_examp l e . z ip ' ) f o r modu l e name in modu l e s : source importe r . ge t_sou rce ( module_name ) print ( ' = ' * В О ) p r i nt ( modul e_name ) print ( ' = ' * В О ) p r i n t ( sou rce ) print ( ) =

19.З. zlplmport: эаrруака КОА8 Python И3 ZIP-epxи808

1 333

1 9.3.5. Пакеты Чтобы определить, ссылается ли имя на пакет, а не на модул1" следует исполь­ зовать метод i s _рас kage ( ) . Листинг 1 9.27. zipimport_i s_Packaqe . py irnpo rt z i pimport irnpo rter z i pirnpor t . z ip irnp or te r ( ' z ipirnport_exarnp le . z ip ' ) for name in [ ' z i pimport_i s_pa c ka g e ' , ' e xarnp l e_p a c kage ' ] : p r i n t ( narne , importer . i s_package ( narne ) ) =

В данном случае имени z ipimport_i s_pac kage соответствует модуль, а имени example_pac kage 11акет. -

$ pythonЗ z ipirnport_ i s_package . py z ipirnport_i s_package Fa l s e examp l e_package True

1 9.3.6. Данные Иногда исходные модули или пакеты должны распространяться вместе с дан­ ными, не являющимися кодом. Изображения, конфигурационные файJiы , данные для использования по умолчанию, тестовые данные это лишь 11ебош.111ой пере­ чень возможных типов таких данных. Во многих случаях для 011редеJ1сния нуги к таким файлам относительно места установки кода можно восполь:юваться атри­ бутами _ра th_ и _ f ile_ модуля. Например, в случае обычного модуля путь в файловой системе можно скон­ струировать на основе атрибута _ file_ импортированного пакета, как показа­ но в приведенном ниже коде. -

Листинг 1 9.28. z ipimport_qe t_data_noz ip . py irnport o s irnpor t ex amp l e_package # Найти каталог , содержащий импортируемый паке т , и созда ть # на его основе имя файла да нных p kg_d i r o s . pa t h . d i rnarne ( exarnp le_pa ckage . �f i l e ) � data_f i l e narne os . pa th . j o i n ( pkg_di r , ' README . tx t ' ) =

=

# Прочитать файл и отобра зить его содержимое p r i n t ( da t a_f i l ename , ' : ' ) p r i n t ( open ( da ta_f i l enarne ,

' r ' ) . re ad ( ) )

Вывод зависит от того, где именно в файловой системе рас11олагаеТ