Luciano Ramalho
                              luciano@ramalho.org
                                      @ramalhoorg




Iteráveis, geradores & cia:
o caminho pythonico
Comparando: C e Python
#include <stdio.h>

int main(int argc, char *argv[]) {
    int i;
    for(i = 0; i < argc; i++)
        printf("%sn", argv[i]);
    return 0;
}                import sys

                 for arg in sys.argv:
                     print arg @ramalhoorg
Iteração em Java

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                      @ramalhoorg
Iteração em Java ≥1.5                        ano:
                                             2004

 • Enhanced for (for melhorado)
class Argumentos2 {
    public static void main(String[] args) {
        for (String arg : args)
            System.out.println(arg);
    }
}


              $ java Argumentos2 alfa bravo charlie
              alfa
              bravo
              charlie                      @ramalhoorg
Iteração em Java ≥1.5                   ano:
                                        2004

 • Enhanced for (for melhorado)
class Argumentos2 {
    public static void main(String[] args) {
        for (String arg : args)
            System.out.println(arg);
    }
}
                                        ano:
                      import sys        1991

                      for arg in sys.argv:
                          print arg @ramalhoorg
Exemplos de iteração

• Iteração em Python não se limita a tipos primitivos
• Exemplos
 • string
 • arquivo
 • Django QuerySet
                                             @ramalhoorg
>>> 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                             demonstração:
MG Abadia dos Dourados
GO Abadiânia                                   queryset é um
MG Abaeté                                      iterável lazy
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'}]
Em Python o comando for
itera sobre... “iteráveis”
• Definicão preliminar informal:
 • “iterável” = que pode ser iterado
 • assim como:
    “desmontável” = que pode ser desmontado
• Iteráveis podem ser usados em outros contextos
  além do laço for

                                          @ramalhoorg
List comprehension
List comprehensions          ●   Compreensão de lista ou abrangência
                             ●   Exemplo: usar todos os elementos:
•   Expressões que consomem L2 = [n*10 for n in L]
                           – iteráveis e


    produzem listas
     qualquer iterável
    resultado: uma lista

>>> 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
                                                     @ramalhoorg
Set & dict comprehensions
• Expressões que consomem iteráveis e
  produzem sets ou dicts


 >>> s = 'abracadabra'
 >>> {c for c in s}
 set(['a', 'r', 'b', 'c', 'd'])
 >>> {c:ord(c) for c in s}
 {'a': 97, 'r': 114, 'b': 98, 'c': 99, 'd': 100}



                                           @ramalhoorg
Tipos iteráveis embutidos
  • basestring   • frozenset
   • str         • list
   • unicode     • set
  • dict         • tuple
  • file          • xrange
                               @ramalhoorg
Funções embutidas que
consomem iteráveis
• all     • max
• any     • min
• filter   • reduce
• iter    • sorted
• len     • sum
• map     • zip
                        @ramalhoorg
Operações com iteráveis
                                       >>>   a, b, c = 'XYZ'
  • Desempacotamento                   >>>
                                       'X'
                                             a

     de tupla                          >>>   b
                                       'Y'

     • em atribuições                  >>>
                                       'Z'
                                       >>>
                                             c

                                             g = (n for n in [1, 2, 3])
     • em chamadas de funções          >>>
                                       >>>
                                             a, b, c = g
                                             a
>>> def soma(a, b):                    1
...     return a + b                   >>>  b
...                                    2
>>> soma(1, 2)                         >>>  c
3                                      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)
                                                               @ramalhoorg
7
Em Python, um iterável é...
• Um objeto a partir do qual a função iter
  consegue obter um iterador.
• A chamada iter(x):
 • invoca x.__iter__() para obter um iterador
 • ou, se x.__iter__ não existe:
   • fabrica um iterador que acessa os itens de x
      sequenciamente: x[0], x[1], x[2] etc.
                                             @ramalhoorg
Trem:
uma sequência de vagões
              trem




    trem[0]
                     Curiosidade:
                     sequências eram chamadas
                     “trains” na linguagem ABC,
                     antecessora de Python
                                      @ramalhoorg
Trem:
uma sequência de vagões
>>> t = Trem(4)
>>> len(t)                           >>> for vagao in t:
4                                    ...   print(vagao)
>>> t[0]                             vagao #1
'vagao #1'                           vagao #2
>>> t[3]                             vagao #3
'vagao #4'                           vagao #4
>>> t[-1]
'vagao #4'
>>> t[4]
Traceback (most recent call last):
  ...
IndexError: vagao inexistente [4]




                                                     @ramalhoorg
Protocolo de sequência
class Trem(object):

    def __init__(self, vagoes):
        self.vagoes = vagoes

    def __getitem__(self, pos):
        indice = pos if pos >= 0 else self.vagoes + pos
        if 0 <= indice < self.vagoes: # indice 2 -> vagao #3
            return 'vagao #%s' % (indice+1)
        else:
            raise IndexError('vagao inexistente [%s]' % pos)




                                                     @ramalhoorg
Protocolo de sequência
 >>> t = Trem(4)
 >>> t[0]
 'vagao #1'
 >>> t[3]
 'vagao #4'            __getitem__
 >>> t[-1]
 'vagao #4'
 >>> for vagao in t:
 ...   print(vagao)
 vagao #1
                       __getitem__
 vagao #2
 vagao #3
 vagao #4




                                @ramalhoorg
Protocolo de sequência
   • protocolo é uma interface informal
    • pode se implementado parcialmente
