Python中如何让工厂构造函数表现得像普通构造函数?
实现优雅且安全的Cell实例复用机制
我最近碰到这么个需求:需要做一个和坐标系关联的Cell类,核心要求是每个坐标对应唯一的Cell实例,大部分坐标可能没有对应的实例。而且我希望能像调用普通类一样,直接写Cell(coord)就能拿到实例——如果这个坐标已经有Cell了就返回现成的,没有就自动创建新的,同时还要避免不小心直接调用构造函数导致重复创建实例,还得保证返回的实例确实是Cell类型。
最开始试了两种方案,都不太满意:
- 工厂方法方案:给Cell类加了个
factory类方法,虽然能实现复用,但每次都要写Cell.factory(coord),不够直观,而且还是能直接调用Cell(coord)创建重复实例,有风险。 - 隐藏内部类方案:把真正的Cell实现成
_Cell内部类,然后写个Cell函数返回_Cell.factory(coord)的结果。这下确实不会重复创建了,但返回的实例类型是_Cell而不是Cell,不符合预期。
后来才反应过来,这其实就是**记忆化(Memoization)**的典型场景!用装饰器就能完美解决问题,既保持了普通类的调用方式,又能自动处理实例缓存,还能保证实例类型正确。
最终解决方案:记忆化装饰器装饰类
直接写一个记忆化装饰器,把它应用在Cell类上。这样每次调用Cell(coord)时,装饰器会先检查缓存里有没有对应坐标的实例,有就返回,没有就创建新实例并缓存起来。
完整代码示例
_memoized = {} def memoize(f): """ 专门针对类实例化的记忆化装饰器 """ def memoized(*args): key = (*args,) if key not in _memoized: _memoized[key] = f(*args) return _memoized[key] return memoized @memoize class Cell(): index = -1 def __init__(self,coord): self.__class__.index += 1 self.index = self.__class__.index self.coord = coord def __repr__(self): return "{}<{}>".format(self.__class__.__name__,self.index,self.coord) # 测试:只会创建2个不同的Cell实例 print([Cell(i) for i in (0,1,0,1)])
这个方案的优点
- 用法直观:完全和普通类实例化一样,直接写
Cell(coord)就行,不用额外记工厂方法 - 安全可靠:没办法通过直接调用构造函数创建重复实例,因为装饰器拦截了所有实例化请求
- 类型正确:返回的实例都是
Cell类型,完全符合预期 - 自动缓存:缓存逻辑由装饰器处理,不用手动维护lookup字典,代码更简洁
进阶优化:把缓存绑定到类本身
上面的代码用了全局的_memoized字典,如果有多个类需要用这个装饰器会冲突。可以把缓存放到类自己的属性里,更符合封装原则:
def memoize(cls): """ 改进版:缓存绑定到目标类 """ cls._lookup = {} def wrapper(*args): key = (*args,) if key not in cls._lookup: cls._lookup[key] = cls(*args) return cls._lookup[key] return wrapper @memoize class Cell(): index = -1 def __init__(self,coord): self.__class__.index += 1 self.index = self.__class__.index self.coord = coord def __repr__(self): return "{}<{}>".format(self.__class__.__name__,self.index,self.coord) # 测试效果一样 print([Cell(i) for i in (0,1,0,1)]) # 还能直接访问类的缓存 print(Cell._lookup)
这样每个被装饰的类都有自己独立的缓存,不会互相干扰,后续如果需要清理缓存或者查看已有实例,直接操作类的_lookup属性就行。
内容的提问来源于stack exchange,提问作者Haydon Berrow
相关产品推荐
相关产品推荐

