Генератор синглтон-питона? Или засолить генератор питона? - PullRequest
4 голосов
/ 21 декабря 2009

Я использую следующий код с вложенными генераторами, чтобы перебрать текстовый документ и вернуть обучающие примеры, используя get_train_minibatch(). Я хотел бы сохранить (перебрать) генераторы, чтобы вернуться в то же место в текстовом документе. Однако вы не можете засолить генераторы.

  • Есть ли простой обходной путь, чтобы я мог сохранить свою позицию и вернуться туда, где я остановился? Возможно, я могу сделать get_train_example() синглтоном, поэтому у меня нет нескольких генераторов, лежащих вокруг. Затем я мог бы создать глобальную переменную в этом модуле, которая отслеживает, как далеко находится get_train_example().

  • У вас есть лучшее (более чистое) предложение, позволяющее мне сохранить этот генератор?

[править: Еще две идеи:

  • Могу ли я добавить переменную / метод члена к генератору, чтобы я мог вызвать generator.tell () и найти местоположение файла? Потому что тогда, когда я в следующий раз создаю генератор, я могу попросить его найти это место. Эта идея звучит проще всего.

  • Могу ли я создать класс и иметь расположение файла в качестве переменной-члена, а затем создать генератор внутри класса и обновлять переменную-член расположения файла при каждом выходе? Потому что тогда я могу знать, как далеко в файл это.

]

Вот код:

def get_train_example():
    for l in open(HYPERPARAMETERS["TRAIN_SENTENCES"]):
        prevwords = []
        for w in string.split(l):
            w = string.strip(w)
            id = None
            prevwords.append(wordmap.id(w))
            if len(prevwords) >= HYPERPARAMETERS["WINDOW_SIZE"]:
                yield prevwords[-HYPERPARAMETERS["WINDOW_SIZE"]:]

def get_train_minibatch():
    minibatch = []
    for e in get_train_example():
        minibatch.append(e)
        if len(minibatch) >= HYPERPARAMETERS["MINIBATCH SIZE"]:
            assert len(minibatch) == HYPERPARAMETERS["MINIBATCH SIZE"]
            yield minibatch
            minibatch = []

Ответы [ 6 ]

2 голосов
/ 21 декабря 2009

Вы можете создать стандартный объект итератора, он будет не так удобен, как генератор; вам нужно сохранить состояние итератора на instace (чтобы он был обработан) и определить функцию next () для возврата следующего объекта:

class TrainExampleIterator (object):
    def __init__(self):
        # set up internal state here
        pass
    def next(self):
        # return next item here
        pass

Протокол итератора очень прост: определение метода .next() для объекта - это все, что вам нужно для передачи его в циклы и т. Д.

В Python 3 протокол итератора использует метод __next__ (несколько более согласованный).

1 голос
/ 21 декабря 2009

Следующий код должен делать более или менее то, что вы хотите. Первый класс определяет что-то, что действует как файл, но может быть засолен. (Когда вы снимаете его, он снова открывает файл и ищет место, где он был, когда вы его мариновали) Второй класс - это итератор, который генерирует слово windows.

class PickleableFile(object):
    def __init__(self, filename, mode='rb'):
        self.filename = filename
        self.mode = mode
        self.file = open(filename, mode)
    def __getstate__(self):
        state = dict(filename=self.filename, mode=self.mode,
                     closed=self.file.closed)
        if not self.file.closed:
            state['filepos'] = self.file.tell()
        return state
    def __setstate__(self, state):
        self.filename = state['filename']
        self.mode = state['mode']
        self.file = open(self.filename, self.mode)
        if state['closed']: self.file.close()
        else: self.file.seek(state['filepos'])
    def __getattr__(self, attr):
        return getattr(self.file, attr)

