← Назад к вопросам

Что такое Абстрактное синтаксическое дерево (abstract syntax tree)?

2.0 Middle🔥 171 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Абстрактное синтаксическое дерево (AST)

Abstract Syntax Tree (AST) — это древовидная структура данных, которая представляет синтаксическую структуру исходного кода. Каждый узел дерева соответствует конструкции исходного языка (функция, оператор, выражение и т.д.), а ребра показывают отношения между ними.

Как работает парсинг кода

Исходный код:  a = 5 + 3
        |
        v
Токенизация:  [NAME(a), ASSIGN(=), NUMBER(5), PLUS(+), NUMBER(3)]
        |
        v
Парсинг:       AST
        |
        v
Модуль
  |
  Assign
    |
    +-- targets: [Name('a')]
    +-- value: BinOp
            |
            +-- left: Constant(5)
            +-- op: Add()
            +-- right: Constant(3)

AST в Python

Python имеет встроенный модуль ast для работы с синтаксическими деревьями:

import ast

code = '''
a = 5 + 3
b = a * 2
'''

# Парсим код в AST
tree = ast.parse(code)

# Выводим структуру
print(ast.dump(tree, indent=2))

Вывод:

Module(
  body=[
    Assign(
      targets=[Name(id='a')],
      value=BinOp(
        left=Constant(value=5),
        op=Add(),
        right=Constant(value=3))),
    Assign(
      targets=[Name(id='b')],
      value=BinOp(
        left=Name(id='a'),
        op=Mult(),
        right=Constant(value=2)))
  ])

Основные типы узлов AST

import ast

# Выражения (Expression)
code = "x + 5"
node = ast.parse(code)
print(type(node.body[0].value))  # <class '_ast.BinOp'>

# Функции (FunctionDef)
def foo(x):
    return x * 2

tree = ast.parse('def foo(x):\n    return x * 2')
func = tree.body[0]
print(type(func))  # <class '_ast.FunctionDef'>
print(func.name)   # 'foo'
print(func.args.args)  # Аргументы функции

# Циклы
for_loop = ast.parse('for i in range(10): print(i)')
print(type(for_loop.body[0]))  # <class '_ast.For'>

# If-else
if_stmt = ast.parse('if x > 5:\n    print("больше")')
print(type(if_stmt.body[0]))  # <class '_ast.If'>

Обход AST (NodeVisitor)

NodeVisitor позволяет обойти дерево и выполнить действия для разных типов узлов:

import ast

class FunctionFinder(ast.NodeVisitor):
    """Находит все функции в коде"""
    
    def __init__(self):
        self.functions = []
    
    def visit_FunctionDef(self, node):
        """Вызывается для каждой функции"""
        self.functions.append({
            'name': node.name,
            'args': [arg.arg for arg in node.args.args],
            'line': node.lineno
        })
        self.generic_visit(node)  # Обходим дочерние узлы
    
    def visit_AsyncFunctionDef(self, node):
        """Асинхронные функции"""
        self.functions.append({
            'name': node.name,
            'type': 'async',
            'args': [arg.arg for arg in node.args.args]
        })
        self.generic_visit(node)

code = '''
def hello(name):
    print(f"Hello, {name}")

async def fetch_data(url):
    pass
'''

tree = ast.parse(code)
finder = FunctionFinder()
finder.visit(tree)

for func in finder.functions:
    print(func)
# {'name': 'hello', 'args': ['name'], 'line': 1}
# {'name': 'fetch_data', 'type': 'async', 'args': ['url']}

Практический пример: Поиск импортов

import ast

class ImportFinder(ast.NodeVisitor):
    """Находит все импорты в коде"""
    
    def __init__(self):
        self.imports = []
    
    def visit_Import(self, node):
        """import x, y, z"""
        for alias in node.names:
            self.imports.append({
                'type': 'import',
                'module': alias.name,
                'alias': alias.asname
            })
        self.generic_visit(node)
    
    def visit_ImportFrom(self, node):
        """from x import y, z"""
        for alias in node.names:
            self.imports.append({
                'type': 'from',
                'module': node.module,
                'name': alias.name,
                'alias': alias.asname
            })
        self.generic_visit(node)

code = '''
import os
import sys as system
from django.db import models
from collections import defaultdict as dd
'''

tree = ast.parse(code)
finder = ImportFinder()
finder.visit(tree)

for imp in finder.imports:
    print(imp)

Практический пример: Поиск переменных и их использования

import ast