class Trem(object):

    def __init__(self, num_vagoes):
        self.num_vagoes = num_vagoes

    def __getitem__(self, pos):
        indice = pos if pos >= 0 else self.num_vagoes + pos
        if 0 <= indice < self.num_vagoes: # indice 2 -> vagao #3
            return 'vagao #%s' % (indice+1)
        else:
            raise IndexError('vagao inexistente [%s]' % pos)

                                                      @ramalhoorg
Sequence
  ABC
   • Abstract Base Class
from collections import Sequence

class Trem(Sequence):

    def __init__(self, vagoes):
        self.vagoes = vagoes

    def __len__(self):
        return self.vagoes

    def __getitem__(self, pos):
        indice = pos if pos >= 0 else self.vagoes + pos
        if 0 <= indice < self.vagoes: # indice 2 -> vagao #3
            return 'vagao #%s' % (indice+1)
        else:
            raise IndexError('vagao inexistente [%s]' % pos) @ramalhoorg
Herança de
 Sequence
>>> t = Trem(4)
>>> 'vagao #2' in t
True
>>> 'vagao #5' in t
False
>>> for i in reversed(t): print i
...
vagao #4                             from collections import Sequence
vagao #3
vagao #2                             class Trem(Sequence):
vagao #1
>>> t.index('vagao #2')                  def __init__(self, vagoes):
1                                            self.vagoes = vagoes
>>> t.index('vagao #7')
Traceback (most recent call last):       def __len__(self):
                                             return self.vagoes
  ...
ValueError                                               @ramalhoorg
                                         def __getitem__(self, pos):
Interface
Iterable
• Iterable provê um método
  __iter__

• O método __iter__ devolve
  uma instância de Iterator
• Você normalmente não chama __iter__, quem
  chama é o Python
 • mas se precisar, use iter(x)
                                        @ramalhoorg
Interface
Iterator
• Iterable provê um método
  next         Python 2
  ou
  __next__     Python 3

• next/__next__ devolve o próximo item
• Você normalmente não chama __next__
 • mas se precisar, use next(x) Python ≥ 2.6
                                          @ramalhoorg
Iterator é...
• um padrão de projeto


                         Design Patterns
                         Gamma, Helm, Johnson & Vlissides
                         Addison-Wesley,
                         ISBN 0-201-63361-2




                                              @ramalhoorg
Head First
Design Patterns
Poster
O'Reilly,
ISBN 0-596-10214-3




                     @ramalhoorg
O padrão
Iterator permite
acessar os itens
de uma coleção
sequencialmente,
isolando o cliente
da implementação
da coleção.
Head First
Design Patterns
Poster
O'Reilly,
ISBN 0-596-10214-3




                     @ramalhoorg
Trem                 class Trem(object):

 com                      def __init__(self, vagoes):
                              self.vagoes = vagoes

 iterator                 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:
>>> t = Trem(4)                   self.atual += 1
>>> for vagao in t:               return 'vagao #%s' % (self.atual)
...   print(vagao)            else:
vagao #1
                                  raise StopIteration()
vagao #2
vagao #3
vagao #4                                                 @ramalhoorg
Trem
                      class Trem(object):

                          def __init__(self, vagoes):
                              self.vagoes = vagoes



 com                      def __iter__(self):
                              return IteradorTrem(self.vagoes)




 iterator
                      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)

      iter(t)                 else:
                                  raise StopIteration()


                      • for vagao in t:
>>> t = Trem(4)
>>> for vagao in t:
                       • invoca iter(t)
...   print(vagao)
vagao #1
                          • devolve IteradorTrem
vagao #2
vagao #3
vagao #4                                                         @ramalhoorg
Trem
                       class Trem(object):

                           def __init__(self, vagoes):
                               self.vagoes = vagoes



 com                       def __iter__(self):
                               return IteradorTrem(self.vagoes)




 iterator
                       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)

 next(it_trem)                 else:
                                   raise StopIteration()


                      • for vagao in t:
>>> t = Trem(4)
>>> for vagao in t:
                       • invoca iter(t)
...   print(vagao)
vagao #1
                          • devolve IteradorTrem
vagao #2
vagao #3               • invoca next(it_trem) até que
vagao #4                    ele levante StopIteration
                                                    @ramalhoorg
Em Python, um iterável é...
• Um objeto a partir do qual a função iter
  consegue obter um iterador.
• A chamada iter(x):       interface Iterable

 • invoca x.__iter__() para obter um iterador
 • ou, se x.__iter__ não existe:
   • fabrica um iterador que acessa os itens de x
      sequenciamente: x[0], x[1], x[2] etc.
           protocolo de sequência            @ramalhoorg
Iteração em C (exemplo 2)
 #include <stdio.h>

 int main(int argc, char *argv[]) {
     int i;
     for(i = 0; i < argc; i++)
         printf("%d : %sn", i, argv[i]);
     return 0;
 }
                      $   ./args2 alfa bravo charlie
                      0   : ./args2
                      1   : alfa
                      2   : bravo
                      3   : charlie          @ramalhoorg
Iteração em Python (ex. 2)
                                     não
import sys
                                  Pythonico!
for i in range(len(sys.argv)):
    print i, ':', sys.argv[i]


             $   python args2.py alfa bravo charlie
             0   : args2.py
             1   : alfa
             2   : bravo
             3   : charlie                 @ramalhoorg
Iteração em Python (ex. 2)
import sys                        Pythonico!
for i, arg in enumerate(sys.argv):
    print i, ':', arg


             $   python args2.py alfa bravo charlie
             0   : args2.py
             1   : alfa
             2   : bravo
             3   : charlie                 @ramalhoorg
