python 动态构造class
十二月 10, 2025 [杂项] #Python #obsidianpython 动态构造class
动态执行代码(如 exec)后,用 inspect 或直接构造源码字符串
,将定义的类及其属性保存为 .py 文件。核心思路是:在动态定义类之后,提取其结构(类名、属性、方法),再生成对应的 Python 源码并写入文件。下面提供两种实用方案(推荐方案一):
✅ 方案一:动态定义后,手动构造源码字符串(最可靠,可控性强)
生成的 generated_class.py 内容为:
✅ 优点:简单、安全、可读性强;适合普通数据类型(str/int/float/list/dict 等) ⚠ 注意:
repr()对复杂对象(如函数、自定义类实例)可能不理想,需额外处理
✅ 方案二:使用 inspect + ast(高级,支持方法,但较复杂)
如果类中还有动态添加的方法,可用 inspect.getsource()(但要求方法是定义在源码中的,exec 中的 lambda/def 通常拿不到源码),更通用的是结合 ast 构建(略复杂)。
简化版支持方法(仅当方法是 def 形式且在 exec 字符串中):
🔒 安全提醒(重要!)
exec()有安全风险!切勿执行不可信来源的代码(可导致任意代码执行)。- 如需持久化类定义,更推荐:用模板(如 Jinja2)或数据驱动方式生成代码,而非
exec→ 再保存。
🎯 进阶建议(生产环境)
如果你频繁需要“动态建类 → 序列化为代码”,可考虑:
- 使用
dataclasses+@dataclass动态生成 - 用
type(name, bases, dict)构造类,并记录dict内容 - 用
pydantic或attrs库辅助生成可序列化结构
需要我帮你封装一个通用函数 save_class_to_file(cls, filename) 吗?欢迎补充你的具体场景~
非常好的问题!我们来系统讲解——如何在生产环境中安全、可靠地动态生成并持久化 Python 类定义,避免 exec 的安全与维护隐患。
核心思想:数据驱动 + 模板生成 👉 将「类的结构」抽象为数据(如字典、YAML/JSON 配置) ,再通过模板渲染成合法 Python 源码。
✅ 推荐方案总览
| 场景 | 推荐工具 | 优点 |
|---|---|---|
| 简单类(属性为主) | string.Template/ f-string | 无依赖,轻量 |
| 复杂类(属性+方法+注解) | Jinja2 | 表达力强,模板复用 |
| 需类型提示 / 数据校验 | pydantic+ 动态生成 | 类型安全,自文档化 |
| 高性能 / 编译时生成 | ast+compile | 100% 合法 AST,可 lint/format |
🔧 方案一:Jinja2 模板驱动(最推荐!)
步骤 1:定义类结构为数据
# config.py 或 config.yaml
class_spec = {
"name": "User",
"docstring": "动态生成的用户模型",
"attributes": [
{"name": "id", "type": "int", "default": 0, "comment": "用户ID"},
{"name": "name", "type": "str", "default": "''", "comment": "用户名"},
{"name": "email", "type": "str | None", "default": "None"},
{"name": "_created_at", "type": "float", "default": "time.time()", "private": True},
],
"methods": [
{
"name": "greet",
"args": ["self"],
"return_type": "str",
"body": [
'return f"Hello, {self.name}!"'
]
}
],
"imports": ["import time"]
}
步骤 2:编写 Jinja2 模板 class_template.j2
{{ '\n'.join(imports) if imports else '' }}
class {{ name }}:
"""{{ docstring }}"""
{% for attr in attributes %}
{% if attr.comment %}# {{ attr.comment }}{% endif %}
{{ attr.name }}: {{ attr.type }} = {{ attr.default }}
{% endfor %}
{% for method in methods %}
def {{ method.name }}({{ ', '.join(method.args) }}){% if method.return_type %} -> {{ method.return_type }}{% endif %}:
{% for line in method.body %}
{{ line }}
{% endfor %}
{% endfor %}
步骤 3:渲染并保存
from jinja2 import Template
import time # 仅用于示例,实际由模板 import
# 读取模板
with open("class_template.j2") as f:
template_str = f.read()
template = Template(template_str)
source_code = template.render(**class_spec)
# 写入文件
with open("models/user.py", "w", encoding="utf-8") as f:
f.write(source_code)
print("✅ 已生成 user.py")
✅ 输出 user.py
import time
class User:
"""动态生成的用户模型"""
# 用户ID
id: int = 0
# 用户名
name: str = ''
email: str | None = None
_created_at: float = time.time()
def greet(self) -> str:
return f"Hello, {self.name}!"
✅ 优势:
- 完全可控、可审查
- 支持复杂逻辑(条件渲染、循环)
- 模板可版本管理、复用
- 避免
exec安全风险- 生成代码可通过
flake8/black检查
🚀 方案二:pydantic 动态模型(适合数据模型)
若你的类本质是数据载体(如 API Schema、配置类),直接用 pydantic:
from pydantic import BaseModel, create_model
from typing import Optional
# 1. 用数据定义字段
fields = {
'id': (int, 0),
'name': (str, ''),
'email': (Optional[str], None),
}
# 2. 动态创建模型类
UserModel = create_model('User', **fields)
# 3. 持久化为代码(利用 pydantic 的 schema 或手动)
import inspect
print(inspect.getsource(UserModel)) # ❌ 不可行!create_model 无源码
# ✅ 正确做法:用 pydantic2 的 `model_json_schema()` + 模板生成
schema = UserModel.model_json_schema()
# 再用模板(同上)生成 class 定义
或——更优:用 datamodel-code-generator 工具链
# 从 JSON Schema 生成 pydantic 模型
pip install datamodel-code-generator
datamodel-codegen --input user_schema.json --output user_model.py
⚙️ 方案三:AST 编程(工业级,100% 合法 Python)
用 ast 模块构建语法树,确保生成的代码语法绝对正确(适合 IDE 插件、代码生成器):
import ast
import astor # pip install astor 用于 ast -> source
# 构建 class 节点
class_node = ast.ClassDef(
name="Config",
bases=[],
keywords=[],
body=[
ast.AnnAssign(
target=ast.Name(id="debug", ctx=ast.Store()),
annotation=ast.Name(id="bool", ctx=ast.Load()),
value=ast.Constant(value=True),
simple=1
),
ast.AnnAssign(
target=ast.Name(id="host", ctx=ast.Store()),
annotation=ast.Name(id="str", ctx=ast.Load()),
value=ast.Constant(value="localhost"),
simple=1
),
],
decorator_list=[]
)
# 包装为模块
module = ast.Module(body=[class_node], type_ignores=[])
# 生成源码
source = astor.to_source(module)
with open("config.py", "w") as f:
f.write(source)
输出:
class Config:
debug: bool = True
host: str = 'localhost'
✅ 优势:无字符串拼接错误,可被
ast.parse()安全解析 🔧 适用:代码生成工具(如 ORM migration、Protobuf → Python)
📌 生产环境最佳实践建议
| 需求 | 推荐方案 |
|---|---|
| 快速原型 / 内部工具 | Jinja2 模板(+ YAML 配置) |
| API/数据模型 | pydantic.create_model+ 代码生成器 |
| IDE 插件 / 编译器后端 | ast+astor/libcst |
| 避免运行时依赖 | 生成.py文件 + Git 提交(而非运行时 exec) |
🌰 附:完整可运行的 Jinja2 示例(复制即用)
# generate_class.py
from jinja2 import Template
TEMPLATE = '''
{%- if imports %}
{{ imports | join('\\n') }}
{%- endif %}
class {{ name }}:
"""{{ docstring }}"""
{%- for attr in attributes %}
{%- if attr.comment %}# {{ attr.comment }}{% endif %}
{{ attr.name }}: {{ attr.type }} = {{ attr.default }}
{%- endfor %}
{%- for method in methods %}
def {{ method.name }}({{ method.args | join(', ') }}){% if method.return_type %} -> {{ method.return_type }}{% endif %}:
{%- for line in method.body %}
{{ line }}
{%- endfor %}
{%- endfor %}
'''
def save_class_to_file(spec, filename):
template = Template(TEMPLATE.strip())
code = template.render(**spec)
with open(filename, "w", encoding="utf-8") as f:
f.write(code + "\n")
print(f"✅ Saved to {filename}")
# 使用
spec = {
"name": "DBConfig",
"docstring": "数据库配置类",
"imports": ["from typing import Optional"],
"attributes": [
{"name": "host", "type": "str", "default": "'localhost'"},
{"name": "port", "type": "int", "default": "5432"},
{"name": "password", "type": "Optional[str]", "default": "None", "comment": "建议使用环境变量"},
],
"methods": [
{
"name": "url",
"args": ["self"],
"return_type": "str",
"body": [
"return f'postgresql://user:{self.password}@{self.host}:{self.port}/db'"
]
}
]
}
save_class_to_file(spec, "db_config.py")
运行后生成 db_config.py ✅
需要我帮你:
- 做一个 YAML → Python 类 的完整工具?
- 生成
dataclass/pydantic/attrs专用模板? - 支持从数据库表结构反向生成 Model?
欢迎告诉我你的具体场景,我可以定制方案!
使用 type() 动态创建类
您无需生成代码文件,可以直接在内存中创建和使用类。type() 函数可以动态地创建类,这比 exec() 更安全、更具结构性。
type(name, bases, dict):
name: 类的名称 (字符串)。bases: 继承的父类(元组)。dict: 类的属性和方法(字典)。
Python
# 动态数据
class_name = "ConfigurableProcessor"
# 动态添加的方法
def run(self, data):
return f"Processing data: {data}"
# 动态创建类的方法字典
class_methods = {
'docstring': '一个基于配置动态创建的处理器。',
'version': '1.0',
'run': run # 添加 run 方法
}
# 使用 type() 动态创建类
DynamicClassInMemory = type(
class_name, # 类名
(object,), # 继承自 object
class_methods # 方法和属性
)
# 立即使用这个在内存中创建的类
processor = DynamicClassInMemory()
print(processor.run("report_A"))
# 检查类名和属性
print(f"Class Name: {processor.__class__.__name__}")
print(f"Class Version: {processor.version}")
Jinja 处理自定义类型
给自定义类型实现 __repr__,让模板直接使用
class Age:
def __init__(self, years): self.years = years
def __repr__(self):
return f"Age({self.years})"
class User:
age = {{ age_value }}
使用 Jinja Filter 自定义序列化方式(推荐)
如果你不想污染你的类(例如第三方类),你可以给 Jinja2 注册一个 filter:
def to_code(val):
if hasattr(val, "to_code"):
return val.to_code() # 推荐方式
return repr(val)
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader("."))
env.filters["to_code"] = to_code
template = env.get_template("model.j2")
class User:
age = {{ age | to_code }}
class Age:
def __init__(self, years): self.years = years
def to_code(self):
return f"Age({self.years})"