Geradores e Iteradores pitónicos

Secção dedicada à linguagem de programação favorita dos quarkianos: Python!

Geradores e Iteradores pitónicos

Mensagempor jap em Sexta Fev 03, 2012 12:48 am

OK, este é um tópico relativamente avançado de Python, mas os geradores e iteradores pitónicos são muito úteis, poderosos e permitem fazer coisas muito giras, IMHO.

Vejamos então um exemplo simples. Vocês pretendem criar uma função que vos gere todos os números ímpares, por exemplo. Ou todos os números pares. Ou todos os números de Fibonacci, you name it. 8)

Para começar digamos que queremos implementar um gerador de todos os números naturais: 1,2,3,4,.... Simples? Não tão simples assim.

Vejamos...

Que tal algo como

Código: Seleccionar Todos
def count():
     n = 1
     return n
     n = n +1


Más notícias! :P Não funciona. Porquê? Porque esta função devolve sempre o número 1. A linha n = n +1 nunca chega a ser executada, porque de cada vez que chamam a função ela inicia-se sempre na primeira linha...:lol:

Claro que podemos fazer

Código: Seleccionar Todos
n = 1
while True:
    print n
    n = n + 1

que imprime todos os números naturais. E podíamos, em vez de imprimir, guardar os números numa lista, por exemplo. mas não era bem isto que pretendíamos. O que queríamos era uma função que sempre que se invocaase nos desse mais um número natural... ou um número ímpar... Algo que pudessemos usar na forma

for n in impares(100):
print n

Era cool não? Para isso precisamos de uma função com memória, ou seja voltando ao nosso exemplo,

Código: Seleccionar Todos
def count():
     n = 1
     return n
     n = n +1


que não recomeçasse sempre na primeira linha, mas sim na linha a seguir ao return. Ou seja, uma função que "retornasse" um objecto mas que na próxima chamada da função arrancasse a seguir ao return, com memória do estado da função antes de devolver o objecto...

Pois as boas notícias são que isso existe no Python! Chama-se um gerador e para criar um gerador, basta substituir "return" numa função pela keyword "yield". Ficaria assim o nosso gerador contador de números naturais:


Código: Seleccionar Todos
def count():
     n = 1
     while True:
        yield n
        n += 1


Usa-se assim:

Código: Seleccionar Todos
for n in count():
      print n


Ora experimentem...

Claro que este é um contador infinito. Se quiserem um contador até um certo número, nmax, ficaria assim:

Código: Seleccionar Todos
def count(nmax):
     n = 1
     while n < nmax:
         yield n
         n = n +1


Experimentem fazer, por exemplo

Código: Seleccionar Todos
>>> list(count(10))
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>


Fixe, né?

Mas ainda mais engraçado.

Se fizermos

Código: Seleccionar Todos
c = count()


de cada vez que fizermos

Código: Seleccionar Todos
c.next()


obtemos um número novo na cadeia de números naturais.


Código: Seleccionar Todos
>>> def count():
    n = 1
    while True:
        yield n
        n += 1

>>> c = count()
>>> c.next()
1
>>> c.next()
2
>>> c.next()
3
>>>


E agora um gerador de números ímpares:

Código: Seleccionar Todos
>>> def impares(nmax):
    n = 1
    while n < nmax:
        yield n
        n += 2

>>>
>>> for n in impares(20):
        print n

   
1
3
5
7
9
11
13
15
17
19
>>>


E com isto podem fazer:

Código: Seleccionar Todos
>>> sum(impares(1000))
250000


Genial, não? :mock:

Mas isto é apenas uma pequena amostra do que os geradores podem fazer...
José António Paixão
Departamento de Física da FCTUC
Avatar do utilizador
jap
Site Admin
Site Admin
 
Mensagens: 6790
Registado: Quinta Nov 09, 2006 9:34 pm
Localização: Univ. de Coimbra

Re: Geradores e Iteradores pitónicos

Mensagempor Bruno Oliveira em Sexta Fev 03, 2012 10:34 am

Python rules!! :hands:

Muito bom este esquema dos geradores!!

Assim, é fácil resolver o segundo problema do ProjectEuler, já usando geradores:

Código: Seleccionar Todos
>>> c = list(even_fib(4000000))
>>> c
[34, 144, 610, 2584, 10946, 46368, 196418, 832040, 3524578]
>>> sum(c)+10
4613732


A minha relação recursiva começava no 34, pelo que tive de somar os dois primeiros números de Fibonacci pares (2 e 8 ) à mão :P
e^{ix}=cos x + i\,sin x
Avatar do utilizador
Bruno Oliveira
top-Quark!
top-Quark!
 
Mensagens: 1553
Registado: Quarta Nov 14, 2007 10:19 pm
Localização: Lisboa

Re: Geradores e Iteradores pitónicos

Mensagempor ruifm em Sexta Fev 03, 2012 9:50 pm

entao o yeld è mais poderoso que o return? eu nao percebi bem a funcao logica de yeld. este baslcamente ignora a 1a permissa (n=1)? exige que o novo n seja universal?
"Everything is determined, the beginning as well as the end, by forces over which we have no control." - Albert Einstein
Avatar do utilizador
ruifm
down-Quark!
down-Quark!
 
Mensagens: 205
Registado: Segunda Jan 16, 2012 12:04 am
Localização: Lisboa - IST

Re: Geradores e Iteradores pitónicos

Mensagempor Tharis em Sexta Fev 03, 2012 10:28 pm

O yield não é mais ou menos poderoso que o return, simplesmente têm funções diferentes.

Tu usas o return quando queres aplicar uma série de procedimentos a um conjunto de variáveis (ou não se ela não receber argumentos) e obter um único (não é bem assim, mas para isto serve) resultado. Por exemplo, se eu quiser o quadrado de um número posso fazer:

Código: Seleccionar Todos
>>> def quadrado(x):
...     return x*x
...
>>> quadrado(4)
16


O yield é usado para retornar uma série de valores obtidos aplicando uma série de procedimentos (à semelhança do que expliquei acima) e recebê-los um de cada vez.

Por exemplo:

Código: Seleccionar Todos
>>> def impares(x):
...     i = 1
...     while i < x:
...             yield i
...             i += 2
...
>>> for i in impares(10):
...     print i
...
1
3
5
7
9


Aqui foi necessário usar o yield porque se se utilizasse return, a função iria retornar o valor 1 sempre! Podes pensar no yield como um return mas que o computador guarda em que passo estava e quando voltares a chamar a "função" ela volta ao passo em que estava e em vez de começar de novo e retornar 1, continua o while e retorna 3.

É um bocado difícil de explicar melhor que isto a alguém que está a dar os seus primeiros passos, não só em Python, mas em programação em geral.
Avatar do utilizador
Tharis
up-Quark!
up-Quark!
 
Mensagens: 387
Registado: Quinta Out 23, 2008 4:26 pm

Re: Geradores e Iteradores pitónicos

Mensagempor ruifm em Sexta Fev 03, 2012 11:22 pm

entao o yeld e uma mistura entre return e assignment (=)?
"Everything is determined, the beginning as well as the end, by forces over which we have no control." - Albert Einstein
Avatar do utilizador
ruifm
down-Quark!
down-Quark!
 
Mensagens: 205
Registado: Segunda Jan 16, 2012 12:04 am
Localização: Lisboa - IST

Re: Geradores e Iteradores pitónicos

Mensagempor Bruno Oliveira em Sábado Fev 04, 2012 7:54 pm

Talvez isto te possa ajudar (retirado da documentação oficial do Python, tomei a liberdade de assinalar a bold as passagens que creio responderem à tua dúvida em concreto)

"The yield statement is only used when defining a generator function, and is only used in the body of the generator function. Using a yield statement in a function definition is sufficient to cause that definition to create a generator function instead of a normal function.

When a generator function is called, it returns an iterator known as a generator iterator, or more commonly, a generator. The body of the generator function is executed by calling the generator's next() method repeatedly until it raises an exception.

When a yield statement is executed, the state of the generator is frozen and the value of expression_list is returned to next()'s caller. By ``frozen'' we mean that all local state is retained, including the current bindings of local variables, the instruction pointer, and the internal evaluation stack: enough information is saved so that the next time next() is invoked, the function can proceed exactly as if the yield statement were just another external call.
(...) "

PS: Isto vai ao encontro do que o Francisco disse, mas aqui está dito com terminologia mais formal... :roll: O meu conselho é mesmo que faças umas funções (com e sem memória :P ) de teste que sejam simples e tentes perceber as diferenças que (caso vás acompanhando a documentação oficial e os posts aqui do fórum) deverão começar a ficar mais explicitas!!
e^{ix}=cos x + i\,sin x
Avatar do utilizador
Bruno Oliveira
top-Quark!
top-Quark!
 
Mensagens: 1553
Registado: Quarta Nov 14, 2007 10:19 pm
Localização: Lisboa

Re: Geradores e Iteradores pitónicos

Mensagempor jap em Segunda Fev 06, 2012 10:19 pm

Ainda sobre geradores, é muito fácil obter um gerador à custa de uma expressão que percorra um objecto iterável com uma instrução for, colocando a expressão entre parentesis curvos. Assim,

Código: Seleccionar Todos
g = (expr que percorre um iterável)


cria um objecto do tipo gerador. Nem é preciso criar uma função com o yield! :D

Um exemplo giro:

Código: Seleccionar Todos
>>> g = (fruit.upper() for fruit in ['apple','orange','banana','apricot','peach','acerola'] if fruit.startswith('a'))
>>> g.next()
'APPLE'
>>> g.next()
'APRICOT'
>>> g.next()
'ACEROLA'
>>> g.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration


ou ainda

Código: Seleccionar Todos
>>> g = (fruit.upper() for fruit in ['apple','orange','banana','apricot','peach','acerola'] if fruit.startswith('a'))
>>> list(g)
['APPLE', 'APRICOT', 'ACEROLA']

ou ainda

Código: Seleccionar Todos
>>> afruits = [fruit.upper() for fruit in ['apple','orange','banana','apricot','peach','acerola'] if fruit.startswith('a')]
>>> afruits
['APPLE', 'APRICOT', 'ACEROLA']
>>>


Notar que nest último caso se omitem os parêntesis curvos a delimitar a expressão. Chama-se "Listas por compreensão" (list comprehensions) a este tipo de "construtor" de listas:

Código: Seleccionar Todos
[expr que percorre um iterável]


Nalguns casos, o uso de geradores, expressões geradoras e/ou listas por compreensão, torna o código mais legível e mais robusto. Não deve ser usado em formas rebuscadas que tornem a leitura difícil. Se não for fácil numa primeira leitura perceber o que faz um pedaço de código escrito usando estes recursos, então foi provavelmente uma má ideia recorrer a estas ferramentas! :wink:

Explicit is better than implicit, como sabemos. :lol:

Have fun! :D
José António Paixão
Departamento de Física da FCTUC
Avatar do utilizador
jap
Site Admin
Site Admin
 
Mensagens: 6790
Registado: Quinta Nov 09, 2006 9:34 pm
Localização: Univ. de Coimbra


Voltar para Pitónica

Quem está ligado

Utilizadores a navegar neste fórum: Nenhum utilizador registado e 1 visitante

cron