O que exatamente são iterador, iterável e iteração?

442

Qual é a definição mais básica de "iterável", "iterador" e "iteração" no Python?

Eu li várias definições, mas não consigo identificar o significado exato, pois ele ainda não se encaixa.

Alguém pode me ajudar com as 3 definições em termos leigos?

thechrishaddad
fonte

Respostas:

530

Iteração é um termo geral para pegar cada item de algo, um após o outro. Sempre que você usa um loop, explícito ou implícito, para examinar um grupo de itens, é iteração.

No Python, iterável e iterador têm significados específicos.

Um iterável é um objeto que possui um __iter__método que retorna um iterador ou que define um __getitem__método que pode receber índices seqüenciais começando do zero (e gera um IndexErrorquando os índices não são mais válidos). Portanto, um iterável é um objeto do qual você pode obter um iterador .

Um iterador é um objeto com um método next(Python 2) ou __next__(Python 3).

Sempre que você usa um forloop, ou mapuma compreensão de lista etc. no Python, o nextmétodo é chamado automaticamente para obter cada item do iterador , passando pelo processo de iteração .

Um bom lugar para começar a aprender seria a seção de iteradores do tutorial e a seção de tipos de iteradores da página de tipos padrão . Depois de entender o básico, tente a seção de iteradores do HOWTO de Programação Funcional .

agf
fonte
1
Observe que collections.abc.AsyncIteratortestes para__aiter__ e __anext__métodos. Esta é uma nova adição no 3.6.
Janus Troelsen
1
@ jlh por que __len__estaria necessariamente vinculado à iteração? Como saber o tamanho de algo o ajudaria a interagir com ele?
Shadowtalker 19/09/19
2
@shadowtalker ajudaria a saber quais índices são válidos, para que você saiba com quais índices pode ser usado __getitem__ .
Jlh
4
@jlh parece que você está propondo um comportamento muito opinativo. Considere que {'a': 'hi', 'b': 'bye'}possui comprimento 2, mas não pode ser indexado por 0, 1 ou 2.
shadowtalker
2
@shadowtalker. Mas um ditado tem um __iter__método. Eu acho que jlh está se referindo a objetos iteráveis ​​especificamente porque eles definem: "um __getitem__método que pode receber índices seqüenciais a partir do zero".
Rico
337

Aqui está a explicação que eu uso nas aulas de Python:

Um ITERABLE é:

  • qualquer coisa que possa ser repetida (ou seja, você pode repetir uma sequência ou arquivo) ou
  • qualquer coisa que possa aparecer no lado direito de um loop for: for x in iterable: ...ou
  • qualquer coisa com a qual você possa ligar iter()retornará um ITERATOR: iter(obj)ou
  • um objeto que define __iter__ que retorna um ITERATOR novo ou pode ter um __getitem__método adequado para pesquisa indexada.

Um ITERATOR é um objeto:

  • com o estado que lembra onde está durante a iteração,
  • com um __next__ método que:
    • retorna o próximo valor na iteração
    • atualiza o estado para apontar para o próximo valor
    • sinais quando é feito, aumentando StopIteration
  • e isso é auto-iterável (o que significa que ele tem um __iter__método que retorna self).

Notas:

  • o __next__ método no Python 3 está escrito nextno Python 2 e
  • A função embutida next() chama esse método no objeto passado para ele.

Por exemplo:

>>> s = 'cat'      # s is an ITERABLE
                   # s is a str object that is immutable
                   # s has no state
                   # s has a __getitem__() method 

>>> t = iter(s)    # t is an ITERATOR
                   # t has state (it starts by pointing at the "c"
                   # t has a next() method and an __iter__() method

>>> next(t)        # the next() function returns the next value and advances the state
'c'
>>> next(t)        # the next() function returns the next value and advances
'a'
>>> next(t)        # the next() function returns the next value and advances
't'
>>> next(t)        # next() raises StopIteration to signal that iteration is complete
Traceback (most recent call last):
...
StopIteration

