ast — Абстрактные синтаксические деревья

Исходный код: Lib/ast.py


Модуль ast помогает Python приложениям обрабатывать Python деревья абстрактных синтаксических грамматик. Сам абстрактный синтаксис может измениться с каждым выпуском Python’а; модуль помогает программно выяснить, на какую текущая грамматика похожа.

Абстрактное синтаксическое дерево может быть создано путем передачи ast.PyCF_ONLY_AST как флаг встроенной функции compile() или с использованием parse() хелпера, представленного в этом модуле. Результатом будет дерево объектов, чьи классы наследуются от ast.AST. Абстрактное синтаксическое дерево может быть скомпилировано в объект Python кода с помощью встроенной функции compile().

Node классы

class ast.AST

Является базовым для всех классов нод AST. Фактические нод классы извлекаются из файла Parser/Python.asdl, которые воспроизводятся below. Они определены в C модуле _ast и реэкспортируются в ast.

В абстрактной грамматике (например, класс или ast.expr) для каждого левого символа определен один ast.stmt. Кроме того, для каждого конструктора справа определен один класс; эти классы наследуют от классы для левосторонних деревьев. Например, ast.BinOp наследуется от ast.expr. Для продакшен правил с альтернативами (например, «суммами») левосторонний класс абстрактен: всегда создаются только экземпляры определенных нод конструктора.

_fields

Каждый класс содержит атрибут _fields который предоставляет имена всех дочерних узлов.

Каждая сущность класс содержит один атрибут для каждого дочерней ноды типа, определенной в грамматике. Например, ast.BinOp сущности содержит left атрибуты типа ast.expr.

Если атрибуты помечены в грамматике как необязательные (с помощью вопросительного знака), значение может содержать None. Если атрибуты содержат ноль или больше значений (помеченные звездочкой), значения представляются в виде списков Python. Все возможные атрибуты должны присутствовать и содержать допустимые значения при компиляции AST с compile().

lineno
col_offset
end_lineno
end_col_offset

Сущности ast.expr и ast.stmt подклассов содержат lineno, col_offset, lineno и col_offset аттрибуты. lineno и end_lineno являются номерами первой и последней строк диапазона исходного текста (индексирование с 1, так что первая строка является строкой номером 1), а col_offset и end_col_offset являются соответствующими UTF-8 байтами смещений первого и последнего маркеров, сгенерировавших ноду. Запиcь cмещения UTF-8 производится по причине внутреннеого использования анализатором UTF-8.

Обратите внимание, что конечные позиции компилятора не требуются и поэтому являются необязательными. Конечное смещение after является последним символом, например, можно получить исходный сегмент однолинейной ноды выражения с помощью source_line[node.col_offset : node.end_col_offset].

Конструктор класс ast.T анализирует свои аргументы следующим образом:

  • Если существуют позиционные аргументы, их должно быть столько, сколько элементов в T._fields; они будут назначены в качестве атрибутов этих имен.
  • Если есть ключевые аргументы, они зададут атрибуты одних и тех же имен для заданных значений.

Например, для создания и заполнения узла ast.UnaryOp можно использовать:

node = ast.UnaryOp()
node.op = ast.USub()
node.operand = ast.Constant()
node.operand.value = 5
node.operand.lineno = 0
node.operand.col_offset = 0
node.lineno = 0
node.col_offset = 0

или более компктно:

node = ast.UnaryOp(ast.USub(), ast.Constant(5, lineno=0, col_offset=0),
                   lineno=0, col_offset=0)

Изменено в версии 3.8: Класс ast.Constant теперь используемый для всех констант.

Не рекомендуется, начиная с версии 3.8: Старые классы ast.Num, ast.Str, ast.Bytes, ast.NameConstant и ast.Ellipsis по-прежнему доступны, но будут удалены в будущих Python релизах. Тем временем, создание их экземпляров вернет сущность другого класса.

Абстрактная грамматика

Абстрактная грамматика в настоящее время определяется следующим образом:

-- ASDL's 5 builtin types are:
-- identifier, int, string, object, constant

module Python
{
    mod = Module(stmt* body, type_ignore *type_ignores)
        | Interactive(stmt* body)
        | Expression(expr body)
        | FunctionType(expr* argtypes, expr returns)

