Gerador Singleton Python? Ou, apodrece um gerador de python?
-
20-09-2019 - |
Pergunta
Estou usando o seguinte código, com geradores aninhados, para iterar sobre um documento de texto e retornar exemplos de treinamento usando get_train_minibatch()
. Gostaria de persistir (Pickle) os geradores, para que eu possa voltar ao mesmo lugar no documento de texto. No entanto, você não pode devolver geradores.
Existe uma solução alternativa simples, para que eu possa salvar minha posição e começar de volta onde parei? Talvez eu possa fazer
get_train_example()
Um singleton, então eu não tenho vários geradores por aí. Então, eu poderia fazer uma variável global neste módulo que acompanha o quão longeget_train_example()
é.Você tem uma sugestão melhor (mais limpa), para me permitir persistir este gerador?
Editar: mais duas idéias:
Posso adicionar uma variável/método de membro ao gerador, para que eu possa chamar o gerador.tell () e encontrar o local do arquivo? Porque então, na próxima vez que eu criar o gerador, posso pedir para procurar esse local. Essa ideia parece mais simples de tudo.
Posso criar uma classe e fazer com que o local do arquivo seja uma variável de membro e, em seguida, o gerador criado dentro da classe e atualize a variável de membro do localização do arquivo cada vez que ele produz? Porque então posso saber a que distância o arquivo.
]
Aqui está o código:
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 = []
Solução
O código a seguir deve fazer mais ou menos o que você deseja. A primeira classe define algo que age como um arquivo, mas pode ser em conserva. (Quando você desenrola-o, ele reabre o arquivo e procura o local onde estava quando você o decapou). A segunda classe é um iterador que gera Word 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()
Outras dicas
Você pode criar um objeto de iterador padrão, ele não será tão conveniente quanto o gerador; Você precisa armazenar o estado do iterador no Instituto (para que seja em conserva) e definir uma função seguinte () para retornar o próximo objeto:
class TrainExampleIterator (object):
def __init__(self):
# set up internal state here
pass
def next(self):
# return next item here
pass
O protocolo iterador é simples assim, definindo o .next()
O método em um objeto é tudo o que você precisa transmitir para loops etc.
No Python 3, o Protocolo Iterador usa o __next__
Método (em vez disso (um pouco mais consistente).
Isso pode não ser uma opção para você, mas python sem pilha (http://stackless.com) faz Permita que você apodreça coisas como funções e geradores, sob certas condições. Isso vai funcionar:
Em foo.py:
def foo():
with open('foo.txt') as fi:
buffer = fi.read()
del fi
for line in buffer.split('\n'):
yield line
Em foo.txt:
line1
line2
line3
line4
line5
No intérprete:
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'
Algumas coisas a serem observadas: você devo buffer o conteúdo do arquivo e exclua o objeto de arquivo. Isso significa que o conteúdo do arquivo será duplicado no picles.
Você também pode considerar o uso de leitores de corpus da NLTK:
- http://nltk.googlecode.com/svn/trunk/doc/api/nltk.corpus.reader-module.html
- http://nltk.googlecode.com/svn/trunk/doc/howto/corpus.html
-Edward
- Converter o gerador em uma classe em que o código do gerador é o
__iter__
método - Adicionar
__getstate__
e__setstate__
Métodos para a classe, para lidar com o decapagem. Lembre -se de que você não pode aprisionar objetos de arquivo. Então__setstate__
terá que reabrir os arquivos, conforme necessário.
Eu descrevo este método em mais profundidade, com código de amostra, aqui.
Você pode tentar criar um objeto chamável:
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()
Agora você pode transformar todo o estado que precisa ser salvo em campos de objeto e expô -los a aprisionar. Esta é uma ideia básica e espero que isso ajude, mas ainda não tentei isso.
ATUALIZAR:
Infelizmente, não consegui entregar minha ideia. Exemplo fornecido não é uma solução completa. Você vê, TrainExampleGenerator
não tem estado. Você deve projetar esse estado e disponibilizá -lo para decapagem. E __call__
O método deve usar e modificar esse estado para que o gerador devolva a partir da posição determinada pelo estado do objeto. Obviamente, o próprio gerador não poderá ser apagado. Mas TrainExampleGenerator
Será possível para apagar e você poderá recriar o gerador com ele Até parece O próprio gerador foi em conserva.