Iteração em Python (ex. 2)
import sys                              isso constroi
                                         um gerador
for i, arg in enumerate(sys.argv):
    print i, ':', arg
o gerador produz uma
                            o gerador é um iterável preguiçoso!
  tupla (indice, item)
     sob demanda $ python args2.py alfa bravo charlie
    a cada iteração 0 : args2.py
                   1 : alfa
                   2 : bravo
                   3 : charlie                          @ramalhoorg
Como                     >>> e = enumerate('Turing')

funciona                 >>> e
                         <enumerate object at 0x...>
                         >>> next(e)

enumerate                (0, 'T')
                         >>> next(e) constroi
                                  isso
                         (1, 'u') um gerador
                         >>> next(e)
     enumerate           (2, 'r')
      constroi           >>> next(e)
     um gerador          (3, 'i')
                         >>> next(e)
o gerador produz uma     (4, 'n')
  tupla (indice, item)   >>> next(e)
    a cada next(e)       (5, 'g')
                         >>> next(e)
                         Traceback (most recent...):
                           ...
                         StopIteration        @ramalhoorg
Iterator x generator
• Gerador é uma generalização do iterador
• Por definição, um objeto iterador produz itens
  iterando sobre outro objeto (alguma coleção)
• Um gerador é um iterável que produz itens sem
  necessariamente acessar uma coleção
 • ele pode iterar sobre outro objeto mas também
    pode gerar itens por contra própria, sem
    qualquer dependência externa (ex. Fibonacci)
                                            @ramalhoorg
Função                  >>> def gen_123():
                        ...     yield 1

geradora                ...
                        ...
                        ...
                                yield 2
                                yield 3

                        >>> for i in gen_123(): print(i)
                        1
                        2
                        3
• Quaquer função        >>> g = gen_123()
                        >>> g
  que tenha a palavra   <generator object gen_123 at ...>
                        >>> next(g)
  reservada yield em    1
  seu corpo é uma       >>> next(g)
                        2
  função geradora       >>> next(g)
                        3
                        >>> next(g)
                        Traceback (most recent call last):
                        ...
                        StopIteration            @ramalhoorg
Objeto                 >>> def gen_123():
                       ...     yield 1

gerador                ...
                       ...
                       ...
                               yield 2
                               yield 3

                       >>> for i in gen_123(): print(i)
                       1

• Quando invocada, a
                       2
                       3
                       >>> g = gen_123()
  função geradora      >>> g
  devolve um           <generator object gen_123 at ...>
                       >>> next(g)
  objeto gerador       1
                       >>> next(g)
                       2
                       >>> next(g)
                       3
                       >>> next(g)
                       Traceback (most recent call last):
                       ...
                       StopIteration            @ramalhoorg
Objeto                    >>> def gen_123():
                          ...     yield 1

gerador                   ...
                          ...
                          ...
                                  yield 2
                                  yield 3

                          >>> for i in gen_123(): print(i)
• O objeto gerador é      1
                          2
  um iterável,            3
  implementa              >>> g = gen_123()
                          >>> g
  .next()      Python 2   <generator object gen_123 at ...>
                          >>> next(g)
  ou                      1
  .__next__() Python 3    >>> next(g)
                          2

• Use next(gerador)       >>> next(g)
                          3
                          >>> next(g)
           Python ≥ 2.6   Traceback (most recent call last):
                          ...
                          StopIteration            @ramalhoorg
>>> def gen_ab():
...
...
...
        print('iniciando...')
        yield 'A'
        print('agora vem B:')
                                     Como
                                     funciona
...     yield 'B'
...     print('FIM.')
...
>>> for s in gen_ab(): print(s)
iniciando...
A                                    • Invocar uma
agora vem B:
B
                                       função geradora
FIM.                                   produz um
>>> g = gen_ab()
>>> g # doctest: +ELLIPSIS             objeto gerador
<generator object gen_ab at 0x...>
>>> next(g)
iniciando...                         • O corpo da função só
'A'                                    começa a ser
>>> next(g)
agora vem B:                           executado quando se
'B'
>>> next(g)
                                       invoca next
Traceback (most recent call last):
...
StopIteration                                        @ramalhoorg
>>> def gen_ab():
...
...
...
        print('iniciando...')
        yield 'A'
        print('agora vem B:')
                                     Como
                                     funciona
...     yield 'B'
...     print('FIM.')
...
>>> for s in gen_ab(): print(s)
iniciando...
A
agora vem B:
B                                    • Quando next(g) é
FIM.                                   invocado, o corpo da
>>> g = gen_ab()
>>> g # doctest: +ELLIPSIS             função é executado
<generator object gen_ab at 0x...>
>>> next(g)
                                       só até o primeiro
iniciando...                           yield
'A'
>>> next(g)
agora vem B:
'B'
>>> next(g)
Traceback (most recent call last):
...
StopIteration                                       @ramalhoorg
>>> def gen_ab():
...
...
...
        print('iniciando...')
        yield 'A'
        print('agora vem B:')
                                     Como
                                     funciona
...     yield 'B'
...     print('FIM.')
...
>>> for s in gen_ab(): print(s)
iniciando...
A
agora vem B:
B                                    • Invocando next(g)
FIM.                                   novamente, a
>>> g = gen_ab()
>>> g # doctest: +ELLIPSIS             execução avança até
<generator object gen_ab at 0x...>
>>> next(g)
                                       o próximo yield