        -- not really an actual node but useful in Jython's typesystem.
        | Suite(stmt* body)

    stmt = FunctionDef(identifier name, arguments args,
                       stmt* body, expr* decorator_list, expr? returns,
                       string? type_comment)
          | AsyncFunctionDef(identifier name, arguments args,
                             stmt* body, expr* decorator_list, expr? returns,
                             string? type_comment)

          | ClassDef(identifier name,
             expr* bases,
             keyword* keywords,
             stmt* body,
             expr* decorator_list)
          | Return(expr? value)

          | Delete(expr* targets)
          | Assign(expr* targets, expr value, string? type_comment)
          | AugAssign(expr target, operator op, expr value)
          -- 'simple' indicates that we annotate simple name without parens
          | AnnAssign(expr target, expr annotation, expr? value, int simple)

          -- use 'orelse' because else is a keyword in target languages
          | For(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment)
          | AsyncFor(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment)
          | While(expr test, stmt* body, stmt* orelse)
          | If(expr test, stmt* body, stmt* orelse)
          | With(withitem* items, stmt* body, string? type_comment)
          | AsyncWith(withitem* items, stmt* body, string? type_comment)

          | Raise(expr? exc, expr? cause)
          | Try(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody)
          | Assert(expr test, expr? msg)

          | Import(alias* names)
          | ImportFrom(identifier? module, alias* names, int? level)

          | Global(identifier* names)
          | Nonlocal(identifier* names)
          | Expr(expr value)
          | Pass | Break | Continue

          -- XXX Jython will be different
          -- col_offset is the byte offset in the utf8 string the parser uses
          attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)

          -- BoolOp() can use left & right?
    expr = BoolOp(boolop op, expr* values)
         | NamedExpr(expr target, expr value)
         | BinOp(expr left, operator op, expr right)
         | UnaryOp(unaryop op, expr operand)
         | Lambda(arguments args, expr body)
         | IfExp(expr test, expr body, expr orelse)
         | Dict(expr* keys, expr* values)
         | Set(expr* elts)
         | ListComp(expr elt, comprehension* generators)
         | SetComp(expr elt, comprehension* generators)
         | DictComp(expr key, expr value, comprehension* generators)
         | GeneratorExp(expr elt, comprehension* generators)
         -- the grammar constrains where yield expressions can occur
         | Await(expr value)
         | Yield(expr? value)
         | YieldFrom(expr value)
         -- need sequences for compare to distinguish between
         -- x < 4 < 3 and (x < 4) < 3
         | Compare(expr left, cmpop* ops, expr* comparators)
         | Call(expr func, expr* args, keyword* keywords)
         | FormattedValue(expr value, int? conversion, expr? format_spec)
         | JoinedStr(expr* values)
         | Constant(constant value, string? kind)

         -- the following expression can appear in assignment context
         | Attribute(expr value, identifier attr, expr_context ctx)
         | Subscript(expr value, slice slice, expr_context ctx)
         | Starred(expr value, expr_context ctx)
         | Name(identifier id, expr_context ctx)
         | List(expr* elts, expr_context ctx)
         | Tuple(expr* elts, expr_context ctx)

          -- col_offset is the byte offset in the utf8 string the parser uses
          attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)

    expr_context = Load | Store | Del | AugLoad | AugStore | Param

    slice = Slice(expr? lower, expr? upper, expr? step)
          | ExtSlice(slice* dims)
          | Index(expr value)

    boolop = And | Or

    operator = Add | Sub | Mult | MatMult | Div | Mod | Pow | LShift
                 | RShift | BitOr | BitXor | BitAnd | FloorDiv

    unaryop = Invert | Not | UAdd | USub

    cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn

    comprehension = (expr target, expr iter, expr* ifs, int is_async)

    excepthandler = ExceptHandler(expr? type, identifier? name, stmt* body)
                    attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)

    arguments = (arg* posonlyargs, arg* args, arg? vararg, arg* kwonlyargs,
                 expr* kw_defaults, arg? kwarg, expr* defaults)

    arg = (identifier arg, expr? annotation, string? type_comment)
           attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)

    -- keyword arguments supplied to call (NULL identifier for **kwargs)
    keyword = (identifier? arg, expr value)

    -- import name with optional 'as' alias.
    alias = (identifier name, identifier? asname)

    withitem = (expr context_expr, expr? optional_vars)

    type_ignore = TypeIgnore(int lineno, string tag)
}

