Подтвердить что ты не робот

Как клонировать объект генератора Python?

Рассмотрим этот сценарий:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os

walk = os.walk('/home')

for root, dirs, files in walk:
    for pathname in dirs+files:
        print os.path.join(root, pathname)

for root, dirs, files in walk:
    for pathname in dirs+files:
        print os.path.join(root, pathname)

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

Я пробовал walk2 = walk клонировать и использовать на второй итерации, но это не сработало. Вопрос... Как я могу его скопировать? Возможно ли это?

Спасибо заранее.

4b9b3361

Ответ 1

Вы можете использовать itertools.tee():

walk, walk2 = itertools.tee(walk)

Обратите внимание, что это может потребовать значительного дополнительного хранения, как указано в документации.

Ответ 2

Если вы знаете, что собираетесь перебирать весь генератор для каждого использования, вы, вероятно, получите максимальную производительность, развернув генератор в список и используя список несколько раз.

walk = list(os.walk('/home'))

Ответ 3

Определить функцию

 def walk_home():
     for r in os.walk('/home'):
         yield r

Или даже это

def walk_home():
    return os.walk('/home')

Оба используются следующим образом:

for root, dirs, files in walk_home():
    for pathname in dirs+files:
        print os.path.join(root, pathname)

Ответ 4

Это хороший пример использования для functools.partial() сделать быструю фабрику-генератор:

from functools import partial
import os

walk_factory = partial(os.walk, '/home')

walk1, walk2, walk3 = walk_factory(), walk_factory(), walk_factory()

То, что делает functools.partial(), трудно описать человеческими словами, но это то, для чего оно.

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

Ответ 5

Этот ответ призван расширить/уточнить, что высказали другие ответы. Решение обязательно будет меняться в зависимости от того, чего именно вы стремитесь достичь.

Если вы хотите повторять один и тот же результат os.walk несколько раз, вам нужно будет инициализировать список из os.walk итеративных элементов (т.е. walk = list(os.walk(path))).

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

  • Невозможно выполнить list() list(), если выход имеет достаточный размер (т.е. попытка list() вся файловая система может заморозить ваш компьютер).
  • Не желательно list() повторять, если вы хотите получить "свежие" данные перед каждым использованием.

В случае, если list() не подходит, вам нужно будет запустить генератор по требованию. Обратите внимание, что генераторы гасятся после каждого использования, поэтому это создает небольшую проблему. Чтобы "повторно запустить" ваш генератор несколько раз, вы можете использовать следующий шаблон:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os

class WalkMaker:
    def __init__(self, path):
        self.path = path
    def __iter__(self):
        for root, dirs, files in os.walk(self.path):
            for pathname in dirs + files:
                yield os.path.join(root, pathname)

walk = WalkMaker('/home')

for path in walk:
    pass

# do something...

for path in walk:
    pass

Вышеупомянутый шаблон дизайна позволит вам сохранить код DRY.

Ответ 6

Этот код "Слушатели Python Generator" позволяет вам иметь много слушателей на одном генераторе, например, os.walk, и даже иметь кого-то "включенного" позже.

def walkme():  os.walk( '/дом')

m1 = Muxer (walkme) m2 = Muxer (walkme)

тогда m1 и m2 могут работать даже в потоках и обрабатывать их на досуге.

Смотрите: https://gist.github.com/earonesty/cafa4626a2def6766acf5098331157b3

import queue
from threading import Lock
from collections import namedtuple

class Muxer():
    Entry = namedtuple('Entry', 'genref listeners, lock')

    already = {}
    top_lock = Lock()

    def __init__(self, func, restart=False):
        self.restart = restart
        self.func = func
        self.queue = queue.Queue()

        with self.top_lock:
            if func not in self.already:
                self.already[func] = self.Entry([func()], [], Lock())
            ent = self.already[func]

        self.genref = ent.genref
        self.lock = ent.lock
        self.listeners = ent.listeners

        self.listeners.append(self)

    def __iter__(self):
        return self

    def __next__(self):
        try:
            e = self.queue.get_nowait()
        except queue.Empty:
            with self.lock:
                try:
                    e = self.queue.get_nowait()
                except queue.Empty:
                    try:
                        e = next(self.genref[0])
                        for other in self.listeners:
                            if not other is self:
                                other.queue.put(e)
                    except StopIteration:
                        if self.restart:
                            self.genref[0] = self.func()
                        raise
        return e

    def __del__(self):
        with self.top_lock:
            try:
                self.listeners.remove(self)
            except ValueError:
                pass
            if not self.listeners and self.func in self.already:
                del self.already[self.func]