Методы автоматической генерации скриптов Python

Раздел: Python -> Генерация и помощь

Генерация кода Python: основные подходы и инструменты

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

Наиболее эффективное решение: шаблоны Jinja2

Как создавать сложные шаблоны кода с условной логикой и циклами?

Jinja2 - мощный шаблонизатор, часто используемый во Flask и других проектах. Он позволяет отделить логику генерации от самого кода и поддерживает наследование, фильтры и макросы. Для установки используется команда pip install jinja2.


from jinja2 import Template

template_code = '''
class {{ class_name }}:
    def __init__(self, {{ params }}):
        {{ params_init }}
'''
template = Template(template_code)
rendered = template.render(
    class_name="MyClass",
    params="x, y",
    params_init="self.x = x\n        self.y = y"
)
print(rendered)
  

генерации кода python (генерация кода python)

class MyClass:
    def __init__(self, x, y):
        self.x = x
        self.y = y
  

ии код python онлайн (ии для написания кода python онлайн)

Возможные проблемы и их решения:

  • Отступы в шаблоне: если шаблон содержит отступы, они становятся частью вывода. Рекомендуется использовать indent или формировать строки с нулевым отступом и добавлять его программно.
  • Экранирование фигурных скобок: в Jinja2 {{ }} зарезервированы, для вывода литерала используется {% raw %}...{% endraw %} или удвоение скобок.

Вариант 1: f-строки с textwrap.dedent

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

f-строки обеспечивают простую подстановку переменных, а textwrap.dedent удаляет лишние отступы, делая код читаемым.


import textwrap

name = "User"
age = 30
code = textwrap.dedent(f'''
    class {name}:
        age = {age}
        def greet(self):
            return "Hello from {name}"
''')
print(code)
  
class User:
    age = 30
    def greet(self):
        return "Hello from User"
  

Проблемы f-строк:

  • Сложность с многострочными выражениями внутри фигурных скобок.
  • Невозможность использовать фигурные скобки вне шаблона без экранирования (удвоение).
  • При большом проекте код становится трудно поддерживать.

Вариант 2: string.Template

Как генерировать код с безопасной подстановкой, избегая инъекций?

string.Template из стандартной библиотеки поддерживает только замену по $variable и ${variable}, что ограничивает возможности, но делает его безопаснее для пользовательского ввода.


from string import Template

t = Template('''
def $funcname($args):
    return $value
''')
code = t.substitute(funcname="add", args="a, b", value="a + b")
print(code)
  
def add(a, b):
    return a + b
  

Ограничение: нельзя использовать условные операторы и циклы в шаблоне. Для динамических структур приходится подготавливать данные заранее. Также символ $ нужно экранировать как $$.

Вариант 3: Генерация через AST и компиляция

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

Модуль ast позволяет построить дерево узлов, а затем преобразовать его в исполняемый код с помощью compile() и exec(). Это даёт максимальный контроль над структурой.


import ast

# Создаём узлы вручную
assign = ast.Assign(
    targets=[ast.Name(id='x', ctx=ast.Store())],
    value=ast.Constant(value=42)
)
mod = ast.Module(body=[assign], type_ignores=[])
code = compile(mod, filename='', mode='exec')
exec(code)
print(x)  # 42
  
42
  

Сложность: требуется знание структуры AST. Для генерации больших фрагментов рекомендуется использовать библиотеки-обёртки, например astor для обратной конвертации в строку. Ошибки в узлах могут привести к некорректному коду.

Вариант 4: Динамическое выполнение с exec/eval

Как выполнить код, сгенерированный на лету?

exec() и eval() позволяют выполнить строку как Python-код. Однако они небезопасны и должны использоваться только с доверенными данными.


code_str = "result = sum([1, 2, 3, 4])"
exec(code_str)
print(result)
  
10
  

Риски:

  • Инъекция вредоносного кода.
  • Трудности с отладкой.
  • Невозможность статической проверки.

Для минимизации рисков следует ограничить глобальные и локальные переменные, передаваемые в exec/eval.

Расширенный пример 1: Генерация FastAPI эндпоинта с помощью Jinja2