Помощники ast

Кроме нод классов, модуль ast определяет служебные функции и классы для обхода абстрактных синтаксических деревьев:

ast.parse(source, filename='<unknown>', mode='exec', *, type_comments=False, feature_version=None)

Выполнит синтаксический анализ источника в AST ноде. Эквивалентно compile(source, filename, mode, ast.PyCF_ONLY_AST).

Если указано type_comments=True, парсер изменяется для проверки и возврата типа комментариев, как указано в PEP 484 и PEP 526. Это эквивалентно добавлению ast.PyCF_TYPE_COMMENTS к флагам, переданным compile(). При этом будут сообщены синтаксические ошибки для недопустимых типов комментариев. Без этого флага комментарии типа будут игнорироваться и поле type_comment на выбранных AST нодах будет всегда None. Кроме того, расположение # type:  ignore комментариев будет возвращено как атрибут type_ignores Module (в противном случае это всегда пустой список).

Кроме того, если mode является 'func_type', входной синтаксис модифицируется таким образом, чтобы он соответствовал PEP 484 «сигнатурой типа комментариям», например (str, int) -> List[str].

Кроме того, при установке feature_version в (major, minor) кортежа будет предпринята попытка синтаксического анализа с использованием грамматики конкретной версии Python. В настоящее время major должно быть равно 3. Например, установка feature_version=(3, 4) позволит использовать async и await в качестве имен переменных. Самая низкая поддерживаемая версия - (3, 4); самая высокая - sys.version_info[0:2].

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

Возможно крушение Python интерпретатора с большой/сложной строкой из-за ограничений глубины стека Python’а компиляторв AST.

Изменено в версии 3.8: Добавлены type_comments, mode='func_type' и feature_version.

ast.literal_eval(node_or_string)

Безопасная оценка узла выражения или строка, содержащего Python литерал или отображение контейнера. Предоставленный строка или узел может состоять только из следующих Python литеральных структур: строки, байт, чисел, кортежей, списков, знаков, наборов, булевов и None.

Это может быть используемый для безопасной оценки строки, содержащих Python значения из ненадежных источников без необходимости разбора самих значений. Он не способен оценивать произвольно сложные выражения, например, с участием операторов или индексирования.

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

Возможно крушение Python интерпретатора с большой/сложной строкой из-за ограничений глубины стека Python’а компиляторв AST.

Изменено в версии 3.2: Теперь разрешены байты и установка литералов.

ast.get_docstring(node, clean=True)

Возвращает докстринг для переданного node (которая должен быть нодой FunctionDef, AsyncFunctionDef, ClassDef или Module) или None, если у ней нет докстринга. Если clean истинно, то очищается отступ докстринга с помощью inspect.cleandoc().

Изменено в версии 3.5: Теперь поддерживается AsyncFunctionDef.

ast.get_source_segment(source, node, *, padded=False)

Получение сегмента исходного кода source, создавшего node. Если какая- либо информация о местоположении (lineno, end_lineno, col_offset или end_col_offset) отсутствует, вернётся None.

Если padded True, первая строка многострочного оператор будет заполнена пробелами в соответствии с исходным положением.

Добавлено в версии 3.8.

ast.fix_missing_locations(node)

При компиляции дерева нод с помощью compile() компилятор ожидает lineno и col_offset атрибутов для каждой поддерживаемой ноды. Это довольно утомительно для заполнения созданных нод, поэтому хелпер рекурсивно добавляет атрибуты там, где они еще не установлены, путем установки их значениями родительской ноды. Она работает рекурсивно, начиная с node.

ast.increment_lineno(node, n=1)

Увеличивает номер строки и номер конечной строки каждой ноды дерева, начиная с node до n. Она полезна для «перемещения кода» в другое расположение в файле.

ast.copy_location(new_node, old_node)

Копирует исходное местоположение (lineno, col_offset, end_lineno и end_col_offset) из old_node в new_node, если возможно, возвращает new_node.

ast.iter_fields(node)