>>> iter(t) is t   # the iterator is self-iterable
Raymond Hettinger
fonte
o que você quer dizer com iterador novo?
Lmiguelvargasf
13
@lmiguelvargasf "Fresco" como em "novo e não consumido", em vez de "esgotado ou parcialmente consumido". A idéia é que um novo iterador inicie no início, enquanto um iterador parcialmente usado retoma de onde parou.
Raymond Hettinger
Seu segundo, terceiro e quarto marcadores indicam claramente o que você quer dizer, em termos de construções específicas de python, built-ins ou chamadas de método. Mas o primeiro marcador ("qualquer coisa que possa ser repetida") não tem essa clareza. Além disso, o primeiro marcador parece ter uma sobreposição com o segundo marcador, já que o segundo marcador é sobre forloops, e o primeiro marcador é sobre "looping over". Você poderia resolver estes?
manancial
2
Pls considerar fraseado re- "qualquer coisa a sua chamada lata com iter()" como "qualquer coisa que você pode passar para iter()"
Manancial de
98

As respostas acima são ótimas, mas, como a maior parte do que eu vi, não enfatize distinção o suficiente para pessoas como eu.

Além disso, as pessoas tendem a ficar "muito pitonicas" colocando definições como "X é um objeto que possui __foo__()método" antes. Tais definições estão corretas - elas se baseiam na filosofia de digitação de patos, mas o foco nos métodos tende a ficar mais claro quando se tenta entender o conceito em sua simplicidade.

Então eu adiciono minha versão.


Em linguagem natural,

  • iteração é o processo de obter um elemento de cada vez em uma linha de elementos.

Em Python,

  • iterável é um objeto que é, bem, iterável, que simplesmente coloca, significa que ele pode ser usado na iteração, por exemplo, com um forloop. Quão? Usando o iterador . Eu vou explicar abaixo.

  • ... enquanto o iterador é um objeto que define como realmente fazer a iteração - especificamente qual é o próximo elemento. É por isso que deve ter next()método.

Os iteradores também são iteráveis, com a distinção de que seu __iter__()método retorna o mesmo objeto ( self), independentemente de seus itens terem ou não sido consumidos por chamadas anteriores para next().


Então, o que o intérprete Python pensa quando vê uma for x in obj:declaração?

Olha, um forlaço. Parece um trabalho para um iterador ... Vamos pegar um. ... Tem esse objcara, então vamos perguntar a ele.

"Sr. obj, você tem seu iterador?" (... chama iter(obj), que chama obj.__iter__(), que felizmente distribui um novo iterador brilhante _i.)

OK, foi fácil ... Vamos começar a iterar então. ( x = _i.next()... x = _i.next()...)

Como o Sr. objobteve sucesso neste teste (com um método retornando um iterador válido), recompensamos-o com o adjetivo: agora você pode chamá-lo de "Sr. iterável obj".

No entanto, em casos simples, você normalmente não se beneficia de ter o iterador e iterável separadamente. Portanto, você define apenas um objeto, que também é seu próprio iterador. (O Python realmente não se importa com o fato de que a _idistribuição objnão foi tão brilhante, mas apenas a objprópria).

É por isso que na maioria dos exemplos que eu vi (e o que estava me confundindo repetidamente), você pode ver:

class IterableExample(object):

    def __iter__(self):
        return self

    def next(self):
        pass

ao invés de

class Iterator(object):
    def next(self):
        pass

class Iterable(object):
    def __iter__(self):
        return Iterator()

Há casos, no entanto, quando você pode se beneficiar da separação do iterador do iterável, como quando você deseja ter uma linha de itens, mas mais "cursores". Por exemplo, quando você deseja trabalhar com os elementos "atual" e "próximo", é possível ter iteradores separados para ambos. Ou vários threads extraídos de uma lista enorme: cada um pode ter seu próprio iterador para percorrer todos os itens. Veja @ Raymond's e @ glglgl's respostas de acima.

