11

我有一个对象子类,它__ 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

它似乎正在工作......有什么我可能不知道的陷阱吗?我在做一些可笑的事情吗?

4

4 回答 4

7

container.__iter__()返回一个迭代器对象。迭代器对象本身需要支持以下两种方法,它们共同构成了迭代器协议:

iterator.__iter__()

返回迭代器对象本身。

iterator.next()

从容器中返回下一个项目。

这正是每台发电机所拥有的。所以不要害怕任何副作用。

于 2012-07-05T15:31:05.533 回答
3

这似乎是一种非常脆弱的方法。在活动迭代期间更改任何 __slots、__dc_list、__iter_cache 足以使对象进入不一致状态。

您需要在迭代期间禁止更改对象或一次生成所有缓存项并返回列表的副本。

于 2012-07-05T15:26:57.147 回答
2

将对象的迭代与其返回的值的缓存分开可能会更好。例如,这将简化迭代过程并允许您轻松控制缓存的完成方式以及是否启用缓存。

另一个可能重要的考虑因素是,您的代码无法预测性地处理被迭代的对象在对方法的连续调用之间发生变化的情况。处理这个问题的一个简单方法是在第一次调用时完全填充缓存的内容,然后yield为每次调用填充缓存的内容——并记录行为。

于 2012-07-05T16:09:44.193 回答
0

你在做什么是有效的,尽管很奇怪。什么是a__slots或a __dc_list??通常最好用属性名称而不是类型来描述对象的内容(例如:self.users 而不是 self.u_list)。

你可以使用我的LazyProperty装饰器来大大简化它。

只需使用 @LazyProperty 装饰您的方法。它将被第一次调用,然后装饰器将用结果替换属性。唯一的要求是该值是可重复的;它不依赖于可变状态。您在当前代码中也有这个要求,您的 self.__iter_cache。

def __iter__(self)
    return self.__iter

@LazyProperty
def __iter(self)
    def my_generator():
        yield whatever
    return tuple(my_generator())
于 2012-07-05T16:37:19.120 回答