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.

Foi útil?

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 yielding 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.

Basicamente evitando funções de call-back quando iteração sobre entrada estado manutenção.

Consulte aqui e aqui para uma visão geral do que pode ser feito usando geradores.

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.

Eu uso geradores quando o nosso servidor web está agindo como um proxy:

  1. O cliente solicita uma url com proxy do servidor
  2. O servidor começa a carregar o alvo url
  3. 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.)

David Beazley em geradores a PyCon 2014

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)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top