Imagine o que você poderia fazer:

class SmartIterableExample(object):

    def create_iterator(self):
        # An amazingly powerful yet simple way to create arbitrary
        # iterator, utilizing object state (or not, if you are fan
        # of functional), magic and nuclear waste--no kittens hurt.
        pass    # don't forget to add the next() method

    def __iter__(self):
        return self.create_iterator()

Notas:

  • Vou repetir novamente: o iterador não é iterável . O iterador não pode ser usado como uma "fonte" no forloop. O que o forloop precisa principalmente é __iter__() (que retorna algo com next()).

  • Obviamente, esse fornão é o único loop de iteração; portanto, acima também se aplica a outras construções ( while...).

  • O iterador next()pode lançar StopIteration para interromper a iteração. No entanto, ele não precisa iterar para sempre ou usar outros meios.

  • No "processo de pensamento" acima, _iele realmente não existe. Eu inventei esse nome.

  • Há uma pequena alteração no Python 3.x: o next()método (não o incorporado) agora deve ser chamado __next__(). Sim, deveria ter sido assim o tempo todo.

  • Você também pode pensar assim: iterável possui os dados, o iterador puxa o próximo item

Isenção de responsabilidade: eu não sou desenvolvedor de nenhum intérprete Python, então não sei realmente o que o intérprete "pensa". As reflexões acima são apenas uma demonstração de como eu entendo o tópico de outras explicações, experimentos e experiências da vida real de um novato em Python.

Alois Mahdal
fonte
1
Isso é ótimo - mas ainda estou um pouco confuso. Eu pensei que sua caixa amarela estava dizendo que um forloop precisa de um iterador ("Olha, um loop for. Parece um trabalho para um iterador ... Vamos pegar um."). Mas então você diz nas notas no final que "O iterador não pode ser usado como fonte em um forloop" ...?
Racing Tadpole 21/03
Por que você coloca apenas passo código para essas nextdefinições? Suponho que você apenas queira dizer que alguém precisa implementar uma maneira de obter a próxima, já que a próxima deve retornar alguma coisa.
Nealmcb 10/04
@nealmcb Sim, acho que foi isso que me significou passado. (Isso é o que passé para , depois de tudo.)
Alois Mahdal
@AloisMahdal Ahh, eu nunca tinha visto esse uso antes. Quando vejo pass, acho que está lá por razões sintáticas. Acabei de encontrar as respostas no objeto de reticências que são bastante interessantes: você pode usar ...para indicar um bloco "todo mais tarde". NotImplementedtambém está disponível.
Nellmcb
Enquanto eu gosto que você está enfatizando a distinção entre um iterador e um iterável, essa resposta se contradiz. Primeiro, você escreve: 'Os iteradores também são iteráveis' (que corresponde ao que está escrito na documentação do Python ). Mas, mais tarde, você escreve: ' iterator não é iterável . O iterador não pode ser usado como uma "fonte" no forloop '. Entendi a sua resposta e, caso contrário, gostaria, mas acho que se beneficiaria disso.
Rich
22

Um iterável é um objeto que possui um __iter__()método. Ele pode ser repetido várias vezes, como list()s e tuple()s.

Um iterador é o objeto que itera. Ele é retornado por um __iter__()método, retorna por meio de seu próprio __iter__()método e possui um next()método ( __next__()na versão 3.x).

A iteração é o processo de chamar isso de next()resp. __next__()até que aumente StopIteration.

Exemplo:

>>> a = [1, 2, 3] # iterable
>>> b1 = iter(a) # iterator 1
>>> b2 = iter(a) # iterator 2, independent of b1
>>> next(b1)
1
>>> next(b1)
2
>>> next(b2) # start over, as it is the first call to b2
1
>>> next(b1)
3
>>> next(b1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> b1 = iter(a) # new one, start over
>>> next(b1)
1
glglgl
fonte
Então, na verdade, é apenas um objeto que passa pelo contêiner? Isso seria útil?
27612 thechrishaddad
Frequentemente, mas nem sempre. Um gerador, arquivo ou cursor do banco de dados só pode ser iterado uma vez e, portanto, são seus próprios iteradores.
glglgl
Eu acho que B2 não precisa independente de B2? para este caso especial, é independente, com certeza posso torná-lo independente, mas também válido Iterable.
Bin
@ Bin Sim. Como e Iteratoré sempre Iterablee é seu Iterator, duas chamadas de iter()não dão necessariamente duas Iterators independentes .
glglgl
13

Aqui está minha cábula:

 sequence
  +
  |
  v
   def __getitem__(self, index: int):
  +    ...
  |    raise IndexError
  |
  |
  |              def __iter__(self):
  |             +     ...
  |             |     return <iterator>
  |             |
  |             |
  +--> or <-----+        def __next__(self):
       +        |       +    ...
       |        |       |    raise StopIteration
       v        |       |
    iterable    |       |
           +    |       |
           |    |       v
           |    +----> and +-------> iterator
           |                               ^
           v                               |
   iter(<iterable>) +----------------------+
                                           |
   def generator():                        |
  +    yield 1                             |
  |                 generator_expression +-+
  |                                        |
  +-> generator() +-> generator_iterator +-+

Quiz: Você vê como ...

  1. todo iterador é iterável?
  2. um objeto contêiner __iter__() método de pode ser implementado como um gerador?
  3. um iterável que possui um __next__método não é necessariamente um iterador?

Respostas:

  1. Todo iterador deve ter um __iter__método Ter __iter__é suficiente para ser iterável. Portanto, todo iterador é iterável.
  2. Quando __iter__é chamado, ele deve retornar um iterador ( return <iterator>no diagrama acima). Chamar um gerador retorna um iterador de gerador, que é um tipo de iterador.

    class Iterable1:
        def __iter__(self):
            # a method (which is a function defined inside a class body)
            # calling iter() converts iterable (tuple) to iterator
            return iter((1,2,3))
    
    class Iterable2:
        def __iter__(self):
            # a generator
            for i in (1, 2, 3):
                yield i
    
    class Iterable3:
        def __iter__(self):
            # with PEP 380 syntax
            yield from (1, 2, 3)
    
    # passes
    assert list(Iterable1()) == list(Iterable2()) == list(Iterable3()) == [1, 2, 3]
  3. Aqui está um exemplo:

    class MyIterable:
    
        def __init__(self):
            self.n = 0
    
        def __getitem__(self, index: int):
            return (1, 2, 3)[index]
    
        def __next__(self):
            n = self.n = self.n + 1
            if n > 3:
                raise StopIteration
            return n
    
    # if you can iter it without raising a TypeError, then it's an iterable.
    iter(MyIterable())
    
    # but obviously `MyIterable()` is not an iterator since it does not have
    # an `__iter__` method.
    from collections.abc import Iterator
    assert isinstance(MyIterable(), Iterator)  # AssertionError
AXO
fonte
1
No teste, eu entendi apenas o primeiro ponto. ou seja, o iterador se torna iterável, pois possui __iter__método. Você pode elaborar os pontos 2 e 3 editando esta resposta
AnV
@AnV: Tanto quanto eu entendo: re 2 .: __iter__()retorna um iterador. Um gerador é um iterador, portanto, pode ser usado para essa finalidade. re 3 .: Só posso adivinhar aqui, mas acho que se __iter__()está faltando ou não retorna self, não é um iterador, porque o iterador __iter__()precisa retornar self.
glglgl
10

Não sei se ajuda alguém, mas sempre gosto de visualizar conceitos na minha cabeça para melhor entendê-los. Então, como eu tenho um filho pequeno, visualizo o conceito iterável / iterador com tijolos e papel branco.

Suponha que estejamos no quarto escuro e no chão tenhamos tijolos para o meu filho. Tijolos de diferentes tamanhos, cores, não importam agora. Suponha que tenhamos 5 tijolos como esses. Esses 5 tijolos podem ser descritos como um objeto - digamos, kit de tijolos . Podemos fazer muitas coisas com este kit de tijolos - podemos pegar um e depois o segundo e o terceiro, trocar de lugar dos tijolos, colocar o primeiro tijolo acima do segundo. Podemos fazer muitos tipos de coisas com elas. Portanto, este kit de tijolos é um objeto ou sequência iterável , pois podemos percorrer cada tijolo e fazer algo com ele. Só podemos fazer isso como meu filho pequeno - podemos brincar com um tijolo de cada vez, . Então, novamente, eu me imagino este kit de tijolos para ser umiterável .

Agora lembre-se de que estamos no quarto escuro. Ou quase escuro. O fato é que não vemos claramente esses tijolos, que cor são, que forma etc. Portanto, mesmo se queremos fazer algo com eles - também conhecido como iterar através deles - não sabemos realmente o que e como, porque é muito escuro.

O que podemos fazer é próximo ao primeiro tijolo - como elemento de um kit de tijolos -, podemos colocar um pedaço de papel branco fluorescente para vermos onde está o primeiro elemento de tijolo. E cada vez que tiramos um tijolo de um kit, substituímos o pedaço de papel branco por outro próximo para poder vê-lo no quarto escuro. Este pedaço de papel branco não passa de um iterador . É um objeto também . Mas um objeto com o que podemos trabalhar e brincar com elementos do nosso iterável objeto - kit de tijolos.

A propósito, isso explica meu erro inicial quando tentei o seguinte em um IDLE e obtive um TypeError:

 >>> X = [1,2,3,4,5]
 >>> next(X)
 Traceback (most recent call last):
    File "<pyshell#19>", line 1, in <module>
      next(X)
 TypeError: 'list' object is not an iterator

A lista X aqui era o nosso kit de tijolos, mas NÃO era um pedaço de papel branco. Eu precisava encontrar um iterador primeiro:

>>> X = [1,2,3,4,5]
>>> bricks_kit = [1,2,3,4,5]
>>> white_piece_of_paper = iter(bricks_kit)
>>> next(white_piece_of_paper)
1
>>> next(white_piece_of_paper)
2
>>>

Não sei se ajuda, mas me ajudou. Se alguém pudesse confirmar / corrigir a visualização do conceito, ficaria agradecido. Isso me ajudaria a aprender mais.

Nikolay Dudaev
fonte
6

Iterável : - algo que é iterável é iterável; como seqüências como listas, strings, etc. Também possui o __getitem__método ou um __iter__método. Agora, se usarmos a iter()função nesse objeto, obteremos um iterador.

Iterador : - Quando obtemos o objeto iterador da iter()função; chamamos __next__()método (em python3) ou simplesmente next()(em python2) para obter os elementos um a um. Essa classe ou instância dessa classe é chamada de iterador.

Dos documentos: -

O uso de iteradores permeia e unifica o Python. Nos bastidores, a instrução for chama  iter() o objeto contêiner. A função retorna um objeto iterador que define o método  __next__() que acessa elementos no contêiner, um de cada vez. Quando não há mais elementos,  __next__() gera uma exceção StopIteration que informa ao loop for para terminar. Você pode chamar o  __next__() método usando a  next() função interna; este exemplo mostra como tudo funciona:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    next(it)
StopIteration

Ex de uma classe: -

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)
    def __iter__(self):
        return self
    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]


