Документация Python неофициальный перевод

parser.md

451 строк · 50.3 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.0/library/parser.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# `parser` – Доступ к деревьям разбора Python89Модуль `parser` предоставляет интерфейс к внутреннему парсеру Python и компилятору байт-кода. Основная цель этого интерфейса – позволить коду Python редактировать дерево разбора выражения Python и создавать из него исполняемый код. Это лучше, чем пытаться разобрать и изменить произвольный фрагмент кода Python как строку, потому что разбор выполняется так же, как и для кода, образующего приложение. Кроме того, это быстрее.1011> **Примечание**12>13> Начиная с Python 2.5 гораздо удобнее внедряться на этапе генерации и компиляции абстрактного синтаксического дерева (AST), используя модуль [`ast`](https://python-all.ru/3.0/library/ast.html#module-ast).1415Следует отметить несколько важных моментов, касающихся этого модуля, которые важны для использования создаваемых структур данных. Это не учебник по редактированию деревьев разбора для кода Python, но здесь приведены некоторые примеры использования модуля `parser`.1617Самое главное – требуется хорошее понимание грамматики Python, которую обрабатывает внутренний парсер. Полную информацию о синтаксисе языка можно найти в [*Справочнике по языку Python*](https://python-all.ru/3.0/reference/index.html#reference-index). Сам парсер создаётся по спецификации грамматики, определённой в файле `Grammar/Grammar` в стандартной поставке Python. Деревья разбора, хранящиеся в объектах ST, создаваемых этим модулем, – это фактический результат работы внутреннего парсера при вызове функций [`expr()`](https://python-all.ru/3.0/library/parser.html#parser.expr) или [`suite()`](https://python-all.ru/3.0/library/parser.html#parser.suite), описанных ниже. Объекты ST, созданные функцией [`sequence2st()`](https://python-all.ru/3.0/library/parser.html#parser.sequence2st), точно воспроизводят эти структуры. Имейте в виду, что значения последовательностей, считающиеся «правильными», могут меняться от версии Python к версии по мере изменения формальной грамматики языка. Однако перенос кода из одной версии Python в другую в виде исходного текста всегда позволит создать правильные деревья разбора в целевой версии, с единственным ограничением: при переходе на более старую версию интерпретатора более новые языковые конструкции не поддерживаются. Деревья разбора обычно не совместимы между версиями, тогда как исходный код всегда был совместим вперёд.1819Каждый элемент последовательностей, возвращаемых [`st2list()`](https://python-all.ru/3.0/library/parser.html#parser.st2list) или [`st2tuple()`](https://python-all.ru/3.0/library/parser.html#parser.st2tuple), имеет простую форму. Последовательности, представляющие нетерминальные элементы грамматики, всегда имеют длину больше единицы. Первый элемент – целое число, идентифицирующее продукцию в грамматике. Этим целым числам даны символьные имена в заголовочном файле C `Include/graminit.h` и в модуле Python [`symbol`](https://python-all.ru/3.0/library/symbol.html#module-symbol). Каждый дополнительный элемент последовательности представляет компонент продукции, распознанный во входной строке: это всегда последовательности, имеющие ту же форму, что и родительская. Важная особенность этой структуры, которую следует отметить: ключевые слова, используемые для идентификации типа родительского узла, например ключевое слово [`if`](https://python-all.ru/3.0/reference/compound_stmts.html#if) в `if_stmt`, включаются в дерево узлов без какой-либо специальной обработки. Например, ключевое слово [`if`](https://python-all.ru/3.0/reference/compound_stmts.html#if) представлено кортежем `(1, 'if')`, где `1` – числовое значение, связанное со всеми токенами `NAME`, включая имена переменных и функций, определённые пользователем. В альтернативной форме, возвращаемой при запросе информации о номерах строк, тот же токен может быть представлен как `(1, 'if', 12)`, где `12` обозначает номер строки, в которой был найден терминальный символ.2021Терминальные элементы представлены почти так же, но без дочерних элементов и с добавлением исходного текста, который был распознан. Пример ключевого слова [`if`](https://python-all.ru/3.0/reference/compound_stmts.html#if) выше показателен. Различные типы терминальных символов определены в заголовочном файле C `Include/token.h` и в модуле Python [`token`](https://python-all.ru/3.0/library/token.html#module-token).2223ST-объекты не обязательны для поддержки функциональности этого модуля, но предоставляются для трёх целей: позволить приложению распределить затраты на обработку сложных деревьев разбора, предоставить представление дерева разбора, экономящее память по сравнению с представлением в виде списков или кортежей Python, и облегчить создание дополнительных модулей на C, работающих с деревьями разбора. В Python можно создать простой класс-«обёртку», чтобы скрыть использование ST-объектов.2425Модуль `parser` определяет функции для нескольких различных целей. Наиболее важные цели – создание объектов ST и преобразование объектов ST в другие представления, такие как деревья разбора и скомпилированные объекты кода, но также есть функции, которые служат для запроса типа дерева разбора, представленного объектом ST.2627> **См. также**28>29> **Модуль [`symbol`](https://python-all.ru/3.0/library/symbol.html#module-symbol)**30>31> Полезные константы, представляющие внутренние узлы дерева разбора.32>33> **Модуль [`token`](https://python-all.ru/3.0/library/token.html#module-token)**34>35> Полезные константы, представляющие листовые узлы дерева разбора, и функции для проверки значений узлов.3637## Создание ST-объектов3839Объекты ST могут быть созданы из исходного кода или из дерева разбора. При создании объекта ST из исходного кода используются разные функции для создания форм `'eval'` и `'exec'`.4041#### `[parser.expr]parser.expr(source)`4243Функция4445[`expr()`](https://python-all.ru/3.0/library/parser.html#parser.expr)4647разбирает параметр4849*source*5051так, как если бы он был входными данными для5253`compile(source, 'file.py', 'eval')`5455. Если разбор прошёл успешно, создаётся объект ST для хранения внутреннего представления дерева разбора; в противном случае выбрасывается соответствующее исключение.5657#### `[parser.suite]parser.suite(source)`5859Функция6061[`suite()`](https://python-all.ru/3.0/library/parser.html#parser.suite)6263разбирает параметр6465*source*6667так, как если бы он был входными данными для6869`compile(source, 'file.py', 'exec')`7071. Если разбор прошёл успешно, создаётся объект ST для хранения внутреннего представления дерева разбора; в противном случае выбрасывается соответствующее исключение.7273#### `[parser.sequence2st]parser.sequence2st(sequence)`7475Эта функция принимает дерево разбора, представленное в виде последовательности, и строит его внутреннее представление, если это возможно. Если удаётся проверить, что дерево соответствует грамматике Python и все узлы имеют допустимые типы для данной версии Python, создаётся объект ST на основе внутреннего представления и возвращается вызывающему коду. Если при создании внутреннего представления возникла проблема или дерево не прошло проверку, выбрасывается исключение [`ParserError`](https://python-all.ru/3.0/library/parser.html#parser.ParserError). Не следует предполагать, что объект ST, созданный таким образом, скомпилируется корректно; обычные исключения, возникающие при компиляции, всё ещё могут быть инициированы при передаче объекта ST в [`compilest()`](https://python-all.ru/3.0/library/parser.html#parser.compilest). Это может указывать на проблемы, не связанные с синтаксисом (например, исключение [`MemoryError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.MemoryError)), но также может быть связано с конструкциями вроде результата разбора `del f(0)`, который пропускается синтаксическим анализатором Python, но проверяется компилятором байт-кода.7677Последовательности, представляющие терминальные токены, могут быть представлены либо как двухэлементные списки вида `(1, 'name')`, либо как трёхэлементные списки вида `(1, 'name', 56)`. Если присутствует третий элемент, он считается допустимым номером строки. Номер строки может быть указан для любого подмножества терминальных символов во входном дереве.7879#### `[parser.tuple2st]parser.tuple2st(sequence)`8081Это та же функция, что и8283[`sequence2st()`](https://python-all.ru/3.0/library/parser.html#parser.sequence2st)8485. Эта точка входа сохранена для обратной совместимости.8687## Преобразование ST-объектов8889ST-объекты, независимо от входных данных, использованных для их создания, могут быть преобразованы в деревья разбора, представленные в виде списков или кортежей, или скомпилированы в исполняемые объекты кода. Деревья разбора могут быть извлечены с информацией о номерах строк или без неё.9091#### `[parser.st2list]parser.st2list(st[, line_info])`9293Эта функция принимает объект ST от вызывающего в параметре *st* и возвращает список Python, представляющий эквивалентное дерево разбора. Полученное представление в виде списка можно использовать для просмотра или создания нового дерева разбора в форме списка. Эта функция не завершается ошибкой, пока есть достаточно памяти для построения спискового представления. Если дерево разбора будет использоваться только для просмотра, следует использовать [`st2tuple()`](https://python-all.ru/3.0/library/parser.html#parser.st2tuple), чтобы уменьшить потребление памяти и фрагментацию. Когда требуется представление в виде списка, эта функция значительно быстрее, чем получение кортежного представления и его преобразование во вложенные списки.9495Если *line\_info* равен true, для всех терминальных токенов будет включена информация о номере строки в качестве третьего элемента списка, представляющего токен. Обратите внимание, что указанный номер строки определяет строку, в которой токен *заканчивается*. Эта информация опускается, если флаг равен false или опущен.9697#### `[parser.st2tuple]parser.st2tuple(st[, line_info])`9899Эта функция принимает объект ST от вызывающего в параметре *st* и возвращает кортеж Python, представляющий эквивалентное дерево разбора. За исключением того, что возвращается кортеж вместо списка, эта функция идентична [`st2list()`](https://python-all.ru/3.0/library/parser.html#parser.st2list).100101Если *line\_info* равен true, для всех терминальных токенов будет включена информация о номере строки в качестве третьего элемента списка, представляющего токен. Эта информация опускается, если флаг равен false или опущен.102103#### `[parser.compilest]parser.compilest(st[, filename='<syntax-tree>'])`104105Компилятор байт-кода Python может быть вызван для объекта ST, чтобы получить объекты кода, которые можно использовать как часть вызова встроенных функций [`exec()`](https://python-all.ru/3.0/library/functions.html#exec) или [`eval()`](https://python-all.ru/3.0/library/functions.html#eval). Эта функция предоставляет интерфейс к компилятору, передавая внутреннее дерево разбора из *st* парсеру, используя имя исходного файла, заданное параметром *filename*. Значение по умолчанию для *filename* указывает, что исходный код был объектом ST.106107Компиляция объекта ST может привести к исключениям, связанным с компиляцией; примером может служить [`SyntaxError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.SyntaxError), вызванная деревом разбора для `del f(0)`: эта инструкция считается допустимой в формальной грамматике Python, но не является допустимой языковой конструкцией. Исключение [`SyntaxError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.SyntaxError), возбуждаемое в этой ситуации, обычно генерируется компилятором байт-кода Python, поэтому оно может быть возбуждено на этом этапе модулем `parser`. Большинство причин сбоя компиляции можно диагностировать программно, исследуя дерево разбора.108109## Запросы к объектам ST110111Предоставляются две функции, которые позволяют приложению определить, был ли объект ST создан как выражение или как набор инструкций. Ни одну из этих функций нельзя использовать для определения того, был ли объект ST создан из исходного кода с помощью [`expr()`](https://python-all.ru/3.0/library/parser.html#parser.expr) или [`suite()`](https://python-all.ru/3.0/library/parser.html#parser.suite), или из дерева разбора с помощью [`sequence2st()`](https://python-all.ru/3.0/library/parser.html#parser.sequence2st).112113#### `[parser.isexpr]parser.isexpr(st)`114115Если *st* представляет форму `'eval'`, эта функция возвращает истину, в противном случае – ложь. Это полезно, поскольку объекты кода обычно нельзя запросить на эту информацию с помощью существующих встроенных функций. Обратите внимание, что объекты кода, созданные [`compilest()`](https://python-all.ru/3.0/library/parser.html#parser.compilest), также нельзя так запросить, и они идентичны объектам, созданным встроенной функцией [`compile()`](https://python-all.ru/3.0/library/functions.html#compile).116117#### `[parser.issuite]parser.issuite(st)`118119Эта функция аналогична120121[`isexpr()`](https://python-all.ru/3.0/library/parser.html#parser.isexpr)122123в том, что она сообщает, представляет ли объект ST форму124125`'exec'`126127, обычно называемую «набором». Не стоит считать, что эта функция эквивалентна128129`not isexpr(st)`130131, поскольку в будущем могут поддерживаться дополнительные синтаксические фрагменты.132133## Исключения и обработка ошибок134135Модуль parser определяет одно исключение, но также может передавать другие встроенные исключения из других частей среды выполнения Python. Информацию об исключениях, которые может вызывать каждая функция, смотрите в её описании.136137#### `[parser.ParserError]exception parser.ParserError`138139Исключение, возникающее при сбое в модуле parser. Обычно оно порождается из-за ошибок валидации, а не из-за встроенного140141[`SyntaxError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.SyntaxError)142143, который выбрасывается при обычном разборе. Аргументом исключения является либо строка с описанием причины сбоя, либо кортеж, содержащий последовательность, вызвавшую сбой (из дерева разбора, переданного в144145[`sequence2st()`](https://python-all.ru/3.0/library/parser.html#parser.sequence2st)146147), и пояснительную строку. Вызовы148149[`sequence2st()`](https://python-all.ru/3.0/library/parser.html#parser.sequence2st)150151должны уметь обрабатывать оба типа исключений, в то время как вызовам других функций модуля достаточно знать только о простых строковых значениях.152153Обратите внимание, что функции [`compilest()`](https://python-all.ru/3.0/library/parser.html#parser.compilest), [`expr()`](https://python-all.ru/3.0/library/parser.html#parser.expr) и [`suite()`](https://python-all.ru/3.0/library/parser.html#parser.suite) могут выбрасывать исключения, которые обычно возникают в процессе разбора и компиляции. К ним относятся встроенные исключения [`MemoryError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.MemoryError), [`OverflowError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.OverflowError), [`SyntaxError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.SyntaxError) и [`SystemError`](https://python-all.ru/3.0/library/exceptions.html#exceptions.SystemError). В этих случаях данные исключения несут весь обычный для них смысл. Обратитесь к описаниям каждой функции за подробной информацией.154155## Объекты ST156157Между объектами ST поддерживаются сравнения на порядок и равенство. Также поддерживается сериализация объектов ST с помощью модуля [`pickle`](https://python-all.ru/3.0/library/pickle.html#module-pickle).158159#### `[parser.STType]parser.STType`160161Тип объектов, возвращаемых функциями162163[`expr()`](https://python-all.ru/3.0/library/parser.html#parser.expr)164165,166167[`suite()`](https://python-all.ru/3.0/library/parser.html#parser.suite)168169и170171[`sequence2st()`](https://python-all.ru/3.0/library/parser.html#parser.sequence2st)172173.174175Объекты ST имеют следующие методы:176177#### `[parser.ST.compile]ST.compile([filename])`178179То же, что и180181`compilest(st, filename)`182183.184185#### `[parser.ST.isexpr]ST.isexpr()`186187То же, что и188189`isexpr(st)`190191.192193#### `[parser.ST.issuite]ST.issuite()`194195То же, что и196197`issuite(st)`198199.200201#### `[parser.ST.tolist]ST.tolist([line_info])`202203То же, что и204205`st2list(st, line_info)`206207.208209#### `[parser.ST.totuple]ST.totuple([line_info])`210211То же, что и212213`st2tuple(st, line_info)`214215.216217## Примеры218219Модуль parser позволяет выполнять операции над деревом разбора исходного кода Python до генерации [*байт-кода*](https://python-all.ru/3.0/glossary.html#term-bytecode), а также предоставляет возможность инспектировать дерево разбора для сбора информации. Приведены два примера. Простой пример демонстрирует эмуляцию встроенной функции [`compile()`](https://python-all.ru/3.0/library/functions.html#compile), а сложный пример показывает использование дерева разбора для извлечения информации.220221### Эмуляция [`compile()`](https://python-all.ru/3.0/library/functions.html#compile)222223Хотя между разбором и генерацией байт-кода может выполняться множество полезных операций, самая простая операция – ничего не делать. Для этой цели использование модуля `parser` для создания промежуточной структуры данных эквивалентно следующему коду224225```python226>>> code = compile('a + 5', 'file.py', 'eval')227>>> a = 5228>>> eval(code)22910230```231232Эквивалентная операция с использованием модуля `parser` несколько длиннее и позволяет сохранить промежуточное внутреннее дерево разбора в виде объекта ST:233234```python235>>> import parser236>>> st = parser.expr('a + 5')237>>> code = st.compile('file.py')238>>> a = 5239>>> eval(code)24010241```242243Приложение, которому нужны как объекты ST, так и объекты кода, может упаковать этот код в готовые функции:244245```python246import parser247248def load_suite(source_string):249    st = parser.suite(source_string)250    return st, st.compile()251252def load_expression(source_string):253    st = parser.expr(source_string)254    return st, st.compile()255```256257### Извлечение информации258259Некоторым приложениям полезен прямой доступ к дереву разбора. Оставшаяся часть этого раздела демонстрирует, как с помощью дерева разбора получить доступ к документации модуля, определённой в строках документации, без необходимости загружать исследуемый код в работающий интерпретатор через [`import`](https://python-all.ru/3.0/reference/simple_stmts.html#import). Это может быть очень полезно для анализа недоверенного кода.260261В целом, пример покажет, как можно обходить дерево разбора, чтобы извлекать интересующую информацию. Разрабатываются две функции и набор классов, которые предоставляют программный доступ к определениям функций и классов верхнего уровня, заданным в модуле. Классы извлекают информацию из дерева разбора и предоставляют к ней доступ на полезном семантическом уровне; одна функция реализует простой механизм сопоставления с образцом низкого уровня, а другая функция определяет высокоуровневый интерфейс к классам, обрабатывая файловые операции от имени вызывающего кода. Все упомянутые здесь исходные файлы, не входящие в установку Python, находятся в каталоге `Demo/parser/` дистрибутива.262263Динамическая природа Python предоставляет программисту большую гибкость, но большинству модулей требуется лишь ограниченная её мера при определении классов, функций и методов. В этом примере будут рассматриваться только те определения, которые находятся на верхнем уровне своего контекста, например, функция, определённая оператором [`def`](https://python-all.ru/3.0/reference/compound_stmts.html#def) в нулевой колонке модуля, но не функция, определённая внутри ветви конструкции [`if`](https://python-all.ru/3.0/reference/compound_stmts.html#if) ... [`else`](https://python-all.ru/3.0/reference/compound_stmts.html#else), хотя в некоторых ситуациях для этого есть веские причины. Вложенность определений будет обрабатываться кодом, разработанным в примере.264265Чтобы построить методы извлечения информации верхнего уровня, нужно знать, как устроено дерево разбора и какая его часть нас на самом деле интересует. Python использует довольно глубокое дерево разбора, поэтому существует множество промежуточных узлов. Важно прочитать и понять формальную грамматику, используемую Python. Она задана в файле `Grammar/Grammar` дистрибутива. Рассмотрим простейший интересующий нас случай при поиске строк документации: модуль, состоящий только из строки документации и ничего больше. (См. файл `docstring.py`.)266267```python268"""Некоторая документация.269"""270```271272Используя интерпретатор для просмотра дерева разбора, мы обнаруживаем ошеломляющую массу чисел и скобок, с документацией, глубоко погребённой во вложенных кортежах.273274```python275>>> import parser276>>> import pprint277>>> st = parser.suite(open('docstring.py').read())278>>> tup = st.totuple()279>>> pprint.pprint(tup)280(257,281 (264,282  (265,283   (266,284    (267,285     (307,286      (287,287       (288,288        (289,289         (290,290          (292,291           (293,292            (294,293             (295,294              (296,295               (297,296                (298,297                 (299,298                  (300, (3, '"""Some documentation.\n"""'))))))))))))))))),299   (4, ''))),300 (4, ''),301 (0, ''))302```303304Числа в первом элементе каждого узла дерева – это типы узлов; они напрямую соответствуют терминальным и нетерминальным символам грамматики. К сожалению, во внутреннем представлении они представлены целыми числами, и генерируемые структуры Python этого не меняют. Однако модули [`symbol`](https://python-all.ru/3.0/library/symbol.html#module-symbol) и [`token`](https://python-all.ru/3.0/library/token.html#module-token) предоставляют символьные имена для типов узлов и словари, которые отображают целые числа в символьные имена типов узлов.305306В выходных данных, представленных выше, внешний кортеж содержит четыре элемента: целое число `257` и три дополнительных кортежа. Тип узла `257` имеет символическое имя `file_input`. Каждый из этих внутренних кортежей содержит целое число в качестве первого элемента; эти целые числа – `264`, `4` и `0` – представляют типы узлов `stmt`, `NEWLINE` и `ENDMARKER` соответственно. Обратите внимание, что эти значения могут меняться в зависимости от используемой версии Python; подробности отображения см. в `symbol.py` и `token.py`. Должно быть достаточно ясно, что внешний узел связан в первую очередь с исходным кодом, а не с содержимым файла, и пока его можно не учитывать. Узел `stmt` гораздо интереснее. В частности, все строки документации находятся в поддеревьях, которые формируются точно так же, как и этот узел, с той лишь разницей, что строки различаются. Связь между строкой документации в подобном дереве и определяемой сущностью (классом, функцией или модулем), которую она описывает, определяется положением поддерева строки документации в дереве, определяющем описываемую структуру.307308Заменяя фактическую строку документации на что-то, обозначающее переменную составляющую дерева, мы получаем возможность использовать простой подход сопоставления с образцом для проверки любого заданного поддерева на соответствие общему шаблону строк документации. Поскольку пример демонстрирует извлечение информации, можно смело потребовать, чтобы дерево было представлено в форме кортежа, а не списка, что позволяет использовать простое представление переменной в виде `['variable_name']`. Простая рекурсивная функция может реализовать сопоставление с образцом, возвращая логическое значение и словарь соответствий имён переменных значениям. (См. файл `example.py`.)309310```python311def match(pattern, data, vars=None):312    if vars is None:313        vars = {}314    if isinstance(pattern, list):315        vars[pattern[0]] = data316        return True, vars317    if not instance(pattern, tuple):318        return (pattern == data), vars319    if len(data) != len(pattern):320        return False, vars321    for pattern, data in zip(pattern, data):322        same, vars = match(pattern, data, vars)323        if not same:324            break325    return same, vars326```327328Благодаря этому простому представлению синтаксических переменных и символических типов узлов шаблон для поддеревьев-кандидатов строк документации становится вполне читаемым. (См. файл `example.py`.)329330```python331import symbol332import token333334DOCSTRING_STMT_PATTERN = (335    symbol.stmt,336    (symbol.simple_stmt,337     (symbol.small_stmt,338      (symbol.expr_stmt,339       (symbol.testlist,340        (symbol.test,341         (symbol.and_test,342          (symbol.not_test,343           (symbol.comparison,344            (symbol.expr,345             (symbol.xor_expr,346              (symbol.and_expr,347               (symbol.shift_expr,348                (symbol.arith_expr,349                 (symbol.term,350                  (symbol.factor,351                   (symbol.power,352                    (symbol.atom,353                     (token.STRING, ['docstring'])354                     )))))))))))))))),355     (token.NEWLINE, '')356     ))357```358359Используя функцию `match()` с этим шаблоном, извлечь строку документации модуля из ранее созданного дерева разбора очень просто:360361```python362>>> found, vars = match(DOCSTRING_STMT_PATTERN, tup[1])363>>> found364True365>>> vars366{'docstring': '"""Some documentation.\n"""'}367```368369Как только конкретные данные могут быть извлечены из места, где они ожидаются, возникает вопрос о том, где можно ожидать информацию. При работе со строками документации ответ довольно прост: строка документации – это первый узел `stmt` в блоке кода (типы узлов `file_input` или [`suite`](https://python-all.ru/3.0/library/parser.html#parser.suite)). Модуль состоит из одного узла `file_input`, а определения класса и функции содержат ровно один узел [`suite`](https://python-all.ru/3.0/library/parser.html#parser.suite). Классы и функции легко идентифицируются как поддеревья узлов блока кода, которые начинаются с `(stmt, (compound_stmt, (classdef, ...` или `(stmt, (compound_stmt, (funcdef, ...`. Обратите внимание, что эти поддеревья не могут быть сопоставлены с помощью `match()`, поскольку она не поддерживает сопоставление нескольких дочерних узлов без учёта их количества. Более сложная функция сопоставления могла бы преодолеть это ограничение, но для примера этого достаточно.370371Имея возможность определить, может ли оператор быть строкой документации, и извлечь из него саму строку, необходимо выполнить некоторую работу по обходу дерева разбора для всего модуля, извлечению информации об именах, определённых в каждом контексте модуля, и связыванию строк документации с этими именами. Код для выполнения этой работы несложен, но требует некоторых пояснений.372373Публичный интерфейс классов прост и, вероятно, должен быть несколько более гибким. Каждый «основной» блок модуля описывается объектом, предоставляющим несколько методов для запросов и конструктор, который принимает как минимум поддерево полного дерева разбора, которое он представляет. Конструктор `ModuleInfo` принимает необязательный параметр *name*, так как иначе он не может определить имя модуля.374375Публичные классы включают `ClassInfo`, `FunctionInfo` и `ModuleInfo`. Все объекты предоставляют методы `get_name()`, `get_docstring()`, `get_class_names()` и `get_class_info()`. Объекты `ClassInfo` поддерживают `get_method_names()` и `get_method_info()`, тогда как другие классы предоставляют `get_function_names()` и `get_function_info()`.376377В каждой из форм блока кода, которые представляют публичные классы, большая часть требуемой информации имеет одинаковую форму и доступ к ней осуществляется одинаково, с тем отличием, что в классах функции, определённые на верхнем уровне, называются «методами». Поскольку различие в терминологии отражает реальное семантическое отличие от функций, определённых вне класса, реализация должна поддерживать это различие. Следовательно, большая часть функциональности публичных классов может быть реализована в общем базовом классе `SuiteInfoBase`, а аксессоры для информации о функциях и методах предоставляются отдельно. Обратите внимание, что существует только один класс, представляющий информацию о функциях и методах; это соответствует использованию оператора [`def`](https://python-all.ru/3.0/reference/compound_stmts.html#def) для определения обоих типов элементов.378379Большинство функций доступа объявлены в `SuiteInfoBase` и не требуют переопределения в подклассах. Более важно то, что извлечение большей части информации из дерева разбора выполняется методом, вызываемым конструктором `SuiteInfoBase`. Пример кода для большинства классов понятен при чтении вместе с формальной грамматикой, но метод, который рекурсивно создаёт новые информационные объекты, требует дополнительного изучения. Вот соответствующая часть определения `SuiteInfoBase` из `example.py`:380381```python382class SuiteInfoBase:383    _docstring = ''384    _name = ''385386    def __init__(self, tree = None):387        self._class_info = {}388        self._function_info = {}389        if tree:390            self._extract_info(tree)391392    def _extract_info(self, tree):393        # extract docstring394        if len(tree) == 2:395            found, vars = match(DOCSTRING_STMT_PATTERN[1], tree[1])396        else:397            found, vars = match(DOCSTRING_STMT_PATTERN, tree[3])398        if found:399            self._docstring = eval(vars['docstring'])400        # discover inner definitions401        for node in tree[1:]:402            found, vars = match(COMPOUND_STMT_PATTERN, node)403            if found:404                cstmt = vars['compound']405                if cstmt[0] == symbol.funcdef:406                    name = cstmt[2][1]407                    self._function_info[name] = FunctionInfo(cstmt)408                elif cstmt[0] == symbol.classdef:409                    name = cstmt[2][1]410                    self._class_info[name] = ClassInfo(cstmt)411```412413После инициализации некоторого внутреннего состояния конструктор вызывает метод `_extract_info()`. Этот метод выполняет основную часть извлечения информации, которая происходит во всём примере. Извлечение состоит из двух отдельных фаз: нахождение строки документации для переданного дерева разбора и обнаружение дополнительных определений в блоке кода, представленном этим деревом разбора.414415Первая проверка [`if`](https://python-all.ru/3.0/reference/compound_stmts.html#if) определяет, является ли вложенный блок кода «краткой формой» или «полной формой». Краткая форма используется, когда блок кода находится на той же строке, что и определение блока кода, например:416417```python418def square(x): "Square an argument."; return x ** 2419```420421тогда как полная форма использует блок с отступом и допускает вложенные определения:422423```python424def make_power(exp):425    "Make a function that raises an argument to the exponent `exp`."426    def raiser(x, y=exp):427        return x ** y428    return raiser429```430431При использовании краткой формы блок кода может содержать строку документации как первый и, возможно, единственный элемент `small_stmt`. Извлечение такой строки документации несколько отличается и требует лишь части полного шаблона, используемого в более распространённом случае. В текущей реализации строка документации будет найдена, только если в узле `simple_stmt` есть только один узел `small_stmt`. Поскольку большинство функций и методов, использующих краткую форму, не предоставляют строки документации, это можно считать достаточным. Извлечение строки документации выполняется с помощью функции `match()`, как описано выше, и значение строки документации сохраняется как атрибут объекта `SuiteInfoBase`.432433После извлечения строки документации простой алгоритм поиска определений работает с узлами `stmt` узла [`suite`](https://python-all.ru/3.0/library/parser.html#parser.suite). Особый случай краткой формы не проверяется; поскольку в краткой форме нет узлов `stmt`, алгоритм молча пропустит единственный узел `simple_stmt` и корректно не обнаружит никаких вложенных определений.434435Каждый оператор в блоке кода классифицируется как определение класса, определение функции или метода, или что-то другое. Для операторов определения извлекается имя определяемого элемента, и создаётся объект представления, соответствующий определению, которому в качестве аргумента конструктора передаётся определяющее поддерево. Объекты представления хранятся в переменных экземпляра и могут быть получены по имени с помощью соответствующих методов доступа.436437Публичные классы предоставляют любые необходимые методы доступа, которые являются более специфичными, чем те, что предоставляет класс `SuiteInfoBase`, но сам алгоритм извлечения остаётся общим для всех форм блоков кода. Для извлечения полного набора информации из исходного файла можно использовать функцию высокого уровня. (См. файл `example.py`.)438439```python440def get_docs(fileName):441    import os442    import parser443444    source = open(fileName).read()445    basename = os.path.basename(os.path.splitext(fileName)[0])446    st = parser.suite(source)447    return ModuleInfo(st.totuple(), basename)448```449450Это обеспечивает простой в использовании интерфейс к документации модуля. Если требуется информация, которая не извлекается кодом из этого примера, код может быть расширен в чётко определённых точках для предоставления дополнительных возможностей.451