iniciando...
'A'
>>> next(g)
agora vem B:
'B'
>>> next(g)
Traceback (most recent call last):
...
StopIteration                                      @ramalhoorg
Trem c/ função geradora
                      class Trem(object):

                          def __init__(self, vagoes):
                              self.vagoes = vagoes

                          def __iter__(self):
                              for i in range(self.vagoes):
      iter(t)                     yield 'vagao #%s' % (i+1)

                        • for vagao in t:
>>> t = Trem(4)
>>> for vagao in t:
                         • invoca iter(t)
...   print(vagao)
vagao #1
                            • devolve gerador
vagao #2
vagao #3                 • invoca next(gerador) até que
vagao #4                     ele levante StopIteration
                                                     @ramalhoorg
Iterador clássico x gerador
                                           class Trem(object):

                                                def __init__(self, vagoes):
class Trem(object):                                 self.vagoes = vagoes

    def __init__(self, vagoes):                 def __iter__(self):
        self.vagoes = vagoes                        for i in range(self.vagoes):
                                                        yield 'vagao #%s' % (i+1)
    def __iter__(self):
        return IteradorTrem(self.vagoes)
                                                       1 classe,
class IteradorTrem(object):
                                                       3 linhas de código
    def __init__(self, vagoes):
        self.atual = 0
        self.ultimo_vagao = vagoes - 1

    def next(self):
        if self.atual <= self.ultimo_vagao:
            self.atual += 1                       2 classes,
            return 'vagao #%s' % (self.atual)
        else:                                     12 linhas de código
            raise StopIteration()
Iterador clássico x gerador
                                           class Trem(object):

                                                def __init__(self, vagoes):
class Trem(object):                                 self.vagoes = vagoes

    def __init__(self, vagoes):                 def __iter__(self):
        self.vagoes = vagoes                        for i in range(self.vagoes):
                                                        yield 'vagao #%s' % (i+1)
    def __iter__(self):
        return IteradorTrem(self.vagoes)

class IteradorTrem(object):
                                                           O gerador
    def __init__(self, vagoes):                            administra
        self.atual = 0
        self.ultimo_vagao = vagoes - 1                     o contexto
    def next(self):
                                                           para você
        if self.atual <= self.ultimo_vagao:
            self.atual += 1
            return 'vagao #%s' % (self.atual)
        else:
            raise StopIteration()
Expressão geradora
(genexp)
 >>> g = (c for c in 'ABC')
 >>> for l in g:
 ...     print l
 ...
 A
 B
 C
 >>> g = (c for c in 'ABC')
 >>> g
 <generator object <genexpr> at 0x10045a410>


                                        @ramalhoorg
Expressão            >>> g = (c for c in 'ABC')

geradora             >>> for l in g:
                     ...
                     ...
                             print l

                     A
                     B
                     C

• Quando avaliada,
                     >>> g = (c for c in 'ABC')
                     >>> g
                     <generator object <genexpr> at
  devolve um         0x10045a410>
  objeto gerador     >>> next(g)
                     'A'
                     >>> next(g)
                     'B'
                     >>> next(g)
                     'C'
                     >>> next(g)
                     Traceback (most recent call last):
                       File "<stdin>", line 1, in <module>
                     StopIteration
                                                 @ramalhoorg
Trem c/ expressão geradora
                  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))

      iter(t)
                         •   for vagao in t:

>>> t = Trem(4)
>>> for vagao in t:
                             •   invoca iter(t)
...   print(vagao)
vagao #1
                                 •   devolve gerador
vagao #2
vagao #3                     •   invoca gerador.next() até que
vagao #4                         ele levante StopIteration @ramalhoorg
Função geradora x genexp
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, num_vagoes):
        self.num_vagoes = num_vagoes

    def __iter__(self):
        return ('vagao #%s' % (i+1) for i in range(self.num_vagoes))


                                                         @ramalhoorg
Construtores embutidos
que consomem e
produzem iteráveis
• dict        • reversed
• enumerate   • set
• frozenset   • tuple
• list
                           @ramalhoorg
Módulo itertools
• 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()...    @ramalhoorg
Geradores em Python 3
• Várias funções e métodos da biblioteca padrão
  que devolviam listas agora devolvem geradores:
  •    dict.keys(), dict.items(), dict.values()...

  •    range(...)

      • como xrange no Py 2 (mais que um gerador)
• Quando precisar de uma lista, basta passar o
  gerador para o construtor de list:
                     list(range(10))

                                             @ramalhoorg
Exemplo prático com
funções geradoras
• Funções geradoras para desacoplar laços de
  leitura e escrita em uma ferramenta para
  conversão de bases de dados semi-estruturadas


https://github.com/ramalho/isis2json


                                           @ramalhoorg
Laço principal escreve
arquivo JSON




                         @ramalhoorg
Um outro laço lê os
registros a converter




                        @ramalhoorg
Implementação possível:
o mesmo laço lê e grava




                      @ramalhoorg
Mas e a lógica para ler
outro formato?




                          @ramalhoorg
Funções
do script
 • iterMstRecords*
 • iterIsoRecords*
 • writeJsonArray
 • main	

      * funções geradoras
                            @ramalhoorg
Função main:
leitura dos
argumentos




               @ramalhoorg
Função main: seleção
do formato escolha dadepende geradora
               de leitura
                          função
                                 do
de entrada     formato de entrada




                    função geradora escolhida
                    é passada como argumento
                                      @ramalhoorg
writeJsonArray:
escrever
registros
em JSON



                  @ramalhoorg
writeJsonArray:
itera sobre umas das
funções geradoras




                       @ramalhoorg
iterIsoRecords:
ler registros     função geradora!


de arquivo
ISO-2709



                            @ramalhoorg
