Автоматическое создание скриптов на Python: инструменты и методики

Раздел: Инструменты -> Генерация

Основные методы генерации кода Python

Как программно создать исполняемый код Python, который будет корректен с точки зрения синтаксиса и структуры?

Наиболее эффективным решением является использование модуля ast (Abstract Syntax Tree). Он позволяет конструировать дерево синтаксического разбора, а затем с помощью функции ast.unparse (Python 3.9+) или сторонних библиотек (например, astor) превращать его обратно в строку кода. Такой подход гарантирует валидность кода и упрощает сложные модификации.

import ast

# Создаём узлы для функции
func_def = ast.FunctionDef(
    name='add',
    args=ast.arguments(
        posonlyargs=[],
        args=[ast.arg(arg='a'), ast.arg(arg='b')],
        kwonlyargs=[],
        kw_defaults=[],
        defaults=[]
    ),
    body=[
        ast.Return(value=ast.BinOp(
            left=ast.Name(id='a', ctx=ast.Load()),
            op=ast.Add(),
            right=ast.Name(id='b', ctx=ast.Load())
        ))
    ],
    decorator_list=[]
)

# Превращаем AST в код
code = ast.unparse(func_def)
print(code)

генерировать код python (генерировать код python)

def add(a, b):
    return a + b

Пошаговое описание:

  • Импортируем модуль ast.
  • Создаём объект FunctionDef, передавая имя, аргументы, тело и декораторы.
  • В теле указываем объект Return с выражением сложения (BinOp).
  • Вызываем ast.unparse для получения строки кода.

Проблемы и их решение:

  • Сложность ручного создания узлов – помогает изучение документации по ast и использование ast.dump для анализа существующего кода.
  • Отличия в версиях Python – в Python 3.8 нет ast.unparse, требуется astor или astunparse.

Как быстро сгенерировать небольшой фрагмент кода без сторонних библиотек?

Простейший способ – прямая конкатенация строк с учётом отступов. Для удобства используют тройные кавычки и textwrap.dedent.

import textwrap

code = textwrap.dedent('''\
    def multiply(a, b):
        return a * b
''')
print(code)
def multiply(a, b):
    return a * b

Этот метод пригоден для маленьких статических шаблонов, но становится громоздким при параметризации.

Частая ошибка: неправильное количество пробелов в отступах. Решение – всегда использовать одинаковое число пробелов (4) и применять textwrap.dedent для выравнивания.

Как параметризовать генерацию кода, подставляя разные имена и выражения?

Использование f-строк позволяет вставлять переменные в шаблон кода. Например, для создания функции с произвольным именем:

func_name = 'calc'
params = ['x', 'y']
expr = 'x ** 2 + y'

code = f"def {func_name}({', '.join(params)}):\n    return {expr}\n"
print(code)
def calc(x, y):
    return x ** 2 + y

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

Ошибка: забыть экранировать фигурные скобки внутри f-строки. Если в шаблоне нужна литеральная фигурная скобка, её удваивают: {{ }}.

Как генерировать целые файлы или проекты с многоуровневой структурой?

Для этого применяется шаблонизатор Jinja2. Он поддерживает циклы, условия, наследование шаблонов. Пример шаблона для генерации модуля:

from jinja2 import Template

template = Template('''
def {{ func_name }}({{ args|join(', ') }}):
    """{{ docstring }}"""
    return {{ expression }}
''')

code = template.render(
    func_name='hello',
    args=['name'],
    docstring='Prints a greeting',
    expression="f'Hello, {name}!'"
)
print(code)
def hello(name):
    """Prints a greeting"""
    return f'Hello, {name}!'

Jinja2 подходит для генерации конфигурационных файлов, тестов, DTO и других повторяющихся структур.

Типичная проблема: экранирование специальных символов Python (например, обратного слеша). Решение – использовать raw-строки в шаблоне или экранировать через |e.

Как получить код на основе естественно-языкового описания?

Современные языковые модели (GPT-4, Claude) позволяют генерировать код по текстовому запросу. Пример через библиотеку openai (требуется API-ключ):

import openai

response = openai.ChatCompletion.create(
    model="gpt-4",
    messages=[
        {"role": "user", "content": "Напиши функцию на Python, которая сортирует список чисел по убыванию."}
    ]
)
code = response.choices[0].message.content
print(code)
def sort_descending(numbers):
    return sorted(numbers, reverse=True)

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

Недостатки: зависимость от внешнего API, затраты, задержки, отсутствие контроля над форматированием. Решение – комбинировать генерацию с последующей верификацией через ast.parse.

Расширенные примеры генерации кода

Генерация класса с конструктором и методами через AST