Шаблон для создания обработчика GET-запроса с одним параметром. Jinja2 позволяет подставить имя функции, путь и возвращаемое значение.

Пример

from jinja2 import Template

endpoint_template = Template('''
@app.get("{{ path }}")
async def {{ func_name }}({{ param }}: {{ param_type }}):
    return {"{{ param }}": {{ param }}}
''')

code = endpoint_template.render(
    path="/items/{item_id}",
    func_name="get_item",
    param="item_id",
    param_type="int"
)
print(code)
@app.get("/items/{item_id}")
async def get_item(item_id: int):
    return {"item_id": item_id}

Расширенный пример 2: Генерация pytest теста через f-строки

Создание тестовой функции для проверки сложения. f-строки удобны для небольших динамических тестов.

Пример

import textwrap

func_name = "test_add"
a, b, expected = 2, 3, 5

code = textwrap.dedent(f'''
def {func_name}():
    result = add({a}, {b})
    assert result == {expected}, "Expected {expected}, got " + str(result)
''')
print(code)
def test_add():
    result = add(2, 3)
    assert result == 5, "Expected 5, got " + str(result)

Расширенный пример 3: Генерация SQLAlchemy модели с помощью string.Template

Шаблон для модели с полями и типами. string.Template безопасен при вводе из внешних источников.

Пример

from string import Template

model_template = Template('''
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class ${class_name}(Base):
    __tablename__ = '${table_name}'
    id = Column(Integer, primary_key=True)
    ${field_name} = Column(String(50))
''')

code = model_template.substitute(
    class_name="User",
    table_name="users",
    field_name="username"
)
print(code)
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    username = Column(String(50))

Расширенный пример 4: Генерация argparse скрипта через AST

Создание дерева AST для парсера аргументов и компиляция в исполняемый код. Пример демонстрирует добавление одного аргумента.

Пример

import ast
import argparse

# Создаём узлы AST для парсера
import_node = ast.Import(names=[ast.alias(name='argparse', asname=None)])

parser_call = ast.Call(
    func=ast.Attribute(value=ast.Name(id='argparse', ctx=ast.Load()), attr='ArgumentParser', ctx=ast.Load()),
    args=[],
    keywords=[ast.keyword(arg='description', value=ast.Constant(value='Sample script'))]
)

add_arg = ast.Call(
    func=ast.Attribute(value=ast.Name(id='parser', ctx=ast.Load()), attr='add_argument', ctx=ast.Load()),
    args=[],
    keywords=[
        ast.keyword(arg='name', value=ast.Constant(value='--name')),
        ast.keyword(arg='help', value=ast.Constant(value='Your name'))
    ]
)

assign_parser = ast.Assign(targets=[ast.Name(id='parser', ctx=ast.Store())], value=parser_call)
assign_args = ast.Call(
    func=ast.Attribute(value=ast.Name(id='parser', ctx=ast.Load()), attr='parse_args', ctx=ast.Load()),
    args=[],
    keywords=[]
)
assign_result = ast.Assign(targets=[ast.Name(id='args', ctx=ast.Store())], value=assign_args)

print_hello = ast.Call(
    func=ast.Name(id='print', ctx=ast.Load()),
    args=[ast.BinOp(left=ast.Constant(value='Hello, '), op=ast.Add(), right=ast.Attribute(value=ast.Name(id='args', ctx=ast.Load()), attr='name', ctx=ast.Load()))],
    keywords=[]
)

mod = ast.Module(body=[import_node, assign_parser, add_arg, assign_result, print_hello], type_ignores=[])

code = compile(mod, filename='', mode='exec')
exec(code)
# Запуск с параметром: python script.py --name World
При запуске с аргументом --name World выведет: Hello, World

Расширенный пример 5: Генерация dataclass с декоратором

Использование комбинации f-строк и ручной сборки для создания класса данных с полями.

Пример

from dataclasses import dataclass

class_name = "Point"
fields = [("x", "float"), ("y", "float")]

field_str = "\n    ".join([f"{name}: {typ}" for name, typ in fields])
code = f'''
from dataclasses import dataclass

@dataclass
class {class_name}:
    {field_str}
'''
print(code)
from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

Генерация кода Python - comments

En
генерации кода python (python)