O que você pode usar as funções de gerador de Python para?
Pergunta
Estou começando a aprender Python e eu vim através de funções de gerador, aqueles que têm uma declaração de rendimento neles. Eu quero saber quais os tipos de problemas que essas funções são realmente bons em resolver.
Solução
Geradores dará avaliação preguiçosa. Você usá-los por iteração sobre eles, quer explicitamente 'para' ou implicitamente, passando-a para qualquer função ou construção que itera. Você pode pensar de geradores como retornar vários itens, como se retornar uma lista, mas em vez de devolvê-los todos de uma vez que devolvê-los um por um, e a função de gerador está em pausa até o próximo item é solicitado.
Geradores são bons para o cálculo de grandes conjuntos de resultados (em cálculos particulares envolvendo próprios loops), onde você não sabe se você vai precisar de todos os resultados, ou onde você não quer alocar a memória para todos os resultados em o mesmo tempo. Ou para situações em que os usos do gerador outro gerador, ou consome algum outro recurso, e é mais conveniente se isso acontecesse o mais tarde possível.
Outro uso para geradores (que é realmente o mesmo) é substituir retornos de chamada com iteração. Em algumas situações você quer uma função para fazer um monte de trabalho e, ocasionalmente, relatório de volta para o chamador. Tradicionalmente, você usaria uma função de chamada de retorno para isso. Você passar esta chamada de retorno para a função-trabalho e seria periodicamente chamar este callback. A abordagem gerador é que a função-trabalho (agora um gerador) nada sobre o retorno sabe, e apenas produz sempre que quer denunciar alguma coisa. O autor da chamada, em vez de escrever um retorno separado e passar isso para a função-trabalho, faz todo o trabalho de relatórios em um pouco 'para' loop em torno do gerador.
Por exemplo, digamos que você escreveu um programa de 'search sistema de arquivos'. Você poderia realizar a pesquisa na íntegra, recolher os resultados e, em seguida, exibi-los um de cada vez. Todos os resultados teriam de ser recolhidas antes que você mostrou o primeiro, e todos os resultados seriam na memória ao mesmo tempo. Ou você pode exibir os resultados enquanto você encontrá-los, o que seria mais memória amigável eficiente e muito para o usuário. Este último poderia ser feito passando a função de impressão do resultado para a função de sistema de arquivos-search, ou poderia ser feito por apenas fazendo a função de pesquisa um gerador e interagindo sobre o resultado.
Se você quiser ver um exemplo das duas últimas abordagens, consulte os.path.walk () (a função de idade filesystem-curta com retorno de chamada) e os.walk () (o novo gerador filesystem-walking). É claro, se você realmente queria para coletar todos os resultados em uma lista, a abordagem gerador é trivial para converter para a abordagem de grande lista:
big_list = list(the_generator)
Outras dicas
Uma das razões para gerador de uso é para fazer a solução mais clara para algum tipo de soluções.
A outra é tratar resultados um de cada vez, evitando a construção de enormes listas de resultados que você iria processar separados de qualquer maneira.
Se você tem um Fibonacci-up-to-n função como esta:
# function version
def fibon(n):
a = b = 1
result = []
for i in xrange(n):
result.append(a)
a, b = b, a + b
return result
Você pode mais facilmente escrever a função como esta:
# generator version
def fibon(n):
a = b = 1
for i in xrange(n):
yield a
a, b = b, a + b
A função é mais clara. E se você usar a função como esta:
for x in fibon(1000000):
print x,
Neste exemplo, se estiver usando a versão do gerador, a lista 1000000 artigo inteiro não será criado em tudo, apenas um valor de cada vez. Isso não seria o caso quando se utiliza a versão lista, onde uma lista seria criado pela primeira vez.
Veja a seção "Motivação" em PEP 255 .
Um uso não-óbvia de geradores está criando funções interruptíveis, que permite fazer coisas como atualização UI ou executar vários trabalhos "em simultâneo" (intercaladas, na verdade), enquanto não usando threads.
Eu acho esta explicação que limpa a minha dúvida. Porque há uma possibilidade de que a pessoa que não sabe Generators
também não sei sobre yield
Voltar
A instrução de retorno é o lugar onde todas as variáveis ??locais são destruídas eo valor resultante é dado de volta (retornado) para o chamador. Caso a mesma função ser chamado algum tempo depois, a função irá obter um novo conjunto de variáveis.
Rendimento
Mas, e se as variáveis ??locais não são jogados fora quando sair de uma função? Isto implica que podemos resume the function
onde paramos. Este é o lugar onde o conceito de generators
são introduzidos ea declaração yield
recomeça onde o function
parou.
def generate_integers(N):
for i in xrange(N):
yield i
In [1]: gen = generate_integers(3)
In [2]: gen
<generator object at 0x8117f90>
In [3]: gen.next()
0
In [4]: gen.next()
1
In [5]: gen.next()
Assim que é a diferença entre return
e yield
declarações em Python.
declaração de rendimento é o que faz uma função de uma função de gerador.
geradores Portanto, são uma forma simples e poderosa ferramenta para criar iteradores. Eles são escritos como funções regulares, mas eles usam a declaração yield
sempre que eles querem voltar dados. Cada vez next () é chamado, o gerador de retoma de onde parou (ele se lembra de todos os valores de dados e que declaração foi última executada).
real Exemplo Mundial
Digamos que você tem 100 milhões de domínios na sua tabela MySQL, e você gostaria de atualizar Alexa Rank para cada domínio.
A primeira coisa que você precisa é selecionar seus nomes de domínio a partir do banco de dados.
Vamos dizer que seu nome da tabela é domains
e nome da coluna é domain
.
Se você usar SELECT domain FROM domains
que vai voltar 100 milhões de linhas que vai consumir muita memória. Então, o servidor pode falhar.
Então você decidiu executar o programa em lotes. Digamos que o nosso tamanho do lote é de 1000.
No nosso primeiro lote que irá consultar os primeiros 1000 linhas, verifique Alexa Rank para cada domínio e atualizar a linha do banco de dados.
Em nosso segundo lote vamos trabalhar nos próximos 1000 linhas. Em nosso terceiro lote será 2001-3000 e assim por diante.
Agora precisamos de uma função de gerador que gera nossos lotes.
Aqui é a nossa função de gerador:
def ResultGenerator(cursor, batchsize=1000):
while True:
results = cursor.fetchmany(batchsize)
if not results:
break
for result in results:
yield result
Como você pode ver, a nossa função continua yield
ing os resultados. Se você usou a palavra-chave return
vez de yield
, então toda a função seria terminou uma vez que chegou retorno.
return - returns only once
yield - returns multiple times
Se a função usa a palavra-chave yield
então é um gerador.
Agora você pode interagir como esta:
db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")
for result in ResultGenerator(cursor):
doSomethingWith(result)
db.close()
Buffer. Quando o produto é eficiente para buscar dados em pedaços grandes, mas processá-lo em pequenos pedaços, em seguida, uma ajuda gerador de força:
def bufferedFetch():
while True:
buffer = getBigChunkOfData()
# insert some code to break on 'end of data'
for i in buffer:
yield i
A descrição acima permite buffer facilmente separado do processamento. A função consumidor pode agora só obter os valores, um por um, sem se preocupar com buffering.
Eu descobri que os geradores são muito úteis na limpeza de seu código e dando-lhe uma forma muito original para encapsular e código modularize. Em uma situação onde você precisa de algo para cuspir constantemente valores com base em seu próprio processo interno e quando que algo precisa ser chamado de qualquer lugar em seu código (e não apenas dentro de um loop ou um bloco, por exemplo), os geradores são o recurso para uso.
Um exemplo abstrato seria um gerador de números de Fibonacci que não vive dentro de um loop e quando ele é chamado de qualquer lugar sempre retornará o próximo número na seqüência:
def fib():
first = 0
second = 1
yield first
yield second
while 1:
next = first + second
yield next
first = second
second = next
fibgen1 = fib()
fibgen2 = fib()
Agora você tem dois Fibonacci objetos gerador de números que você pode chamar de qualquer lugar em seu código e eles sempre voltarão cada vez maiores números de Fibonacci em sequência como se segue:
>>> fibgen1.next(); fibgen1.next(); fibgen1.next(); fibgen1.next()
0
1
1
2
>>> fibgen2.next(); fibgen2.next()
0
1
>>> fibgen1.next(); fibgen1.next()
3
5
A bela coisa sobre geradores é que eles encapsulam estado sem ter que ir através dos aros de criação de objetos. Uma maneira de pensar sobre eles é como "funções" que se lembrar seu estado interno.
Eu tenho o exemplo Fibonacci de Geradores de Python - O que são eles? e com um pouco de imaginação, você pode vir até com um monte de outras situações onde os geradores fazem para uma ótima alternativa para laços for
e outras construções de iteração tradicionais.
A explicação simples:
Considere uma declaração for
for item in iterable:
do_stuff()
Uma grande parte do tempo, todos os itens em iterable
não precisa estar lá desde o início, mas pode ser gerada em tempo real como eles são necessários. Isso pode ser muito mais eficiente em ambos
- espaço (você nunca precisa para armazenar todos os itens simultaneamente) e
- vez (a iteração pode terminar antes de todos os itens são necessários).
Outras vezes, você nem sequer sabem todos os itens antes do tempo. Por exemplo:
for command in user_input():
do_stuff_with(command)
Você não tem nenhuma maneira de saber todos os comandos do usuário de antemão, mas você pode usar um laço agradável como este se você tiver uma entrega gerador que você comanda:
def user_input():
while True:
wait_for_command()
cmd = get_command()
yield cmd
Com geradores você também pode ter iteração sobre sequências infinitas, que é, naturalmente, não é possível quando iteração sobre recipientes.
Os meus usos favoritos são "filtro" e "reduzir" as operações.
Vamos dizer que estamos lendo um arquivo, e só querem as linhas que começam com "##".
def filter2sharps( aSequence ):
for l in aSequence:
if l.startswith("##"):
yield l
Podemos então usar a função de gerador em um loop adequada
source= file( ... )
for line in filter2sharps( source.readlines() ):
print line
source.close()
O reduzir exemplo é similar. Vamos dizer que temos um arquivo onde precisamos localizar blocos de linhas <Location>...</Location>
. [Tags não HTML, mas as linhas que acontecem olhar tag-like.]
def reduceLocation( aSequence ):
keep= False
block= None
for line in aSequence:
if line.startswith("</Location"):
block.append( line )
yield block
block= None
keep= False
elif line.startsWith("<Location"):
block= [ line ]
keep= True
elif keep:
block.append( line )
else:
pass
if block is not None:
yield block # A partial block, icky
Mais uma vez, podemos usar este gerador em uma adequado para loop.
source = file( ... )
for b in reduceLocation( source.readlines() ):
print b
source.close()
A idéia é que uma função de gerador permite filtro ou reduzir uma seqüência, produzindo um valor de um outro seqüência de cada vez.
Um exemplo prático, onde você poderia fazer uso de um gerador é se você tiver algum tipo de forma e você quer iterar sobre seus cantos, bordas ou o que quer. Para o meu próprio projeto (código fonte aqui ) eu tive um retângulo:
class Rect():
def __init__(self, x, y, width, height):
self.l_top = (x, y)
self.r_top = (x+width, y)
self.r_bot = (x+width, y+height)
self.l_bot = (x, y+height)
def __iter__(self):
yield self.l_top
yield self.r_top
yield self.r_bot
yield self.l_bot
Agora posso criar um retângulo e laço sobre seus cantos:
myrect=Rect(50, 50, 100, 100)
for corner in myrect:
print(corner)
Em vez de __iter__
você poderia ter um iter_corners
método e chamada que com for corner in myrect.iter_corners()
. É apenas mais elegante para usar __iter__
desde então podemos usar o nome da instância de classe diretamente na expressão for
.
Algumas boas respostas aqui, no entanto, eu também recomendo uma leitura completa do Python Programação funcional tutorial que ajuda a explicar alguns dos mais potentes casos de uso de geradores.
- Particularmente interessante é que agora é possível atualizar a variável rendimento de fora a função de gerador , portanto, tornando possível a criação de dinâmica e entrelaçadas coroutines com relativamente pouco esforço.
- Veja também PEP 342: coroutines via Geradores aprimorados para mais informações .
Eu uso geradores quando o nosso servidor web está agindo como um proxy:
- O cliente solicita uma url com proxy do servidor
- O servidor começa a carregar o alvo url
- Os yields servidor para retornar os resultados para o cliente assim que ele recebe-los
Uma vez que o método de envio de um gerador não foi mencionado, aqui está um exemplo:
def test():
for i in xrange(5):
val = yield
print(val)
t = test()
# Proceed to 'yield' statement
next(t)
# Send value to yield
t.send(1)
t.send('2')
t.send([3])
Isso mostra a possibilidade de enviar um valor a um gerador funcionando. Um curso mais avançado em geradores no vídeo abaixo (incluindo yield
de explination, geradores para o processamento paralelo, escapando o limite recursão, etc.)
pilhas de coisas. Toda vez que você deseja gerar uma seqüência de itens, mas não quero ter que 'materializar'-los todos em uma lista de uma só vez. Por exemplo, você poderia ter um gerador simples que retorna números primos:
def primes():
primes_found = set()
primes_found.add(2)
yield 2
for i in itertools.count(1):
candidate = i * 2 + 1
if not all(candidate % prime for prime in primes_found):
primes_found.add(candidate)
yield candidate
Você poderia, então, usar isso para gerar os produtos de primos seguintes:
def prime_products():
primeiter = primes()
prev = primeiter.next()
for prime in primeiter:
yield prime * prev
prev = prime
Estes são exemplos bastante trivial, mas você pode ver como ele pode ser útil para o processamento de grandes conjuntos de dados (potencialmente infinito!) Sem gerar-los com antecedência, que é apenas um dos usos mais óbvios.
Também é bom para imprimir os números primos até N:
def genprime(n=10):
for num in range(3, n+1):
for factor in range(2, num):
if num%factor == 0:
break
else:
yield(num)
for prime_num in genprime(100):
print(prime_num)