You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:38:29