我有一个对象子类,它使用缓存生成器实现动态调度__ iter __
(我还有一个使iter缓存无效的方法),如下所示:
def __iter__(self):
print("iter called")
if self.__iter_cache is None:
iter_seen = {}
iter_cache = []
for name in self.__slots:
value = self.__slots[name]
iter_seen[name] = True
item = (name, value)
iter_cache.append(item)
yield item
for d in self.__dc_list:
for name, value in iter(d):
if name not in iter_seen:
iter_seen[name] = True
item = (name, value)
iter_cache.append(item)
yield item
self.__iter_cache = iter_cache
else:
print("iter cache hit")
for item in self.__iter_cache:
yield item
似乎工作......有没有我可能不知道的陷阱?我做了一些荒谬的事吗?
答案 0 :(得分:3)
container.__iter__()
返回一个迭代器对象。迭代器对象本身需要支持以下两种方法,它们共同构成迭代器协议:
iterator.__iter__()
返回迭代器对象本身。
iterator.next()
从容器中返回下一个项目。
这正是每台发电机所拥有的。所以不要害怕任何副作用。
答案 1 :(得分:2)
将对象的迭代与其返回的值的缓存分开可能更好。这将简化迭代过程,并允许您轻松控制缓存的完成方式以及是否启用缓存,例如。
另一个可能重要的考虑因素是,您的代码无法预测性地处理迭代对象在连续调用方法之间发生变化的情况。处理这种情况的一种简单方法是在第一次调用时完全填充缓存的内容,然后只为yield
每次调用包含的内容 - 并记录行为。
答案 2 :(得分:1)
这似乎是一种非常脆弱的方法。在活动迭代期间更改__slots,__ dc_list,__ iter_cache中的任何一个就足以将对象置于不一致状态。
您需要在迭代期间禁止更改对象或立即生成所有缓存项并返回列表的副本。
答案 3 :(得分:0)
你正在做的事情是有效的,尽管很奇怪。什么是__slots
或__dc_list
??通常,最好在属性名称中描述对象的内容,而不是其类型(例如:self.users而不是self.u_list)。
您可以使用我的LazyProperty装饰器来大幅简化这一过程。
使用@LazyProperty装饰您的方法。它将在第一次调用,然后装饰器将使用结果替换属性。唯一的要求是价值是可重复的;它不依赖于可变状态。你的当前代码中也有这个要求,你的自己.__ iter_cache。
def __iter__(self)
return self.__iter
@LazyProperty
def __iter(self)
def my_generator():
yield whatever
return tuple(my_generator())