iterIsoRecords
                        cria um novo dict
                        a cada iteração




                 produz (yield) registro
                 na forma de um dict @ramalhoorg
iterMstRecords:   função geradora!

ler registros
de arquivo
ISIS .MST



                             @ramalhoorg
iterMstRecords
iterIsoRecords
                    cria um novo dict
                    a cada iteração




             produz (yield) registro
             na forma de um dict @ramalhoorg
Geradores na prática




                       @ramalhoorg
Geradores na prática




                       @ramalhoorg
Geradores na prática




                       @ramalhoorg
Faltou apresentar...
• Envio de dados para um gerador através do
  método .send() (em vez de .next()),
  e uso de yield como uma     .send() não costuma
  expressão para obter o dado ser usado no contexto
  enviado                     de iteração mas em
                              pipelines
• Uso de funções geradoras como co-rotinas
               “Coroutines are not related to iteration”
                                         David Beazley
                                               @ramalhoorg
Faltou apresentar...
• Envio de dados para um gerador através do
  método .send() (em vez de .next()),
  e uso de yield como uma     .send() não costuma
  expressão para obter o dado ser usado no contexto
  enviado                     de iteração mas em
                              pipelines
• Uso de funções geradoras como co-rotinas
              “Co-rotinas não têm relação com iteração”
                                         David Beazley
                                              @ramalhoorg
Oficinas Turing:
computação para programadores
 • Próximos lançamentos:
   • 1ª turma de Python para quem usa Django
   • 3ª turma de Objetos Pythonicos
   • 4ª turma de Python para quem sabe Python
   Para saber mais sobre estes cursos ONLINE
                    escreva para:
           ramalho@turing.com.br
 Turing.com.br