>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
...     print(char)
...
m
a
p
s
Vicrobot
fonte
4

Eu não acho que você pode simplificar muito mais que a documentação , no entanto, tentarei:

  • Iterável é algo que pode ser repetido . Na prática, geralmente significa uma sequência, por exemplo, algo que tem um começo e um fim e uma maneira de percorrer todos os itens.
  • Você pode pensar no Iterator como um pseudo-método auxiliar (ou pseudo-atributo) que fornece (ou retém) o próximo (ou primeiro) item no iterável . (Na prática, é apenas um objeto que define o método next())

  • A iteração é provavelmente melhor explicada pela definição Merriam-Webster da palavra :

b: a repetição de uma sequência de instruções do computador um número especificado de vezes ou até que uma condição seja atendida - compare a recursão

Kimvais
fonte
3
iterable = [1, 2] 

iterator = iter(iterable)

print(iterator.__next__())   

print(iterator.__next__())   

tão,

  1. iterableé um objeto que pode ser repetido . por exemplo, lista, string, tupla etc.

  2. usar a iterfunção em nosso iterableobjeto retornará um objeto iterador.

  3. agora esse objeto iterador possui um método chamado __next__(no Python 3 ou apenas nextno Python 2) pelo qual você pode acessar cada elemento do iterável.

