Iteráveis
Um iterável é qualquer objeto que pode ser percorrido, item por item, em um loop for ou utilizado em outras construções que requerem iteração, como map(), filter(), etc. A principal característica de um iterável é que ele implementa o método especial __iter__() ou o método __getitem__().
Nem todo iterável é uma coleção, como por exemplo geradores e arquivos. Quando uma coleção é Ordenável significa que mantém a ordem de inserção dos elementos, ou seja, ao iterar sobre, os itens aparecem na mesma ordem em que foram adicionados.
| Coleção | Ordenada | Iterável | Sequencial | Mutável |
|---|---|---|---|---|
| Listas | Sim | Sim | Sim | Sim |
| Tuplas | Sim | Sim | Sim | Não |
| Strings | Sim | Sim | Sim | Não |
| Conjuntos | Não | Sim | Não | Sim |
| Dicionários | Sim (a partir do Python 3.7) | Sim | Não | Sim |
✔ Iterável → Um objeto que pode ser percorrido (for x in obj:)
✔ Iterador → Um objeto/MECANISMO que armazena a posição atual e retorna elementos com next().
next()).__iter__() → podem gerar um iterador. Mas não têm __next__() → não percorrem sozinhos), a não ser que você crie um pelo metodo iter(nome_da_coleção), se isso for feito em uma lista, por exemplo, o metodo __iter__() dela, retorna um objeto iterador (no caso, um list_iterator). Mas quando percorre uma lista por um for, internamente é criado um iterador para saber o proximo item.Principais Diferenças
| Característica | Iterável | Iterador |
|---|---|---|
| Implementa | __iter__() ou __getitem__() (mais antigo) |
__iter__() e __next__() |
Pode ser usado em for? |
✅ Sim | ✅ Sim, se tiver o __iter__() |
Pode ser usado com next()? |
❌ Não | ✅ Sim |
| Retorna o próprio iterador? | ❌ Não (ele cria um novo) | ✅ Sim (__iter__() retorna self) |
| Armazena estado da iteração? | ❌ Não | ✅ Sim |
class Contador:
def __init__(self, tamanho):
self.cont = 0
self.tamanho = tamanho
def __iter__(self):
return self
def __next__(self):
if self.cont < self.tamanho:
self.cont += 1
return self.cont
else:
raise StopIteration
cont = Contador(3)
# chamada automatica do next
for i in cont:
print(i)
# chamada manual de next
print(cont.__next__()) # chamo diretamente
print(next(cont)) # o metodo built in chama internamente a mesmo metodo __next__ acima: def next(iterator): return iterator.__next__()
# Transformar uma lista em um iterador no Python é uma tarefa simples
numeros = [1, 2, 3, 4, 5]
num_iterador = iter(numeros) # Cria um iterador para percorrer os números
print(type(num_iterador)) # <class 'list_iterator'>, retorna uma instância de uma classe interna do Python, otimizada e já pronta para iterar sobre listas.
print(next(num_iterador)) # consumo a lista de forma sequencial
Criação de um objeto iterador proprio.
lst = [10, 20, 30]
it = iter(lst) # Aqui criei um iterador para a lista acima.
print(type(lst)) # <class 'list'>
print(type(it)) # <class 'list_iterator'>
# Internamente quando é feito um for em um iterável.
for x in [10, 20, 30]:
print(x)
# Quando é feito um for como acima, o Python internamente faz isso
_iter = iter([10, 20, 30]) # cria o list_iterator
while True:
try:
x = next(_iter) # pega o próximo item
except StopIteration:
break
print(x)
it não é uma copia da lista, é apenas um objeto (list_iterator - iterador especializado em percorrer listas) apontando para a lista e guardando o progresso.No segundo caso, quando o for termina (ou ocorre break), o iterador temporário é descartado (não é mais referenciado e o garbage collector pode limpar depois). A lista original nunca é alterada nesse processo.
Assuntos Relacionados
Sequências
Em Python, sequências (subtipo de iteráveis) são tipos de dados que mantêm a ordem de seus elementos e permitem acessar elementos por índice. Elas são um subconjunto das coleções e são projetadas para armazenar dados de forma ordenada.
Tipos de sequências em Python incluem:
- Listas (
list) - Tuplas (
tuple) - Strings (
str) range(embora não seja uma coleção tradicional, é considerado uma sequência)
Características das sequências:
- Ordenação: A ordem dos elementos é importante e preservada.
- Indexação: Você pode acessar elementos por índice (por exemplo,
lista[0]). - Imutabilidade/Mutabilidade: Algumas sequências são mutáveis (listas), enquanto outras são imutáveis (tuplas, strings).
Geradores
Um gerador é um tipo específico de iterável, criado para gerar itens "sob demanda". Ele é mais eficiente para grandes conjuntos de dados, pois só mantém um item na memória de cada vez (produzem os valores de forma preguiçosa - lazy evaluation). Esse itens são consumiveis, o que significa que, uma vez que você iterar sobre um item gerado, ele não estará mais disponível.
A palavra-chave yield transforma uma função em geradora. Quando uma função com yield é chamada, Python cria um objeto de gerador que salva:
- A posição atual no código (linha do último yield).
- O estado das variáveis locais (como em um ponto de salvamento).
Yield: palavra chave que a cada vez que é encontrada, a função pausa e salva o estado atual. (incluindo variáveis locais e a posição na função) "lembra de onde parou". para que ela possa continuar na próxima vez em que for chamada.
Cada vez que um gerador é chamado, ele gera um item de cada vez, até que não haja mais itens a serem produzidos. Assim, quando next() é chamado novamente, a execução continua exatamente do ponto em que parou. quando chamamos next e todos os elementos foram consumidos gera o erro StopIteration.
Expressão geradora é uma maneira concisa de criar geradores, semelhante a uma compreensão em lista. gera valores um de cada vez economizando memória.
def contagem():
print("Iniciando contagem")
yield 1 # Retorna o valor, mas ao contrario de return ela não encerra a função.
print("Contando 2")
yield 2
print("Contando 3")
yield 3
print("Contagem finalizada")
# Cria o gerador
contador = contagem()
# Usando o gerador com next()
print(next(contador)) # Saída: "Iniciando contagem" e "1"
print(next(contador)) # Saída: "Contando 2" e "2"
print(next(contador)) # Saída: "Contando 3" e "3"
# Se não tiver mais valor o valor padrão None é retornado para não gerar o erro StopIteration,
print(next(contador, None)) # Saída: "Contagem finalizada" e "None"
# O for internamente chama a função next() e já trata o erro StopIteration quando os iteráveis acabam
for numero in contagem():
print(numero)
def contador():
valor = 0
while True:
incremento = yield valor # Pausa e espera um valor externo
if incremento is None:
incremento = 1 # Se nada for enviado, assume 1
valor += incremento
# Criando o gerador
gen = contador()
# Iniciando o gerador (necessário antes de usar send)
print(next(gen)) # Saída: 0
# Enviando valores para o gerador
print(gen.send(3)) # Saída: 3 (0 + 3)
print(gen.send(2)) # Saída: 5 (3 + 2)
print(gen.send(5)) # Saída: 10 (5 + 5)
print(next(gen)) # Saída: 11 (10 + 1, pois None vira 1)
O método .send() é usado para enviar valores para um gerador que contém yield. Isso permite comunicação bidirecional entre o gerador e quem está chamando. Nesse caso o gerador começa em 0 (next(gen)).A cada .send(valor), o gerador recebe um número e soma ao valor atual. Se None for enviado, ele soma 1 por padrão. yield pausa e retorna o valor, esperando o próximo .send().
| Expressão Geradora vs Compreensão em Lista
# Cria um gerador, observe o uso de parênteses ao invés de colchetes
quadrados = (x ** 2 for x in range(5))
# ela salva o objeto onde parou
print(next(quadrados)) # Saída: 0
print(next(quadrados)) # Saída: 1
print(next(quadrados)) # Saída: 4
# Compreensão em lista. Salva todos os valores na memoria
quadrados = [x ** 2 for x in range(5)]
Desempacotamento (Unpacking)
O desempacotamento (ou unpacking) é uma técnica poderosa que permite extrair os valores de um iterável (como listas, tuplas ou dicionários) e atribuí-los diretamente a variáveis de forma concisa.
starred variable (variável estrelada) é uma variável de desempacotamento com asterisco. O operador * é usado para agrupar múltiplos valores em uma lista. Esse recurso é útil quando você quer capturar uma parte específica da lista e agrupar o restante dos itens em uma única variável.
# desempacotamento de tupla
tupla = (1, 2, 3)
a, b, c = tupla
# Itera sobre as variáveis descompactada anteriormente
for valor in [a, b, c]:
print(valor)
dic = {
'nome': 'Diogo',
'idade': '28'
}
# Descompacotamento em dicionário. Duas Strings (chave e valor)
for chave, valor in dic.items():
print(chave, valor)
nomes = ['Diogo', 'Roberto', 'Rodrigo']
idades = [20, 30, 19]
# desempacotamento aninhado (nesting)
for i, (nome, idade) in enumerate(zip(nomes, idades)):
print(i, nome, idade)
# Saída:
# 0 Diogo 20
# 1 Roberto 30
# 2 Rodrigo 19
# Sem desempacotamento
for e in enumerate(zip(nomes,idades)):
print(e) # Saída: (0, ('Diogo', 20)) (1, ('Roberto', 30)) (2, ('Rodrigo', 19))
minha_lista = [1, 2, 3, 4, 5, 6]
# o asterisco retorna uma lista, abaixo, o meio é uma lista com valor 3 e 4
primeiro, segundo, *meio, penultimo, ultimo = minha_lista # saída: 1 2 [3, 4] 5 6
# Primeiro e ultimo da lista uso *_ por convenção
primeiro, *_, ultimo = minha_lista # Saída (se imprimir sem o _): 1 6. Saída (com _ ): 1 [2, 3, 4, 5] 6
# Se não sobrar valores para preencher, ele retorna uma lista vazia
minha_lista_b = [1, 2]
num_primeiro, *metade, num_ultimo = minha_lista_b # Saída ao imprimir: 1 [] 2
Exemplos com Starred Variable.
Range
O range é um tipo embutido em Python (uma classe) usado para gerar uma sequência de números inteiros. Não é um iterador mas pode ser iterado (é um iterável), e é muito eficiente pois não armazena todos os números na memória (lazy), ele gera cada número apenas quando necessário. Por isso é comumente usado em laços for. Também é random-access, ou seja, pode acessar qualquer posição sem precisar consumir do início, o que um gerador não faz.
Sintaxe:
range(start, stop, step)
start(opcional): O valor inicial da sequência (padrão é 0).stop(obrigatório): O valor final, não incluso na sequência.step(opcional): O intervalo entre os números (padrão é 1).
r = range(1, 10, 2)
print(r[0]) # Saída: 1
print(r[2]) # Saída: 5
r = range(10**12) # É lazy, não ocupa memória de 1 trilhão de números!
# Diferenças de um range para um gerador
print(r[999_999_999]) # acesso direto, funciona
print(len(r)) # também funciona
Listas
Coleções ordenadas (sequênciais) que podem ser alteradas (mútaveis).
É possível obter o último item da lista com números negativos (o último elemento tem índice -1, o penúltimo tem índice -2 e etc… )
numeros = [10, 20, 30, 40]
# Imprime uma lista completamente
print(numeros)
# Acessa o primeiro elemento da lista
print(numeros[0])
# imprime o primeiro caracter da String ja que uma string é tratada como uma lista em python
print("Ola"[0])
cidades = ["Palmas", "Chicago", "LEM"]
# Retorna LEM, que é o ultimo item da lista
print(cidades[-1])
# Soma de Listas (+=)
l_a = [1, 2, 3]
l_b = [4, 5, 6]
l_a+=l_b
print(l_a) # Saída
numeros = [10, 20, 30, 40]
numeros2 = [50,60,]
# Adiciona um novo elemento à lista
numeros.append(50)
# Adiciona uma lista a outra lista
numeros.extend(numeros2)
# Remove o último elemento
numeros.pop()
# Limpa a lista
numeros2.clear()
# reordena a lista sortida em ordem crescente
numeros.sort()
# inverte a ordem da lista
numeros.reverse()
lista = ["Banana","Laranja","Maça","Limão" ]
# Retorna o índice que este item está na lista (primeira ocorrência), também presente na tupla e string.
lista.index("Maça")
# Copia a lista, assim evita modificar a lista original (como ao atribuir com =, pois é uma referencia)
copia_lista = lista.copy()
Os principais métodos de uma lista
Tuplas
Coleção ordenada (sequência) imútaveis. Atribuir valor à tupla vai gerar um erro (tupla[0] = 10).
Obs.: Sempre que criar uma tupla com um único elemento, adicione uma vírgula ao final para evitar que o python interprete como uma tupla e não como um valor isolado.
ponto = (4, 5)
# Acessa o primeiro elemento da tupla
print(ponto[0])
# Desempacotando uma tupla
a, b = (1, 2)
# Criar tupla de um único elemento
tupla_real = ("Diogo",)
Dicionários
Dicionários são coleções de pares chave-valor. São úteis para armazenar dados associativos.
# Define um dicionário vazio, ou limpa/zera um dicionário
pessoa = {}
# Define um dicionário
pessoa = {
"nome": "João",
"idade": 25,
}
# imprime o dicionário completo com chave e valor
print(pessoas)
# Acessa o valor associado à chave "nome"
print(pessoa["nome"])
# Adiciona um novo elemento/par chave-valor
pessoa["cidade"] = "São Paulo"
pessoa = {
"nome": "João",
"idade": 25,
}
# recupera o valor de uma chave, equivalente ao uso dos colchetes, mas se a chave não existir retorna None
pessoa.get("nome")
# mostra todas as chaves do dicionário, retorna um dict_keys
pessoa.keys()
# mostra todos os valores, retorna um dict_values
pessoa.values()
# retorna uma lista de tuplas, com chave e valor, um dict_items
pessoa.items()
# Atualiza um valor ou adiciona valores ao dicionário
pessoa.update({"nome":"pedro", "sobrenome": "Carmelo"}) # atualiza o nome e adiciona o sobrenome
pessoa.update(altura=1.75, profissao="dentista") # pode ser atualizado com argumentos nomeados também
# remove item do dicionário
pessoa.pop("altura", "not found") # retorna a chave removida, pode personalizar o retorno se não a chave não existir (2º argumento)
del pessoa["altura"] # Não retorna nada, e não tem personalização de retorno
# remove todos os itens
pessoa.clear()
# Exclui o dicionário inteiro
del pessoa
Os principais métodos de um dicionário.
Conjuntos (Sets)
Conjuntos são mutáveis e não ordenados (pois é possivel adicionar e excluir elementos), mas seus elementos precisam ser Imutáveis, únicos (hashable) . Os conjuntos aceitam diversos tipos de dados simultaneamente!
Conjuntos não aceitam listas, dicionários e etc, como valor (pois são multaveis), mas podem aceitar tuplas (contanta que os elementos da tupla sejam imutáveis também).
O função built-in set(), recebe um iterável e retorna um conjunto com os elementos únicos do iterável. Bastante usado para tirar valores repetidos de uma lista.
Os conjuntos tem melhor performance de execução do que listas.
# cria um conjunto
conjunto = {
1, 2, 3, 4,
}
# Conjunto com vários tipos de dados
conjuntoEcletico = {
1, 'Diogo', 2.5, True
}
print(conjunto)
conjunto = {
1, 2, 3, 4,
}
# Adiciona um elemento ao conjunto
conjunto.add(5)
# Remove um elemento do conjunto
conjunto.remove(3)
# Adiciona vários elementos de uma vez
conjunto.update([3, 4, 5]) # adiciona só o 5, pois já tem o 3 e o 4.
# Cria um conjunto vazio
conjunto2 = set()
lista = [3, 4, 5]
# converte uma lista em um conjunto
conjunto2 = set(lista)
# Adiciona valores (ordena automático) e não adiciona valores repetidos
conjunto2.add(6)
# União
conjunto.union(conjunto2)
# União com operador
uniao = conjunto1 | conjunto2
# Intersecção
conjunto.intersection(conjunto2)
# Intersecção com operador
interseccao = conjunto & conjunto2
# Diferença de conjuntos (mostra o que tem no conjunto a que não tem em conjunto2)
conjunto.difference(conjunto2)
# Diferença com operador
diferenca = conjunto - conjunto2
# diferença simétrica (mostra tudo o que não tem em comum entre os dois conjuntos)
conjunto.symmetric_difference(conjunto2)
# Diferença simétrica com operador
diferenca_simetrica = (conjunto-conjunto2) | (conjunto2-conjunto)
numeros = list(range(1_000))
%timeit 500 in numeros # Saída: 11.8 μs (microsegundos)
# Converte em conjunto
conjunto_velocidade = set(numeros)
%timeit 500 in conjunto_velocidade # Saída: 101 ns
Teste de velocidade em conjunto comparado à lista. Nesse teste, o conjunto foi cerca de 116.83 vezes mais rápido que a lista, ou seja, cerca de 1/117 do tempo da busca na lista
Aninhamento (Nesting)
O aninhamento é uma técnica essencial em programação que permite organizar fluxos de controle, dados ou elementos visuais de forma hierárquica.
O Aninhamento de Estruturas de Dados como listas, dicionários ou tuplas, podem ser aninhadas para representar informações complexas.
# Nesting em estrutura de dados
viagens_log = [
{
"pais": "Brasil",
"visitas": 10,
"cidades": ["Rio", "Sao Paulo", "Lem"]
},
{
"pais": "França",
"visitas": 3,
"cidades": ["Paris", "Lile"]
}]