class VariableAnalyzer(ast.NodeVisitor):
    """Анализирует использование переменных"""
    
    def __init__(self):
        self.assignments = set()  # Переменные которые присваиваются
        self.usages = set()  # Переменные которые используются
    
    def visit_Assign(self, node):
        """x = 5"""
        for target in node.targets:
            if isinstance(target, ast.Name):
                self.assignments.add(target.id)
        self.generic_visit(node)
    
    def visit_Name(self, node):
        """Использование переменной"""
        if isinstance(node.ctx, ast.Load):
            self.usages.add(node.id)
        self.generic_visit(node)

code = '''
x = 5
y = x + 10
z = y * 2
print(x, y, z)
'''

tree = ast.parse(code)
analyzer = VariableAnalyzer()
analyzer.visit(tree)

print(f'Присвоены: {analyzer.assignments}')
print(f'Используются: {analyzer.usages}')
print(f'Неиспользованные: {analyzer.assignments - analyzer.usages}')

Практический пример: Поиск потенциальных проблем

import ast

class ProblemFinder(ast.NodeVisitor):
    """Находит потенциальные проблемы в коде"""
    
    def __init__(self):
        self.problems = []
    
    def visit_Except(self, node):
        """Ловят все исключения без типа"""
        if node.type is None:
            self.problems.append(f'Line {node.lineno}: Bare except clause')
        self.generic_visit(node)
    
    def visit_Compare(self, node):
        """x == None вместо x is None"""
        for op, comparator in zip(node.ops, node.comparators):
            if isinstance(op, ast.Eq):
                if isinstance(comparator, ast.Constant) and comparator.value is None:
                    self.problems.append(f'Line {node.lineno}: Use "is None" not "== None"')
        self.generic_visit(node)

code = '''
try:
    x = 1 / 0
except:  # Проблема!
    pass

if x == None:  # Проблема!
    pass
'''

tree = ast.parse(code)
finder = ProblemFinder()
finder.visit(tree)

for problem in finder.problems:
    print(problem)

Изменение AST (NodeTransformer)

NodeTransformer позволяет изменять дерево:

import ast

class VariableRenamer(ast.NodeTransformer):
    """Переименовывает переменные"""
    
    def __init__(self, old_name, new_name):
        self.old_name = old_name
        self.new_name = new_name
    
    def visit_Name(self, node):
        if node.id == self.old_name:
            node.id = self.new_name
        return node

code = '''
x = 5
y = x + 10
print(x, y)
'''

tree = ast.parse(code)
renamer = VariableRenamer('x', 'variable')
tree = renamer.visit(tree)

# Преобразуем обратно в код
compiled = compile(tree, filename='<ast>', mode='exec')

# Или используем astor (pip install astor)
import astor
print(astor.to_source(tree))

Использование AST в реальном мире

1. Линтеры (pylint, flake8):

# Проверяют AST на проблемы стиля и логики

2. Форматеры (black):

# Парсят код в AST, затем форматируют обратно

3. Рефакторинг инструменты:

import ast

# Автоматическое переименование переменных
# Удаление неиспользуемых импортов
# Оптимизация кода

4. Анализ кода (инструменты типа mypy):

# Проверка типов на основе AST

Граммарика Python в AST

import ast

# Все возможные типы узлов
print(dir(ast))

# Основные:
# Module, FunctionDef, AsyncFunctionDef, ClassDef
# Return, Delete, Assign, AugAssign, For, AsyncFor, While
# If, With, AsyncWith, Try, Assert, Import, ImportFrom
# Global, Nonlocal, Expr, Pass, Break, Continue

Ограничения AST

import ast

# AST теряет часть информации:
code = "x   =   5  # комментарий"
tree = ast.parse(code)

# Пробелы потеряны
# Комментарии потеряны
# Форматирование потеряно

# Для сохранения всей информации используйте libcst

Итого

AST используется для:

  • Анализа кода (линтеры, type checkers)
  • Трансформации кода (форматеры, рефакторинг)
  • Выполнение пользовательского кода безопасно
  • Создание инструментов разработки
  • Оптимизация кода

Ключевые компоненты:

  • ast.parse() — парсит код в дерево
  • ast.NodeVisitor — обходит дерево
  • ast.NodeTransformer — изменяет дерево
  • ast.dump() — выводит структуру

Альтернативы:

  • libcst — сохраняет точное форматирование
  • parso — парсер Python с полной информацией
  • ast-grep — grep для AST структур

AST это фундаментальный инструмент для работы с кодом на уровне структуры, а не текста.

Что такое Абстрактное синтаксическое дерево (abstract syntax tree)? | PrepBro