portanto, a SAÍDA DO CÓDIGO ACIMA SERÁ:

1

2

arpan kumar
fonte
3

Os iteráveis têm um __iter__método que instancia um novo iterador sempre.

Os iteradores implementam um __next__método que retorna itens individuais e um __iter__método que retorna self.

Portanto, os iteradores também são iteráveis, mas iterables não são iteradores.

Luciano Ramalho, Python Fluente.

techkuz
fonte
2

Antes de lidar com os iteráveis ​​e o iterador, o principal fator que decide o iterável e o iterador é a sequência

Sequência: Sequência é a coleta de dados

Iterável: Iterável é o objeto do tipo sequência que suporta o __iter__método.

Método Iter: o método Iter toma a sequência como uma entrada e cria um objeto que é conhecido como iterador

Iterador: Iterador é o objeto que chama o próximo método e transversal através da sequência. Ao chamar o próximo método, ele retorna o objeto que ele atravessou atualmente.

exemplo:

x=[1,2,3,4]

x é uma sequência que consiste na coleta de dados

y=iter(x)

Ao chamar, iter(x)ele retorna um iterador apenas quando o objeto x tem o método iter, caso contrário, gera uma exceção.Se ele retorna o iterador, y é atribuído assim:

y=[1,2,3,4]

Como y é um iterador, portanto, ele suporta next() método

Ao chamar o próximo método, ele retorna os elementos individuais da lista, um por um.

Depois de retornar o último elemento da sequência, se chamarmos novamente o próximo método, gera um erro StopIteration

exemplo:

>>> y.next()
1
>>> y.next()
2
>>> y.next()
3
>>> y.next()
4
>>> y.next()
StopIteration
Sombra
fonte
Apenas uma observação: y = iter (x) não é exatamente y = [1,2,3,4], pois y agora é um objeto iterador. Talvez você deva adicionar um comentário para esclarecer que não é uma lista, mas um objeto iterador ou alterar a representação.
coelhudo
-6

No Python, tudo é um objeto. Quando se diz que um objeto é iterável, significa que você pode percorrer (ou seja, iterar) o objeto como uma coleção.

Matrizes, por exemplo, são iteráveis. Você pode percorrê-los com um loop for e passar do índice 0 ao índice n, sendo n o comprimento do objeto da matriz menos 1.

Dicionários (pares de chave / valor, também chamados de matrizes associativas) também são iteráveis. Você pode percorrer as chaves deles.

Obviamente, os objetos que não são coleções não são iteráveis. Um objeto bool, por exemplo, tem apenas um valor, Verdadeiro ou Falso. Não é iterável (não faria sentido que seja um objeto iterável).

Consulte Mais informação. http://www.lepus.org.uk/ref/companion/Iterator.xml

user93097373
fonte
6
objetos que não são coleções não são iteráveis, geralmente não é verdade. Para dar apenas alguns exemplos, os geradores são iteráveis, mas não são coleções, e os objetos iteradores criados chamando iter()os tipos de coleção padrão são iteráveis, mas não são coleções em si.
Mark Amery