Методы автоматической генерации скриптов 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