iter () реализован как генератор

у меня есть подкласс объекта, который реализует динамическую отправку __ 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 ответов


Это кажется очень хрупким подходом. Достаточно изменить любой из _ _ слотов,_ _ dc _ list,_ _ iter _ cache во время активной итерации, чтобы привести объект в несогласованное состояние.

вам нужно либо запретить изменение объекта во время итерации, либо создать все элементы кэша сразу и вернуть копию списка.


container.__iter__() возвращает объект iterator. Сами объекты итератора должны поддерживать следующие два метода, которые вместе образуют протокол итератора:

iterator.__iter__()

возвращает сам объект iterator.

iterator.next()

возврат следующего элемента из контейнера.

это именно то, что есть у каждого генератора. Так что не бойтесь никаких побочных эффектов.


возможно, было бы лучше отделить итерацию объекта от кэширования возвращаемых им значений. Это упростит процесс итерации и позволит вам легко контролировать, как выполняется кэширование, а также включено ли оно или нет, например.

другое возможно важным соображением является тот факт, что ваш код не будет предсказуемо обрабатывать ситуацию, когда объект, который повторяется, изменяется между последовательными вызовами метод. Один простой способ справиться с этим-полностью заполнить содержимое кэша при первом вызове, а затем просто yield что он содержит для каждого вызова -- и документировать поведение.


то, что вы делаете, действительно, хотя и странно. Что такое __slots или __dc_list ?? Как правило, лучше описать содержимое вашего объекта в имени атрибута, а не его тип (например, 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())