Извлекает кортеж (fieldname, value) для каждого поля в node._fields, который присутствует в ноде.

ast.iter_child_nodes(node)

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

ast.walk(node)

Рекурсивно выводит все дочерние ноды в дереве, начиная с node (включая сам node), в неопределенном порядке. Это полезно, если нужно изменить только ноды на месте и не беспокоиться о контексте.

class ast.NodeVisitor

Базовый класс посетителя ноды, который обходит абстрактное синтаксическое дерево и вызывает функции посетителя для каждого найденного узла. Эта функция может возвращать значение которое передается методу visit().

Класс должен быть подклассом, а подкласс добавляет методы посетителю.

visit(node)

Посещает ноду. Реализация по умолчанию вызывает метод self.visit_classname, где classname имя класса ноды или generic_visit(), если этот метод не существует.

generic_visit(node)

Посетитель вызывает visit() на всех дочерних узлах.

Обратите внимание, что дочерние ноды нод с иным методом посетителя не будут посещаться, если только посититель не вызовет generic_visit() или сам не посетит их.

Не используйте NodeVisitor, если требуется во время прохождения применить изменения к нодам. Для этого существует специальный посетитель (NodeTransformer), который допускает модификации.

Не рекомендуется, начиная с версии 3.8: Методы visit_Num(), visit_Str(), visit_Bytes(), visit_NameConstant() и visit_Ellipsis() теперь запрещены и не будут вызываться в будующих Python версиях. Добавлен метод visit_Constant() для обработки всех константных нод.

class ast.NodeTransformer

Подкласс NodeVisitor обходит абстрактное синтаксическое дерево и позволяет менять ноды.

NodeTransformer обходит AST и использовать возвращаемое значение методов посещения для замены или удаления старых нод. Если возвращаемое значение метода visitor является None, нода удаляется из его местоположения, в противном случае он заменяется возвращаемым значением. Возвращаемое значение может быть исходной, в этом случае замена не выполняется.

Вот пример преобразователя, который перезаписывает все вхождения найденных имён (foo) в data['foo']:

class RewriteName(NodeTransformer):

    def visit_Name(self, node):
        return Subscript(
            value=Name(id='data', ctx=Load()),
            slice=Index(value=Constant(value=node.id)),
            ctx=node.ctx
        )

Помните, что если нода, над которой вы работаете, имеет дочерние ноды, необходимо либо преобразовать дочерние ноды самостоятельно, либо вызвать метод generic_visit() для этой ноды.

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

Если NodeTransformer вводит новые ноды (которые не были частью исходного дерева) без предоставления им информации о местоположении (например, lineno), fix_missing_locations() следует вызвать вместе с новым поддеревом для повторного расчета информации о местоположении:

tree = ast.parse('foo', mode='eval')
new_tree = fix_missing_locations(RewriteName().visit(tree))

Обычно преобразователь используется следующим образом:

node = YourTransformer().visit(node)
ast.dump(node, annotate_fields=True, include_attributes=False)

Возвращение отформатированного дампа дерева нод. Бывает полезно для отладки. Если annotate_fields имеет значение True (по умолчанию), в возвращаемой строке будут отображаться имена и значения полей. Если annotate_fields является False, то строка результата будет более компактным, если не указать однозначные имена полей. По умолчанию такие атрибуты, как номера строк и смещения столбцов, не сбрасываются. Если это требуется, include_attributes можно установить значение в True.

См.также

Зеленые Древесные Змеи, внешний ресурс документации, имеет хорошие сведения о работе с Python AST.

ASTTokens аннотирует Python AST с позициями токенов и текста в исходном коде, который их сгенерировал. Это полезно для инструментов, которые делают преобразования исходного кода.

leoAst.py объединяет представления программ python на основе токенов и дерева синтаксического анализа, вставляя двухсторонние связи между токеныами и узлами ast.

LibCST анализирует код как конкретное синтаксическое дерево, которое выглядит как дерево ast и сохраняет все детали форматирования. Он полезен для создания приложений автоматизированного рефакторинга (codemod) и линтеров.

Parso — это парсер Python, который поддерживает восстановление ошибок и синтаксический анализ туда и обратно для различных версий Python (в нескольких версиях Python). Parso также может перечислить несколько синтаксических ошибок в вашем файле python.