动态创建可变对象:Python类工厂函数深度解析
在Python开发中,我们常遇到需要快速创建简单数据容器的场景。collections.namedtuple
提供了不可变解决方案,但当我们需要可变记录类型时,如何避免重复编写样板代码?本文将揭秘通过类工厂函数动态创建类的核心技术。
一、传统方式的痛点
定义简单数据类时,重复的__init__
代码违反DRY原则:
class Dog:def __init__(self, name, weight, owner): # 属性名重复3次!self.name = nameself.weight = weightself.owner = owner
不仅代码冗余,默认的__repr__
输出也不友好:<__main__.Dog at 0x2865bac>
二、理想解决方案
参考namedtuple
设计可变工厂函数:
Dog = record_factory('Dog', 'name weight owner')
rex = Dog('Rex', 30, 'Bob')
print(rex) # 输出: Dog(name='Rex', weight=30, owner='Bob')
rex.weight = 32 # 支持修改属性
三、核心实现解析(附关键代码)
def record_factory(cls_name, field_names):# 1. 字段名处理(支持空格/逗号分隔)field_names = tuple(field_names.replace(',', ' ').split())# 2. 动态构建__init__def __init__(self, *args, kwargs):attrs = dict(zip(self.__slots__, args))attrs.update(kwargs)for name, value in attrs.items():setattr(self, name, value)# 3. 实现可迭代协议 def __iter__(self):for name in self.__slots__:yield getattr(self, name)# 4. 生成友好__repr__def __repr__(self):values = ', '.join(f'{name}={getattr(self, name)!r}' for name in self.__slots__)return f'{cls_name}({values})'# 5. 组装类属性字典 cls_attrs = {'__slots__': field_names, # 固定属性节省内存'__init__': __init__,'__iter__': __iter__,'__repr__': __repr__}# 6. 动态创建类 return type(cls_name, (object,), cls_attrs)
四、关键技术原理
1. type
的三元用法
type(cls_name, bases, attrs)
是动态创建类的核心:
cls_name
:类名字符串bases
:继承的父类元组attrs
:包含属性和方法的字典
2. __slots__
优化
显式声明属性:
- 禁止动态添加新属性
- 内存占用比常规类少40%-50%
- 固定属性顺序保证迭代一致性
3. 鸭子类型处理参数
try:field_names = field_names.replace(',', ' ').split()
except AttributeError:pass # 直接使用传入的可迭代对象
五、对比两种动态类实现方式
方法 | 优势 | 缺陷 |
---|---|---|
type() 构建 | 无安全风险,代码直观 | 无法生成源码字符串 |
exec 模板 | 支持._source 查看源码 | 存在代码注入安全风险 |
注:
collections.namedtuple
使用exec
实现是为了支持源码查看
六、局限性及解决方案
当前实现不支持序列化(pickle):
import pickle
pickle.dumps(rex) # 报错:Can't pickle Dog...
解决方案:
参考官方namedtuple
实现,添加__reduce__
方法处理序列化
七、适用场景建议
- 配置参数容器:替代字典存储配置项,支持属性访问
- 临时数据对象:测试时快速构建模拟对象
- 协议转换中间件:CSV/JSON等数据转换为Python对象
生产环境推荐使用
dataclasses
(Python 3.7+) 或attrs
库,它们提供更完整的特性(类型提示、序列化等)
附录:性能对比(百万实例内存占用)
- 普通类:≈1.2GB
__slots__
类:≈650MBnamedtuple
:≈550MBrecord_factory
:≈650MB(与__slots__
类相当)
通过动态类创建技术,我们实现了简洁性与灵活性的平衡。虽然现代Python有更先进的替代方案,但理解其底层原理对掌握元编程至关重要。