Share
Embed
Transcript
65 slides
-
a p r e g u i ç a c
o m o q u a l i d a d e O PODER DOS GERADORES Como funcionam geradores em Python, e como usá-los para processar grandes volumes de dados com eficiência -
FLUENT PYTHON, MEU PRIMEIRO LIVRO Fluent Python (O’Reilly, 2015) Python
Fluente (Novatec, 2015) Python к вершинам мастерства* (DMK, 2015) 流暢的 Python† (Gotop, 2016) also in Polish, Korean… 3 * Python. To the heights of excellence † Smooth Python -
ZIP, MAP E FILTER EM PYTHON 2 5 >>> L
= [0, 1, 2] >>> zip('ABC', L) [('A', 0), ('B', 1), ('C', 2)] >>> map(lambda x: x*10, L) [0, 10, 20] >>> filter(None, L) [1, 2] Python 2 zip: percorre N iteráveis em paralelo, devolve sequência de N-uplas map: aplica função a cada item, devolve sequência de resultados filter: aplica predicado a cada item, devolve sequência de itens “verdadeiros” -
ZIP, MAP E FILTER: PYTHON 2 × PYTHON 3 6
>>> L = [0, 1, 2] >>> zip('ABC', L) [('A', 0), ('B', 1), ('C', 2)] >>> map(lambda x: x*10, L) [0, 10, 20] >>> filter(None, L) [1, 2] Python 2 >>> L = [0, 1, 2] >>> zip('ABC', L) <zip object at 0x102218408> >>> map(lambda x: x*10, L) <map object at 0x102215a90> >>> filter(None, L) <filter object at 0x102215b00> Python 3 Cadê as listas? O que são estes resultados? -
ZIP, MAP E FILTER DEVOLVEM GERADORES Um gerador é um
objeto iterável: 7 >>> L = [0, 1, 2] >>> for par in zip('ABC', L): ... print(par) ... ('A', 0) ('B', 1) ('C', 2) Para criar a lista, basta passar o gerador para o construtor: >>> list(zip('ABC', L)) [('A', 0), ('B', 1), ('C', 2)] >>> dict(zip('ABC', L)) {'C': 2, 'B': 1, 'A': 0} Vários construtores de coleções aceitam iteráveis: -
ZIP, MAP E FILTER DEVOLVEM GERADORES Um gerador implementa a
interface Iterator: 8 >>> L = [0, 1, 2] >>> z = zip('ABC', L) >>> next(z) ('A', 0) >>> next(z) ('B', 1) >>> next(z) ('C', 2) Uma vez esgotado, um gerador levanta StopIteration e não serve mais para nada, pois não pode ser reiniciado: >>> next(z) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration -
ITERAÇÃO: LINGUAGEM C 10 #include <stdio.h> int main(int argc, char
*argv[]) { int i; for(i = 0; i < argc; i++) printf("%s\n", argv[i]); return 0; } $ ./args alfa bravo charlie ./args alfa bravo charlie -
ITERAÇÃO: LINGUAGENS C E PYTHON 11 #include <stdio.h> int main(int
argc, char *argv[]) { int i; for(i = 0; i < argc; i++) printf("%s\n", argv[i]); return 0; } import sys for arg in sys.argv: print arg -
ITERAÇÃO: ANTES DE JAVA 5 12 class Argumentos { public
static void main(String[] args) { for (int i=0; i < args.length; i++) System.out.println(args[i]); } } $ java Argumentos alfa bravo charlie alfa bravo charlie -
ITERAÇÃO: A PARTIR DE JAVA 5 13 $ java Argumentos2
alfa bravo charlie alfa bravo charlie class Argumentos2 { public static void main(String[] args) { for (String arg : args) System.out.println(arg); } } Foreach: oficialmente, enhanced for (“for melhorado”) -
ITERAÇÃO: JAVA E PYTHON 14 class Argumentos2 { public static
void main(String[] args) { for (String arg : args) System.out.println(arg); } } Foreach: oficialmente, enhanced for (“for melhorado”) ano: 2004 import sys for arg in sys.argv: print arg ano: 1991 -
FOR/FOREACH FUNCIONA PORQUE • Python (e Java) possuem objetos iteráveis
• iterável = “que pode ser iterado” • A partir de um objeto iterável, é possível se obter um iterador • O iterador suporta a função next que fornece valores sucessivos para a variável do laço for 15 import sys for arg in sys.argv: print arg -
ITERATOR É UM PADRÃO DE PROJETO Design Patterns Gamma, Helm,
Johnson & Vlissides Addison-Wesley, ISBN 0-201-63361-2 16 -
18 Head First Design Patterns Poster O'Reilly ISBN 0-596-10214-3 O
padrão Iterator permite acessar os itens de uma coleção sequencialmente, isolando o cliente da implementação da coleção. -
ITERÁVEL VERSUS ITERADOR 19 • Iterável: implementa a interface Iterable
(método __iter__) • Método __iter__ devolve um iterador • Iterador: implementa a interface Iterator (método __next__) • Método __next__ devolve o próximo item da série • levanta StopIteration para sinalizar o final da série. • Em Python, os iteradores também são iteráveis! -
UM TREM ITERÁVEL Uma instância de Trem pode ser iterada,
vagão por vagão. 21 >>> t = Trem(3) >>> for vagao in t: ... print(vagao) vagao #1 vagao #2 vagao #3 >>> -
CÓDIGO DE UM ITERATOR CLÁSSICO O padrão conforme a receita
do livro. 22 class Trem(object): def __init__(self, vagoes): self.vagoes = vagoes def __iter__(self): return IteradorTrem(self.vagoes) class IteradorTrem(object): def __init__(self, vagoes): self.atual = 0 self.ultimo_vagao = vagoes - 1 def __next__(self): if self.atual <= self.ultimo_vagao: self.atual += 1 return 'vagao #%s' % (self.atual) else: raise StopIteration() >>> t = Trem(4) >>> for vagao in t: ... print(vagao) vagao #1 vagao #2 vagao #3 vagao #4 -
UMA FUNÇÃO GERADORA MUITO SIMPLES Qualquer função que tenha a
palavra reservada yield em seu corpo é uma função geradora. A palavra reservada gen foi sugerida no lugar de def, mas Guido não topou… 24 >>> def gen_123(): ... yield 1 ... yield 2 ... yield 3 ... >>> for i in gen_123(): print(i) 1 2 3 >>> g = gen_123() >>> g <generator object gen_123 at ...> >>> next(g) 1 >>> next(g) 2 >>> next(g) 3 >>> next(g) Traceback (most recent call last): ... StopIteration Quando invocada, a função geradora devolve um objeto gerador -
COMO FUNCIONA 25 >>> def gen_ab(): ... print('iniciando...') ... yield
'A' ... print('agora vem B:') ... yield 'B' ... print('FIM.') ... >>> for s in gen_ab(): print(s) iniciando... A agora vem B: B FIM. >>> g = gen_ab() >>> g <generator object gen_ab at 0x...> >>> next(g) iniciando... 'A' >>> next(g) agora vem B: 'B' >>> next(g) FIM. Traceback (most recent call last): ... StopIteration • Invocar a função geradora produz um objeto gerador • O corpo da função só começa a ser executado quando se invoca next(g) • A cada next(g), o corpo da função é executado só até o próximo yield. -
O FAMOSO GERADOR DE FIBONACCI Um gerador de série infinita
26 def fibonacci(): a, b = 0, 1 while True: yield a a, b = b, a + b >>> fib = fibonacci() >>> for i in range(10): ... print(next(fib)) ... 0 1 1 2 3 5 8 13 21 34 -
GERADOR DE FIBONACCI LIMITADO A "N" ITENS Mais fácil de
usar 27 def fibonacci(n): a, b = 0, 1 for _ in range(n): yield a a, b = b, a + b >>> for x in fibonacci(10): ... print(x) ... 0 1 1 2 3 5 8 13 21 34 >>> list(fibonacci(10)) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] -
GERADOR DE PROGRESSÃO ARITMÉTICA 28 def progressão_aritmética(incremento, *, início=0, término=None):
infinita = término is None índice = 0 resultado = início + incremento * índice while infinita or resultado < término: yield resultado índice += 1 resultado = início + incremento * índice >>> pa = progressão_aritmética(.1) >>> next(pa), next(pa), next(pa), next(pa), next(pa) (0.0, 0.1, 0.2, 0.30000000000000004, 0.4) >>> from decimal import Decimal >>> pa = progressão_aritmética(Decimal('.1')) >>> next(pa), next(pa), next(pa), next(pa), next(pa) (Decimal('0.0'), Decimal('0.1'), Decimal('0.2'), Decimal('0.3'), Decimal('0.4')) >>> pa = progressão_aritmética(.5, início=1, término=5) >>> list(pa) [1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5] >>> pa = progressão_aritmética(1/3, término=1) >>> list(pa) [0.0, 0.3333333333333333, 0.6666666666666666] -
CLASSE TREM COM FUNÇÃO GERADORA Como um recurso da linguagem
torna a receita de um design pattern obsoleta: 29 class Trem(object): def __init__(self, vagoes): self.vagoes = vagoes def __iter__(self): for i in range(self.vagoes): yield 'vagao #%s' % (i+1) Quando invocada, a função geradora devolve um objeto gerador >>> t = Trem(3) >>> it = iter(t) >>> it <generator object __iter__ at 0x…> >>> next(it), next(it), next(it) ('vagão #1', 'vagão #2', 'vagão #3') -
CONTRASTE: ITERATOR CLÁSSICO × FUNÇÃO GERADORA Só quem ainda não
aprendeu sobre geradores vai querer implementar a receita clássica… 30 class Trem(object): def __init__(self, vagoes): self.vagoes = vagoes def __iter__(self): for i in range(self.vagoes): yield 'vagao #%s' % (i+1) class Trem(object): def __init__(self, vagoes): self.vagoes = vagoes def __iter__(self): return IteradorTrem(self.vagoes) class IteradorTrem(object): def __init__(self, vagoes): self.atual = 0 self.ultimo_vagao = vagoes - 1 def __next__(self): if self.atual <= self.ultimo_vagao: self.atual += 1 return 'vagao #%s' % (self.atual) else: raise StopIteration() O gerador administra o estado da iteração para você -
LIST COMPREHENSION Expressão que constrói lista a partir de qualquer
iterável finito (desde que haja memória suficiente ;-) 32 >>> s = 'abracadabra' >>> l = [ord(c) for c in s] >>> [ord(c) for c in s] [97, 98, 114, 97, 99, 97, 100, 97, 98, 114, 97] ≈ notação matemática de conjuntos List comprehension Compreensão de lista ou abrangência de lista xemplo: usar todos os elementos: – L2 = [n*10 for n in L] entrada: qualquer iterável saída: sempre uma lista -
GENERATOR EXPRESSION Expressão que constrói gerador a partir de qualquer
iterável finito ou não. O gerador é lazy: entrada é consumida um item de cada vez. 33 >>> s = 'abracadabra' >>> g = (ord(c) for c in s) >>> g <generator object <genexpr> at 0x102610620> >>> list(g) [97, 98, 114, 97, 99, 97, 100, 97, 98, 114, 97] List comprehension Compreensão de lista ou abrangência de lista xemplo: usar todos os elementos: – L2 = [n*10 for n in L] entrada: qualquer iterável saída: sempre um gerador -
TREM COM EXPRESSÃO GERADORA Com expressão geradora: Com função geradora:
34 class Trem(object): def __init__(self, num_vagoes): self.num_vagoes = num_vagoes def __iter__(self): return ('vagao #%s' % (i+1) for i in range(self.num_vagoes)) class Trem(object): def __init__(self, vagoes): self.vagoes = vagoes def __iter__(self): for i in range(self.vagoes): yield 'vagao #%s' % (i+1) -
OPERAÇÕES COM ITERÁVEIS Desempacotamento de tupla em atribuições em chamadas
de funções 36 >>> def soma(a, b): ... return a + b ... >>> soma(1, 2) 3 >>> t = (3, 4) >>> soma(t) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: soma() takes exactly 2 arguments (1 given) >>> soma(*t) 7 >>> a, b, c = 'XYZ' >>> a 'X' >>> b 'Y' >>> c 'Z' >>> g = (n for n in [1, 2, 3]) >>> a, b, c = g >>> a 1 >>> b 2 >>> c 3 -
OBJETOS ITERÁVEIS Alguns exemplos: str bytes list set tuple io.TextIOWrapper
(arquivo texto) models.query.QuerySet (Django) 37 >>> octetos = b'Python' >>> for octeto in octetos: ... print(octeto) ... 80 121 116 104 111 110 >>> list(octetos) [80, 121, 116, 104, 111, 110] >>> with open('1.txt') as arq: ... for lin in arq: ... print(lin.rstrip()) ... alfa beta gama delta -
>>> from django.db import connection >>> q = connection.queries >>>
q [] >>> from municipios.models import * >>> res = Municipio.objects.all()[:5] >>> q [] >>> for m in res: print m.uf, m.nome ... GO Abadia de Goiás MG Abadia dos Dourados GO Abadiânia MG Abaeté PA Abaetetuba >>> q [{'time': '0.000', 'sql': u'SELECT "municipios_municipio"."id", "municipios_municipio"."uf", "municipios_municipio"."nome", "municipios_municipio"."nome_ascii", "municipios_municipio"."meso_regiao_id", "municipios_municipio"."capital", "municipios_municipio"."latitude", "municipios_municipio"."longitude", "municipios_municipio"."geohash" FROM "municipios_municipio" ORDER BY "municipios_municipio"."nome_ascii" ASC LIMIT 5'}] -
>>> from django.db import connection >>> q = connection.queries >>>
q [] >>> from municipios.models import * >>> res = Municipio.objects.all()[:5] >>> q [] >>> for m in res: print m.uf, m.nome ... GO Abadia de Goiás MG Abadia dos Dourados GO Abadiânia MG Abaeté PA Abaetetuba >>> q [{'time': '0.000', 'sql': u'SELECT "municipios_municipio"."id", "municipios_municipio"."uf", "municipios_municipio"."nome", "municipios_municipio"."nome_ascii", "municipios_municipio"."meso_regiao_id", "municipios_municipio"."capital", "municipios_municipio"."latitude", "municipios_municipio"."longitude", "municipios_municipio"."geohash" FROM "municipios_municipio" ORDER BY "municipios_municipio"."nome_ascii" ASC LIMIT 5'}] -
>>> from django.db import connection >>> q = connection.queries >>>
q [] >>> from municipios.models import * >>> res = Municipio.objects.all()[:5] >>> q [] >>> for m in res: print m.uf, m.nome ... GO Abadia de Goiás MG Abadia dos Dourados GO Abadiânia MG Abaeté PA Abaetetuba >>> q [{'time': '0.000', 'sql': u'SELECT "municipios_municipio"."id", "municipios_municipio"."uf", "municipios_municipio"."nome", "municipios_municipio"."nome_ascii", "municipios_municipio"."meso_regiao_id", "municipios_municipio"."capital", "municipios_municipio"."latitude", "municipios_municipio"."longitude", "municipios_municipio"."geohash" FROM "municipios_municipio" ORDER BY "municipios_municipio"."nome_ascii" ASC LIMIT 5'}] -
>>> from django.db import connection >>> q = connection.queries >>>
q [] >>> from municipios.models import * >>> res = Municipio.objects.all()[:5] >>> q [] >>> for m in res: print m.uf, m.nome ... GO Abadia de Goiás MG Abadia dos Dourados GO Abadiânia MG Abaeté PA Abaetetuba >>> q [{'time': '0.000', 'sql': u'SELECT "municipios_municipio"."id", "municipios_municipio"."uf", "municipios_municipio"."nome", "municipios_municipio"."nome_ascii", "municipios_municipio"."meso_regiao_id", "municipios_municipio"."capital", "municipios_municipio"."latitude", "municipios_municipio"."longitude", "municipios_municipio"."geohash" FROM "municipios_municipio" ORDER BY "municipios_municipio"."nome_ascii" ASC LIMIT 5'}] conclusão: queryset é um iterável preguiçoso (lazy iterable) -
FUNÇÕES EMBUTIDAS QUE CONSOMEM ITERÁVEIS Funções de redução: aceitam iteráveis
finitos e devolvem um valor escalar (ex. um número, ou o maior item etc.) all any max min sum Consome qualquer iterável finito e devolve uma lista ordenada: sorted 42 -
FUNÇÕES GERADORAS EMBUTIDAS Podem lidar com iteráveis potencialmente ilimitados, e
devolvem geradores: enumerate filter map reversed zip 43 -
O MÓDULO ITERTOOLS 44 • geradores (potencialmente) infinitos • count(),
cycle(), repeat() • geradores que combinam vários iteráveis • chain(), tee(), izip(), imap(), product(), compress()... • geradores que selecionam ou agrupam itens: • compress(), dropwhile(), groupby(), ifilter(), islice()... • Iteradores que produzem combinações • product(), permutations(), combinations()... -
A FUNÇÃO ITER iter(iterável) Devolve um iterador sobre o iterável.
Invoca __iter__ ou constrói um iterador usando __getitem__ com índices a partir de 0. iter(função, sentinela) Constrói um iterador que invoca repetidamente a função até que o um valor igual à sentinela seja gerado. 45 >>> from random import randint >>> def d6(): ... return randint(1, 6) ... >>> for dado in iter(d6, 6): ... print(dado) ... 4 1 4 2 >>> for dado in iter(d6, 6): ... print(dado) ... >>> for dado in iter(d6, 6): ... print(dado) ... 3 2 3 -
CONVERSÃO DE GRANDES MASSAS DE DADOS Contexto ferramenta para conversão
de bases de dados semi- estruturadas. Uso funções geradoras para desacoplar laços de leitura e escrita 47 https://github.com/fluentpython/isis2json -
SOLUÇÃO: FUNÇÕES GERADORAS iterMstRecords função geradora: lê registros MST iterIsoRecords
função geradora: lê registros ISO-2709 writeJsonArray itera por registros; salva em novo formato main função principal 52 -
MAIN: SELEÇÃO DO GERADOR DE ENTRADA 54 função geradora escolhida
é passada como argumento escolha da função geradora de leitura depende do formato de entrada -
ESCREVER REGISTROS JSON writeJsonArray itera pelo gerador construído pela função
que recebe como primeiro argumento. 56 -
LER REGISTROS ISO-2709 Laço de entrada em iterJsonRecords lê
cada registro ISO-2709 e produz um dicionário com seus campos 57 função geradora! -
LER REGISTROS ISO-2709 58 produz (yield) registro na forma de
um dict cria um novo dict a cada iteração -
LER REGISTROS .MST Laço de entrada em iterMstRecords lê
cada registro .MST e produz um dicionário com seus campos 59 função geradora! -
LER REGISTROS ISO-2709 60 produz (yield) registro na forma de
um dict cria um novo dict a cada iteração -
VISÃO GERAL DA SOLUÇÃO Duas funções geradoras de entrada para
alimentar um laço de saída. Fácil estender para mais formatos de entrada! 63 -
ASSUNTOS QUE EVITAMOS… Envio de dados para um gerador através
do método .send() (em vez de next()). Uso de yield como uma expressão para obter o dado enviado por .send(). Uso de funções geradoras como co-rotinas. 64 .send() não costuma ser usado no contexto de iteração mas em pipelines “Co-rotinas não têm relação com iteração” David Beazley