class WordWindowReader:
    def __init__(self, filenames, window_size):
        self.filenames = filenames
        self.window_size = window_size
        self.filenum = 0
        self.stream = None
        self.filepos = 0
        self.prevwords = []
        self.current_line = []

    def __iter__(self):
        return self

    def next(self):
        # Read through files until we have a non-empty current line.
        while not self.current_line:
            if self.stream is None:
                if self.filenum >= len(self.filenames):
                    raise StopIteration
                else:
                    self.stream = PickleableFile(self.filenames[self.filenum])
                    self.stream.seek(self.filepos)
                    self.prevwords = []
            line = self.stream.readline()
            self.filepos = self.stream.tell()
            if line == '':
                # End of file.
                self.stream = None
                self.filenum += 1
                self.filepos = 0
            else:
                # Reverse line so we can pop off words.
                self.current_line = line.split()[::-1]

        # Get the first word of the current line, and add it to
        # prevwords.  Truncate prevwords when necessary.
        word = self.current_line.pop()
        self.prevwords.append(word)
        if len(self.prevwords) > self.window_size:
            self.prevwords = self.prevwords[-self.window_size:]

        # If we have enough words, then return a word window;
        # otherwise, go on to the next word.
        if len(self.prevwords) == self.window_size:
            return self.prevwords
        else:
            return self.next()
0 голосов
/ 22 декабря 2009
  1. Преобразовать генератор в класс, в котором код генератора является методом __iter__
  2. Добавить __getstate__ и __setstate__ методы к классу для обработки травления. Помните, что вы не можете выбирать файловые объекты. Так что __setstate__ придется заново открывать файлы по мере необходимости.

Я опишу этот метод более подробно, с примером кода, здесь .

0 голосов
/ 21 декабря 2009

Вы также можете рассмотреть возможность использования корпусных считывателей NLTK:

1012 * Эдвард *

0 голосов
/ 21 декабря 2009

Это может быть не вариант для вас, но Stackless Python (http://stackless.com) делает позволяет вам при определенных условиях выбирать такие вещи, как функции и генераторы.

In foo.py:

def foo():
    with open('foo.txt') as fi:
        buffer = fi.read()
    del fi
    for line in buffer.split('\n'):
        yield line

В файле foo.txt:

line1
line2
line3
line4
line5

В переводчике:

Python 2.6 Stackless 3.1b3 060516 (python-2.6:66737:66749M, Oct  2 2008, 18:31:31) 
IPython 0.9.1 -- An enhanced Interactive Python.

In [1]: import foo

In [2]: g = foo.foo()

In [3]: g.next()
Out[3]: 'line1'

In [4]: import pickle

In [5]: p = pickle.dumps(g)

In [6]: g2 = pickle.loads(p)

In [7]: g2.next()
Out[7]: 'line2'

Некоторые замечания: вы должны буферизировать содержимое файла и удалять объект файла. Это означает, что содержимое файла будет продублировано в рассоле.

0 голосов
/ 21 декабря 2009

Вы можете попробовать создать вызываемый объект:

class TrainExampleGenerator:

    def __call__(self):
        for l in open(HYPERPARAMETERS["TRAIN_SENTENCES"]):
            prevwords = []
            for w in string.split(l):
                w = string.strip(w)
                id = None
                prevwords.append(wordmap.id(w))
                if len(prevwords) >= HYPERPARAMETERS["WINDOW_SIZE"]:
                    yield prevwords[-HYPERPARAMETERS["WINDOW_SIZE"]:]

get_train_example = TrainExampleGenerator()

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

UPDATE:
К сожалению, мне не удалось реализовать мою идею. Приведенный пример не является полным решением. Понимаете, у TrainExampleGenerator нет государства. Вы должны разработать это состояние и сделать его доступным для маринования. И метод __call__ должен использовать и изменять это состояние так, чтобы он возвращал генератор, который стартовал с позиции, определенной состоянием объекта. Очевидно, сам генератор не будет засолен. Но TrainExampleGenerator можно будет засолить, и вы сможете воссоздать генератор с ним , как если бы сам генератор был засолен.

...