Создадим класс Person с атрибутами имени и возраста, а также методом greet.

Пример
import ast

# Конструируем класс
class_def = ast.ClassDef(
    name='Person',
    bases=[],
    keywords=[],
    body=[
        # Метод __init__
        ast.FunctionDef(
            name='__init__',
            args=ast.arguments(
                posonlyargs=[],
                args=[ast.arg(arg='self'), ast.arg(arg='name'), ast.arg(arg='age')],
                kwonlyargs=[],
                kw_defaults=[],
                defaults=[]
            ),
            body=[
                ast.Assign(
                    targets=[ast.Attribute(value=ast.Name(id='self', ctx=ast.Load()), attr='name', ctx=ast.Store())],
                    value=ast.Name(id='name', ctx=ast.Load())
                ),
                ast.Assign(
                    targets=[ast.Attribute(value=ast.Name(id='self', ctx=ast.Load()), attr='age', ctx=ast.Store())],
                    value=ast.Name(id='age', ctx=ast.Load())
                )
            ],
            decorator_list=[]
        ),
        # Метод greet
        ast.FunctionDef(
            name='greet',
            args=ast.arguments(
                posonlyargs=[],
                args=[ast.arg(arg='self')],
                kwonlyargs=[],
                kw_defaults=[],
                defaults=[]
            ),
            body=[
                ast.Return(
                    value=ast.Call(
                        func=ast.Attribute(value=ast.Constant(value='Hello, my name is '), attr='format', ctx=ast.Load()),
                        args=[ast.Attribute(value=ast.Name(id='self', ctx=ast.Load()), attr='name', ctx=ast.Load())],
                        keywords=[]
                    )
                )
            ],
            decorator_list=[]
        )
    ],
    decorator_list=[]
)

# Преобразуем в строку
code = ast.unparse(class_def)
print(code)
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        return 'Hello, my name is '.format(self.name)

Результат – валидный Python-класс, готовый к выполнению.

Генерация декоратора для замера времени исполнения

Создадим шаблон декоратора с использованием f-строк и текстового шаблона.

Пример
import textwrap

def generate_timer_decorator(import_time=True):
    parts = []
    if import_time:
        parts.append('import time')
    parts.append('')
    parts.append('def timer(func):')
    parts.append('    def wrapper(*args, **kwargs):')
    parts.append('        start = time.time()')
    parts.append('        result = func(*args, **kwargs)')
    parts.append('        end = time.time()')
    parts.append('        print(f"Function {func.__name__} took {end-start:.4f} sec")')
    parts.append('        return result')
    parts.append('    return wrapper')
    return textwrap.dedent('\n'.join(parts))

code = generate_timer_decorator(import_time=True)
print(code)
import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"Function {func.__name__} took {end-start:.4f} sec")
        return result
    return wrapper

Этот код можно сразу сохранить в файл decorators.py и импортировать.

Генерация кода для Flask-эндпоинта на основе конфигурации

Используем Jinja2 для создания REST-обработчика.

Пример
from jinja2 import Template

route_template = Template('''
@app.route('{{ path }}', methods=[{{ methods | map('quote') | join(', ') }}])
def {{ func_name }}({{ params }}):
    """{{ doc }}"""
    # TODO: implement
    return {{ response }}
''')

config = {
    'path': '/api/users/',
    'methods': ['GET', 'PUT'],
    'func_name': 'user_detail',
    'params': 'user_id',
    'doc': 'Get or update user by ID',
    'response': '{"message": "ok"}'
}

code = route_template.render(config)
print(code)
@app.route('/api/users/', methods=['GET', 'PUT'])
def user_detail(user_id):
    """Get or update user by ID"""
    # TODO: implement
    return {"message": "ok"}

Так можно автоматически сгенерировать весь набор эндпоинтов, если есть список конфигураций.

Использование inspect для воссоздания функции из существующей сигнатуры

Иногда требуется сгенерировать код, эквивалентный уже существующей функции, но с изменёнными именами.

Пример
import inspect

def original_function(a, b: int = 10, *args, **kwargs) -> float:
    """Original docstring."""
    return a + b

# Получаем сигнатуру
sig = inspect.signature(original_function)
source = inspect.getsource(original_function)

# Создаём новую функцию с другим именем
new_name = 'new_function'
new_source = source.replace('original_function', new_name)
new_source = new_source.replace('Original docstring.', 'Generated copy.')

print(new_source)
def new_function(a, b: int = 10, *args, **kwargs) -> float:
    """Generated copy."""
    return a + b

Этот приём полезен для кодогенерации на основе рефлексии, например, при создании ORM-моделей.

Генерировать код Python - comments

En
генерировать код python (python)