Iteraveis e geradores

  • 1.
    Luciano Ramalho luciano@ramalho.org @ramalhoorg Iteráveis, geradores & cia: o caminho pythonico
  • 2.
    Comparando: C ePython #include <stdio.h> int main(int argc, char *argv[]) { int i; for(i = 0; i < argc; i++) printf("%sn", argv[i]); return 0; } import sys for arg in sys.argv: print arg @ramalhoorg
  • 3.
    Iteração em Java classArgumentos { 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 @ramalhoorg
  • 4.
    Iteração em Java≥1.5 ano: 2004 • Enhanced for (for melhorado) class Argumentos2 { public static void main(String[] args) { for (String arg : args) System.out.println(arg); } } $ java Argumentos2 alfa bravo charlie alfa bravo charlie @ramalhoorg
  • 5.
    Iteração em Java≥1.5 ano: 2004 • Enhanced for (for melhorado) class Argumentos2 { public static void main(String[] args) { for (String arg : args) System.out.println(arg); } } ano: import sys 1991 for arg in sys.argv: print arg @ramalhoorg
  • 6.
    Exemplos de iteração •Iteração em Python não se limita a tipos primitivos • Exemplos • string • arquivo • Django QuerySet @ramalhoorg
  • 7.
    >>> from django.dbimport 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 demonstração: MG Abadia dos Dourados GO Abadiânia queryset é um MG Abaeté iterável lazy 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'}]
  • 8.
    Em Python ocomando for itera sobre... “iteráveis” • Definicão preliminar informal: • “iterável” = que pode ser iterado • assim como: “desmontável” = que pode ser desmontado • Iteráveis podem ser usados em outros contextos além do laço for @ramalhoorg
  • 9.
    List comprehension List comprehensions ● Compreensão de lista ou abrangência ● Exemplo: usar todos os elementos: • Expressões que consomem L2 = [n*10 for n in L] – iteráveis e produzem listas qualquer iterável resultado: uma lista >>> 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 @ramalhoorg
  • 10.
    Set & dictcomprehensions • Expressões que consomem iteráveis e produzem sets ou dicts >>> s = 'abracadabra' >>> {c for c in s} set(['a', 'r', 'b', 'c', 'd']) >>> {c:ord(c) for c in s} {'a': 97, 'r': 114, 'b': 98, 'c': 99, 'd': 100} @ramalhoorg
  • 11.
    Tipos iteráveis embutidos • basestring • frozenset • str • list • unicode • set • dict • tuple • file • xrange @ramalhoorg
  • 12.
    Funções embutidas que consomemiteráveis • all • max • any • min • filter • reduce • iter • sorted • len • sum • map • zip @ramalhoorg
  • 13.
    Operações com iteráveis >>> a, b, c = 'XYZ' • Desempacotamento >>> 'X' a de tupla >>> b 'Y' • em atribuições >>> 'Z' >>> c g = (n for n in [1, 2, 3]) • em chamadas de funções >>> >>> a, b, c = g a >>> def soma(a, b): 1 ... return a + b >>> b ... 2 >>> soma(1, 2) >>> c 3 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) @ramalhoorg 7
  • 14.
    Em Python, umiterável é... • Um objeto a partir do qual a função iter consegue obter um iterador. • A chamada iter(x): • invoca x.__iter__() para obter um iterador • ou, se x.__iter__ não existe: • fabrica um iterador que acessa os itens de x sequenciamente: x[0], x[1], x[2] etc. @ramalhoorg
  • 15.
    Trem: uma sequência devagões trem trem[0] Curiosidade: sequências eram chamadas “trains” na linguagem ABC, antecessora de Python @ramalhoorg
  • 16.
    Trem: uma sequência devagões >>> t = Trem(4) >>> len(t) >>> for vagao in t: 4 ... print(vagao) >>> t[0] vagao #1 'vagao #1' vagao #2 >>> t[3] vagao #3 'vagao #4' vagao #4 >>> t[-1] 'vagao #4' >>> t[4] Traceback (most recent call last): ... IndexError: vagao inexistente [4] @ramalhoorg
  • 17.
    Protocolo de sequência classTrem(object): def __init__(self, vagoes): self.vagoes = vagoes def __getitem__(self, pos): indice = pos if pos >= 0 else self.vagoes + pos if 0 <= indice < self.vagoes: # indice 2 -> vagao #3 return 'vagao #%s' % (indice+1) else: raise IndexError('vagao inexistente [%s]' % pos) @ramalhoorg
  • 18.
    Protocolo de sequência >>> t = Trem(4) >>> t[0] 'vagao #1' >>> t[3] 'vagao #4' __getitem__ >>> t[-1] 'vagao #4' >>> for vagao in t: ... print(vagao) vagao #1 __getitem__ vagao #2 vagao #3 vagao #4 @ramalhoorg
  • 19.
    Protocolo de sequência • protocolo é uma interface informal • pode se implementado parcialmente class Trem(object): def __init__(self, num_vagoes): self.num_vagoes = num_vagoes def __getitem__(self, pos): indice = pos if pos >= 0 else self.num_vagoes + pos if 0 <= indice < self.num_vagoes: # indice 2 -> vagao #3 return 'vagao #%s' % (indice+1) else: raise IndexError('vagao inexistente [%s]' % pos) @ramalhoorg
  • 20.
    Sequence ABC • Abstract Base Class from collections import Sequence class Trem(Sequence): def __init__(self, vagoes): self.vagoes = vagoes def __len__(self): return self.vagoes def __getitem__(self, pos): indice = pos if pos >= 0 else self.vagoes + pos if 0 <= indice < self.vagoes: # indice 2 -> vagao #3 return 'vagao #%s' % (indice+1) else: raise IndexError('vagao inexistente [%s]' % pos) @ramalhoorg
  • 21.
    Herança de Sequence >>>t = Trem(4) >>> 'vagao #2' in t True >>> 'vagao #5' in t False >>> for i in reversed(t): print i ... vagao #4 from collections import Sequence vagao #3 vagao #2 class Trem(Sequence): vagao #1 >>> t.index('vagao #2') def __init__(self, vagoes): 1 self.vagoes = vagoes >>> t.index('vagao #7') Traceback (most recent call last): def __len__(self): return self.vagoes ... ValueError @ramalhoorg def __getitem__(self, pos):
  • 22.
    Interface Iterable • Iterable provêum método __iter__ • O método __iter__ devolve uma instância de Iterator • Você normalmente não chama __iter__, quem chama é o Python • mas se precisar, use iter(x) @ramalhoorg
  • 23.
    Interface Iterator • Iterable provêum método next Python 2 ou __next__ Python 3 • next/__next__ devolve o próximo item • Você normalmente não chama __next__ • mas se precisar, use next(x) Python ≥ 2.6 @ramalhoorg
  • 24.
    Iterator é... • umpadrão de projeto Design Patterns Gamma, Helm, Johnson & Vlissides Addison-Wesley, ISBN 0-201-63361-2 @ramalhoorg
  • 25.
  • 26.
    O padrão Iterator permite acessaros itens de uma coleção sequencialmente, isolando o cliente da implementação da coleção. Head First Design Patterns Poster O'Reilly, ISBN 0-596-10214-3 @ramalhoorg
  • 27.
    Trem class Trem(object): com def __init__(self, vagoes): self.vagoes = vagoes iterator 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: >>> t = Trem(4) self.atual += 1 >>> for vagao in t: return 'vagao #%s' % (self.atual) ... print(vagao) else: vagao #1 raise StopIteration() vagao #2 vagao #3 vagao #4 @ramalhoorg
  • 28.
    Trem class Trem(object): def __init__(self, vagoes): self.vagoes = vagoes com def __iter__(self): return IteradorTrem(self.vagoes) iterator 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) iter(t) else: raise StopIteration() • for vagao in t: >>> t = Trem(4) >>> for vagao in t: • invoca iter(t) ... print(vagao) vagao #1 • devolve IteradorTrem vagao #2 vagao #3 vagao #4 @ramalhoorg
  • 29.
    Trem class Trem(object): def __init__(self, vagoes): self.vagoes = vagoes com def __iter__(self): return IteradorTrem(self.vagoes) iterator 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) next(it_trem) else: raise StopIteration() • for vagao in t: >>> t = Trem(4) >>> for vagao in t: • invoca iter(t) ... print(vagao) vagao #1 • devolve IteradorTrem vagao #2 vagao #3 • invoca next(it_trem) até que vagao #4 ele levante StopIteration @ramalhoorg
  • 30.
    Em Python, umiterável é... • Um objeto a partir do qual a função iter consegue obter um iterador. • A chamada iter(x): interface Iterable • invoca x.__iter__() para obter um iterador • ou, se x.__iter__ não existe: • fabrica um iterador que acessa os itens de x sequenciamente: x[0], x[1], x[2] etc. protocolo de sequência @ramalhoorg
  • 31.
    Iteração em C(exemplo 2) #include <stdio.h> int main(int argc, char *argv[]) { int i; for(i = 0; i < argc; i++) printf("%d : %sn", i, argv[i]); return 0; } $ ./args2 alfa bravo charlie 0 : ./args2 1 : alfa 2 : bravo 3 : charlie @ramalhoorg
  • 32.
    Iteração em Python(ex. 2) não import sys Pythonico! for i in range(len(sys.argv)): print i, ':', sys.argv[i] $ python args2.py alfa bravo charlie 0 : args2.py 1 : alfa 2 : bravo 3 : charlie @ramalhoorg
  • 33.
    Iteração em Python(ex. 2) import sys Pythonico! for i, arg in enumerate(sys.argv): print i, ':', arg $ python args2.py alfa bravo charlie 0 : args2.py 1 : alfa 2 : bravo 3 : charlie @ramalhoorg
  • 34.
    Iteração em Python(ex. 2) import sys isso constroi um gerador for i, arg in enumerate(sys.argv): print i, ':', arg o gerador produz uma o gerador é um iterável preguiçoso! tupla (indice, item) sob demanda $ python args2.py alfa bravo charlie a cada iteração 0 : args2.py 1 : alfa 2 : bravo 3 : charlie @ramalhoorg
  • 35.
    Como >>> e = enumerate('Turing') funciona >>> e <enumerate object at 0x...> >>> next(e) enumerate (0, 'T') >>> next(e) constroi isso (1, 'u') um gerador >>> next(e) enumerate (2, 'r') constroi >>> next(e) um gerador (3, 'i') >>> next(e) o gerador produz uma (4, 'n') tupla (indice, item) >>> next(e) a cada next(e) (5, 'g') >>> next(e) Traceback (most recent...): ... StopIteration @ramalhoorg
  • 36.
    Iterator x generator •Gerador é uma generalização do iterador • Por definição, um objeto iterador produz itens iterando sobre outro objeto (alguma coleção) • Um gerador é um iterável que produz itens sem necessariamente acessar uma coleção • ele pode iterar sobre outro objeto mas também pode gerar itens por contra própria, sem qualquer dependência externa (ex. Fibonacci) @ramalhoorg
  • 37.
    Função >>> def gen_123(): ... yield 1 geradora ... ... ... yield 2 yield 3 >>> for i in gen_123(): print(i) 1 2 3 • Quaquer função >>> g = gen_123() >>> g que tenha a palavra <generator object gen_123 at ...> >>> next(g) reservada yield em 1 seu corpo é uma >>> next(g) 2 função geradora >>> next(g) 3 >>> next(g) Traceback (most recent call last): ... StopIteration @ramalhoorg
  • 38.
    Objeto >>> def gen_123(): ... yield 1 gerador ... ... ... yield 2 yield 3 >>> for i in gen_123(): print(i) 1 • Quando invocada, a 2 3 >>> g = gen_123() função geradora >>> g devolve um <generator object gen_123 at ...> >>> next(g) objeto gerador 1 >>> next(g) 2 >>> next(g) 3 >>> next(g) Traceback (most recent call last): ... StopIteration @ramalhoorg
  • 39.
    Objeto >>> def gen_123(): ... yield 1 gerador ... ... ... yield 2 yield 3 >>> for i in gen_123(): print(i) • O objeto gerador é 1 2 um iterável, 3 implementa >>> g = gen_123() >>> g .next() Python 2 <generator object gen_123 at ...> >>> next(g) ou 1 .__next__() Python 3 >>> next(g) 2 • Use next(gerador) >>> next(g) 3 >>> next(g) Python ≥ 2.6 Traceback (most recent call last): ... StopIteration @ramalhoorg
  • 40.
    >>> def gen_ab(): ... ... ... print('iniciando...') yield 'A' print('agora vem B:') Como funciona ... yield 'B' ... print('FIM.') ... >>> for s in gen_ab(): print(s) iniciando... A • Invocar uma agora vem B: B função geradora FIM. produz um >>> g = gen_ab() >>> g # doctest: +ELLIPSIS objeto gerador <generator object gen_ab at 0x...> >>> next(g) iniciando... • O corpo da função só 'A' começa a ser >>> next(g) agora vem B: executado quando se 'B' >>> next(g) invoca next Traceback (most recent call last): ... StopIteration @ramalhoorg
  • 41.
    >>> def gen_ab(): ... ... ... print('iniciando...') yield 'A' print('agora vem B:') Como funciona ... yield 'B' ... print('FIM.') ... >>> for s in gen_ab(): print(s) iniciando... A agora vem B: B • Quando next(g) é FIM. invocado, o corpo da >>> g = gen_ab() >>> g # doctest: +ELLIPSIS função é executado <generator object gen_ab at 0x...> >>> next(g) só até o primeiro iniciando... yield 'A' >>> next(g) agora vem B: 'B' >>> next(g) Traceback (most recent call last): ... StopIteration @ramalhoorg
  • 42.
    >>> def gen_ab(): ... ... ... print('iniciando...') yield 'A' print('agora vem B:') Como funciona ... yield 'B' ... print('FIM.') ... >>> for s in gen_ab(): print(s) iniciando... A agora vem B: B • Invocando next(g) FIM. novamente, a >>> g = gen_ab() >>> g # doctest: +ELLIPSIS execução avança até <generator object gen_ab at 0x...> >>> next(g) o próximo yield iniciando... 'A' >>> next(g) agora vem B: 'B' >>> next(g) Traceback (most recent call last): ... StopIteration @ramalhoorg
  • 43.
    Trem c/ funçãogeradora class Trem(object): def __init__(self, vagoes): self.vagoes = vagoes def __iter__(self): for i in range(self.vagoes): iter(t) yield 'vagao #%s' % (i+1) • for vagao in t: >>> t = Trem(4) >>> for vagao in t: • invoca iter(t) ... print(vagao) vagao #1 • devolve gerador vagao #2 vagao #3 • invoca next(gerador) até que vagao #4 ele levante StopIteration @ramalhoorg
  • 44.
    Iterador clássico xgerador class Trem(object): def __init__(self, vagoes): class Trem(object): self.vagoes = vagoes def __init__(self, vagoes): def __iter__(self): self.vagoes = vagoes for i in range(self.vagoes): yield 'vagao #%s' % (i+1) def __iter__(self): return IteradorTrem(self.vagoes) 1 classe, class IteradorTrem(object): 3 linhas de código def __init__(self, vagoes): self.atual = 0 self.ultimo_vagao = vagoes - 1 def next(self): if self.atual <= self.ultimo_vagao: self.atual += 1 2 classes, return 'vagao #%s' % (self.atual) else: 12 linhas de código raise StopIteration()
  • 45.
    Iterador clássico xgerador class Trem(object): def __init__(self, vagoes): class Trem(object): self.vagoes = vagoes def __init__(self, vagoes): def __iter__(self): self.vagoes = vagoes for i in range(self.vagoes): yield 'vagao #%s' % (i+1) def __iter__(self): return IteradorTrem(self.vagoes) class IteradorTrem(object): O gerador def __init__(self, vagoes): administra self.atual = 0 self.ultimo_vagao = vagoes - 1 o contexto def next(self): para você if self.atual <= self.ultimo_vagao: self.atual += 1 return 'vagao #%s' % (self.atual) else: raise StopIteration()
  • 46.
    Expressão geradora (genexp) >>>g = (c for c in 'ABC') >>> for l in g: ... print l ... A B C >>> g = (c for c in 'ABC') >>> g <generator object <genexpr> at 0x10045a410> @ramalhoorg
  • 47.
    Expressão >>> g = (c for c in 'ABC') geradora >>> for l in g: ... ... print l A B C • Quando avaliada, >>> g = (c for c in 'ABC') >>> g <generator object <genexpr> at devolve um 0x10045a410> objeto gerador >>> next(g) 'A' >>> next(g) 'B' >>> next(g) 'C' >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration @ramalhoorg
  • 48.
    Trem c/ expressãogeradora 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)) iter(t) • for vagao in t: >>> t = Trem(4) >>> for vagao in t: • invoca iter(t) ... print(vagao) vagao #1 • devolve gerador vagao #2 vagao #3 • invoca gerador.next() até que vagao #4 ele levante StopIteration @ramalhoorg
  • 49.
    Função geradora xgenexp 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, num_vagoes): self.num_vagoes = num_vagoes def __iter__(self): return ('vagao #%s' % (i+1) for i in range(self.num_vagoes)) @ramalhoorg
  • 50.
    Construtores embutidos que consomeme produzem iteráveis • dict • reversed • enumerate • set • frozenset • tuple • list @ramalhoorg
  • 51.
    Módulo itertools • 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()... @ramalhoorg
  • 52.
    Geradores em Python3 • Várias funções e métodos da biblioteca padrão que devolviam listas agora devolvem geradores: • dict.keys(), dict.items(), dict.values()... • range(...) • como xrange no Py 2 (mais que um gerador) • Quando precisar de uma lista, basta passar o gerador para o construtor de list: list(range(10)) @ramalhoorg
  • 53.
    Exemplo prático com funçõesgeradoras • Funções geradoras para desacoplar laços de leitura e escrita em uma ferramenta para conversão de bases de dados semi-estruturadas https://github.com/ramalho/isis2json @ramalhoorg
  • 54.
  • 55.
    Um outro laçolê os registros a converter @ramalhoorg
  • 56.
    Implementação possível: o mesmolaço lê e grava @ramalhoorg
  • 57.
    Mas e alógica para ler outro formato? @ramalhoorg
  • 58.
    Funções do script •iterMstRecords* • iterIsoRecords* • writeJsonArray • main * funções geradoras @ramalhoorg
  • 59.
  • 60.
    Função main: seleção doformato escolha dadepende geradora de leitura função do de entrada formato de entrada função geradora escolhida é passada como argumento @ramalhoorg
  • 61.
  • 62.
    writeJsonArray: itera sobre umasdas funções geradoras @ramalhoorg
  • 63.
    iterIsoRecords: ler registros função geradora! de arquivo ISO-2709 @ramalhoorg
  • 64.
    iterIsoRecords cria um novo dict a cada iteração produz (yield) registro na forma de um dict @ramalhoorg
  • 65.
    iterMstRecords: função geradora! ler registros de arquivo ISIS .MST @ramalhoorg
  • 66.
    iterMstRecords iterIsoRecords cria um novo dict a cada iteração produz (yield) registro na forma de um dict @ramalhoorg
  • 67.
  • 68.
  • 69.
  • 70.
    Faltou apresentar... • Enviode dados para um gerador através do método .send() (em vez de .next()), e uso de yield como uma .send() não costuma expressão para obter o dado ser usado no contexto enviado de iteração mas em pipelines • Uso de funções geradoras como co-rotinas “Coroutines are not related to iteration” David Beazley @ramalhoorg
  • 71.
    Faltou apresentar... • Enviode dados para um gerador através do método .send() (em vez de .next()), e uso de yield como uma .send() não costuma expressão para obter o dado ser usado no contexto enviado de iteração mas em pipelines • Uso de funções geradoras como co-rotinas “Co-rotinas não têm relação com iteração” David Beazley @ramalhoorg
  • 72.
    Oficinas Turing: computação paraprogramadores • Próximos lançamentos: • 1ª turma de Python para quem usa Django • 3ª turma de Objetos Pythonicos • 4ª turma de Python para quem sabe Python Para saber mais sobre estes cursos ONLINE escreva para: ramalho@turing.com.br Turing.com.br