Itere como um Profissional

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Neste laboratório, você aprenderá sobre o conceito fundamental de iteração na programação Python. A iteração permite que você processe eficientemente elementos em sequências como listas, tuplas e dicionários. Dominar as técnicas de iteração pode aprimorar significativamente suas habilidades de codificação em Python.

Você explorará várias técnicas de iteração Python poderosas, incluindo a iteração básica com o loop for, desempacotamento de sequências, o uso de funções embutidas como enumerate() e zip(), e o aproveitamento de expressões geradoras para melhor eficiência de memória.

Este é um Lab Guiado, que fornece instruções passo a passo para ajudá-lo a aprender e praticar. Siga as instruções cuidadosamente para completar cada etapa e ganhar experiência prática. Dados históricos mostram que este é um laboratório de nível iniciante com uma taxa de conclusão de 96%. Recebeu uma taxa de avaliações positivas de 100% dos estudantes.

Iteração Básica e Desempacotamento de Sequências

Nesta etapa, exploraremos a iteração básica usando loops for e o desempacotamento de sequências em Python. A iteração é um conceito fundamental na programação, permitindo que você percorra cada item em uma sequência, um por um. O desempacotamento de sequências, por outro lado, permite que você atribua elementos individuais de uma sequência a variáveis de forma conveniente.

Carregando Dados de um Arquivo CSV

Vamos começar carregando alguns dados de um arquivo CSV. CSV (Valores Separados por Vírgula) é um formato de arquivo comum usado para armazenar dados tabulares. Para começar, precisamos abrir um terminal no WebIDE e iniciar o interpretador Python. Isso nos permitirá executar o código Python interativamente.

cd ~/project
python3

Agora que estamos no interpretador Python, podemos executar o seguinte código Python para ler dados do arquivo portfolio.csv. Primeiro, importamos o módulo csv, que fornece funcionalidade para trabalhar com arquivos CSV. Em seguida, abrimos o arquivo e criamos um objeto csv.reader para ler os dados. Usamos a função next para obter os cabeçalhos das colunas e convertemos os dados restantes em uma lista. Finalmente, usamos a função pprint do módulo pprint para imprimir as linhas em um formato mais legível.

import csv

f = open('portfolio.csv')
f_csv = csv.reader(f)
headers = next(f_csv)    ## Get the column headers
rows = list(f_csv)       ## Convert the remaining data to a list
from pprint import pprint
pprint(rows)             ## Pretty print the rows

Você deve ver uma saída semelhante a esta:

[['AA', '100', '32.20'],
 ['IBM', '50', '91.10'],
 ['CAT', '150', '83.44'],
 ['MSFT', '200', '51.23'],
 ['GE', '95', '40.37'],
 ['MSFT', '50', '65.10'],
 ['IBM', '100', '70.44']]

Iteração Básica com Loops for

A instrução for em Python é usada para iterar sobre qualquer sequência de dados, como uma lista, tupla ou string. Em nosso caso, usaremos para iterar sobre as linhas de dados que carregamos do arquivo CSV.

for row in rows:
    print(row)

Este código percorrerá cada linha na lista rows e a imprimirá. Você verá cada linha de dados do nosso arquivo CSV impressa uma por uma.

['AA', '100', '32.20']
['IBM', '50', '91.10']
['CAT', '150', '83.44']
['MSFT', '200', '51.23']
['GE', '95', '40.37']
['MSFT', '50', '65.10']
['IBM', '100', '70.44']

Desempacotamento de Sequências em Loops

Python permite que você desempacote sequências diretamente em um loop for. Isso é muito útil quando você conhece a estrutura de cada item na sequência. Em nosso caso, cada linha na lista rows contém três elementos: um nome, o número de ações e o preço. Podemos desempacotar esses elementos diretamente no loop for.

for name, shares, price in rows:
    print(name, shares, price)

Este código desempacotará cada linha nas variáveis name, shares e price e, em seguida, as imprimirá. Você verá os dados impressos em um formato mais legível.

AA 100 32.20
IBM 50 91.10
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.10
IBM 100 70.44

Se você não precisar de alguns valores, pode usar _ como um espaço reservado para indicar que você não se importa com esses valores. Por exemplo, se você quiser imprimir apenas o nome e o preço, pode usar o seguinte código:

for name, _, price in rows:
    print(name, price)

Este código ignorará o segundo elemento em cada linha e imprimirá apenas o nome e o preço.

AA 32.20
IBM 91.10
CAT 83.44
MSFT 51.23
GE 40.37
MSFT 65.10
IBM 70.44

Desempacotamento Estendido com o Operador *

Para um desempacotamento mais avançado, você pode usar o operador * como um curinga. Isso permite que você colete vários elementos em uma lista. Vamos agrupar nossos dados por nome usando esta técnica.

from collections import defaultdict

byname = defaultdict(list)
for name, *data in rows:
    byname[name].append(data)

## Print the data for IBM
print(byname['IBM'])

## Iterate through IBM's data
for shares, price in byname['IBM']:
    print(shares, price)

Neste código, primeiro importamos a classe defaultdict do módulo collections. Um defaultdict é um dicionário que cria automaticamente um novo valor (neste caso, uma lista vazia) se a chave não existir. Em seguida, usamos o operador * para coletar todos os elementos, exceto o primeiro, em uma lista chamada data. Armazenamos esta lista no dicionário byname, agrupada por nome. Finalmente, imprimimos os dados para IBM e iteramos sobre eles para imprimir as ações e o preço.

Saída:

[['50', '91.10'], ['100', '70.44']]
50 91.10
100 70.44

Neste exemplo, *data coleta todos os itens, exceto o primeiro, em uma lista, que então armazenamos em um dicionário agrupado por nome. Esta é uma técnica poderosa para lidar com dados com sequências de comprimento variável.

Usando as Funções enumerate() e zip()

Nesta etapa, vamos explorar duas funções embutidas incrivelmente úteis em Python que são essenciais para a iteração: enumerate() e zip(). Essas funções podem simplificar significativamente seu código quando você estiver trabalhando com sequências.

Contando com enumerate()

Ao iterar sobre uma sequência, você pode frequentemente precisar acompanhar o índice ou a posição de cada item. É aí que a função enumerate() é útil. A função enumerate() recebe uma sequência como entrada e retorna pares de (índice, valor) para cada item nessa sequência.

Se você esteve acompanhando no interpretador Python da etapa anterior, pode continuar usando-o. Caso contrário, inicie uma nova sessão. Veja como você pode configurar os dados se estiver começando do zero:

## Se você estiver iniciando uma nova sessão, recarregue os dados primeiro:
## import csv
## f = open('portfolio.csv')
## f_csv = csv.reader(f)
## headers = next(f_csv)
## rows = list(f_csv)

## Use enumerate para obter os números das linhas
for rowno, row in enumerate(rows):
    print(rowno, row)

Quando você executa o código acima, a função enumerate(rows) gerará pares de um índice (começando em 0) e a linha correspondente da sequência rows. O loop for então desempacota esses pares nas variáveis rowno e row, e nós os imprimimos.

Saída:

0 ['AA', '100', '32.20']
1 ['IBM', '50', '91.10']
2 ['CAT', '150', '83.44']
3 ['MSFT', '200', '51.23']
4 ['GE', '95', '40.37']
5 ['MSFT', '50', '65.10']
6 ['IBM', '100', '70.44']

Podemos tornar o código ainda mais legível combinando enumerate() com desempacotamento. O desempacotamento nos permite atribuir diretamente os elementos de uma sequência a variáveis individuais.

for rowno, (name, shares, price) in enumerate(rows):
    print(rowno, name, shares, price)

Neste código, estamos usando um par extra de parênteses em torno de (name, shares, price) para desempacotar corretamente os dados da linha. O enumerate(rows) ainda nos dá o índice e a linha, mas agora estamos desempacotando a linha nas variáveis name, shares e price.

Saída:

0 AA 100 32.20
1 IBM 50 91.10
2 CAT 150 83.44
3 MSFT 200 51.23
4 GE 95 40.37
5 MSFT 50 65.10
6 IBM 100 70.44

Emparelhando Dados com zip()

A função zip() é outra ferramenta poderosa em Python. Ela é usada para combinar elementos correspondentes de múltiplas sequências. Quando você passa múltiplas sequências para zip(), ela cria um iterador que produz tuplas, onde cada tupla contém elementos de cada uma das sequências de entrada na mesma posição.

Vamos ver como podemos usar zip() com os dados headers e row com os quais temos trabalhado.

## Lembre-se da variável headers de antes
print(headers)  ## Deve mostrar ['name', 'shares', 'price']

## Obtenha a primeira linha
row = rows[0]
print(row)      ## Deve mostrar ['AA', '100', '32.20']

## Use zip para emparelhar nomes de colunas com valores
for col, val in zip(headers, row):
    print(col, val)

Neste código, zip(headers, row) pega a sequência headers e a sequência row e emparelha seus elementos correspondentes. O loop for então desempacota esses pares em col (para o nome da coluna de headers) e val (para o valor de row), e nós os imprimimos.

Saída:

['name', 'shares', 'price']
['AA', '100', '32.20']
name AA
shares 100
price 32.20

Um uso muito comum de zip() é criar dicionários a partir de pares chave-valor. Em Python, um dicionário é uma coleção de pares chave-valor.

## Crie um dicionário a partir de cabeçalhos e valores de linha
record = dict(zip(headers, row))
print(record)

Aqui, zip(headers, row) cria pares de nomes de colunas e valores, e a função dict() pega esses pares e os transforma em um dicionário.

Saída:

{'name': 'AA', 'shares': '100', 'price': '32.20'}

Podemos estender essa ideia para converter todas as linhas em nossa sequência rows em dicionários.

## Converta todas as linhas em dicionários
for row in rows:
    record = dict(zip(headers, row))
    print(record)

Neste loop, para cada linha em rows, usamos zip(headers, row) para criar pares chave-valor e, em seguida, dict() para transformar esses pares em um dicionário. Essa técnica é muito comum em aplicações de processamento de dados, especialmente ao trabalhar com arquivos CSV, onde a primeira linha contém cabeçalhos.

Saída:

{'name': 'AA', 'shares': '100', 'price': '32.20'}
{'name': 'IBM', 'shares': '50', 'price': '91.10'}
{'name': 'CAT', 'shares': '150', 'price': '83.44'}
{'name': 'MSFT', 'shares': '200', 'price': '51.23'}
{'name': 'GE', 'shares': '95', 'price': '40.37'}
{'name': 'MSFT', 'shares': '50', 'price': '65.10'}
{'name': 'IBM', 'shares': '100', 'price': '70.44'}

Expressões Geradoras e Eficiência de Memória

Nesta etapa, vamos explorar as expressões geradoras. Elas são incrivelmente úteis quando você está lidando com grandes conjuntos de dados em Python. Elas podem tornar seu código muito mais eficiente em termos de memória, o que é crucial quando você está trabalhando com uma grande quantidade de dados.

Compreendendo as Expressões Geradoras

Uma expressão geradora é semelhante a uma list comprehension, mas há uma diferença fundamental. Quando você usa uma list comprehension, o Python cria uma lista com todos os resultados de uma vez. Isso pode consumir muita memória, especialmente se você estiver trabalhando com um grande conjunto de dados. Por outro lado, uma expressão geradora produz resultados um de cada vez, conforme necessário. Isso significa que ela não precisa armazenar todos os resultados na memória de uma vez, o que pode economizar uma quantidade significativa de memória.

Vamos analisar um exemplo simples para ver como isso funciona:

## Inicie uma nova sessão Python, se necessário
## python3

## List comprehension (cria uma lista na memória)
nums = [1, 2, 3, 4, 5]
squares_list = [x*x for x in nums]
print(squares_list)

## Expressão geradora (cria um objeto gerador)
squares_gen = (x*x for x in nums)
print(squares_gen)  ## Isso não imprime os valores, apenas o objeto gerador

## Itere pelo gerador para obter valores
for n in squares_gen:
    print(n)

Quando você executa este código, verá a seguinte saída:

[1, 4, 9, 16, 25]
<generator object <genexpr> at 0x7f...>
1
4
9
16
25

Uma coisa importante a observar sobre os geradores é que eles só podem ser iterados uma vez. Depois de percorrer todos os valores em um gerador, ele é esgotado e você não pode obter os valores novamente.

## Tente iterar novamente sobre o mesmo gerador
for n in squares_gen:
    print(n)  ## Nada será impresso, pois o gerador já está esgotado

Você também pode obter valores manualmente de um gerador, um de cada vez, usando a função next().

## Crie um novo gerador
squares_gen = (x*x for x in nums)

## Obtenha valores um por um
print(next(squares_gen))  ## 1
print(next(squares_gen))  ## 4
print(next(squares_gen))  ## 9

Quando não houver mais valores no gerador, chamar next() levantará uma exceção StopIteration.

Funções Geradoras com yield

Para uma lógica de gerador mais complexa, você pode escrever funções geradoras usando a instrução yield. Uma função geradora é um tipo especial de função que usa yield para retornar valores um de cada vez, em vez de retornar um único resultado de uma vez.

def squares(nums):
    for x in nums:
        yield x*x

## Use a função geradora
for n in squares(nums):
    print(n)

Quando você executa este código, verá a seguinte saída:

1
4
9
16
25

Reduzindo o Uso de Memória com Expressões Geradoras

Agora, vamos ver como as expressões geradoras podem economizar memória ao trabalhar com grandes conjuntos de dados. Usaremos o arquivo de dados do ônibus CTA, que é bastante grande.

cd /home/labex/project
unzip ctabus.csv.zip && rm ctabus.csv.zip

Primeiro, vamos tentar uma abordagem intensiva em memória:

import tracemalloc
tracemalloc.start()

import readrides
rows = readrides.read_rides_as_dicts('ctabus.csv')
rt22 = [row for row in rows if row['route'] == '22']
max_row = max(rt22, key=lambda row: int(row['rides']))
print(max_row)

## Verifique o uso de memória
current, peak = tracemalloc.get_traced_memory()
print(f"Current memory usage: {current / 1024 / 1024:.2f} MB")
print(f"Peak memory usage: {peak / 1024 / 1024:.2f} MB")

Agora, saia do Python e reinicie-o para comparar com uma abordagem baseada em geradores:

exit() python3
import tracemalloc
tracemalloc.start()

import csv
f = open('ctabus.csv')
f_csv = csv.reader(f)
headers = next(f_csv)

## Use expressões geradoras para todas as operações
rows = (dict(zip(headers, row)) for row in f_csv)
rt22 = (row for row in rows if row['route'] == '22')
max_row = max(rt22, key=lambda row: int(row['rides']))
print(max_row)

## Verifique o uso de memória
current, peak = tracemalloc.get_traced_memory()
print(f"Current memory usage: {current / 1024 / 1024:.2f} MB")
print(f"Peak memory usage: {peak / 1024 / 1024:.2f} MB")

Você deve notar uma diferença significativa no uso de memória entre essas duas abordagens. A abordagem baseada em geradores processa os dados incrementalmente, sem carregar tudo na memória de uma vez, o que é muito mais eficiente em termos de memória.

Expressões Geradoras com Funções de Redução

As expressões geradoras são particularmente úteis quando combinadas com funções como sum(), min(), max(), any() e all() que processam uma sequência inteira e produzem um único resultado.

Vamos analisar alguns exemplos:

from readport import read_portfolio
portfolio = read_portfolio('portfolio.csv')

## Calcule o valor total da carteira
total_value = sum(s['shares']*s['price'] for s in portfolio)
print(f"Total portfolio value: {total_value}")

## Encontre o número mínimo de ações em qualquer participação
min_shares = min(s['shares'] for s in portfolio)
print(f"Minimum shares in any holding: {min_shares}")

## Verifique se alguma ação é IBM
has_ibm = any(s['name'] == 'IBM' for s in portfolio)
print(f"Portfolio contains IBM: {has_ibm}")

## Verifique se todas as ações são IBM
all_ibm = all(s['name'] == 'IBM' for s in portfolio)
print(f"All stocks are IBM: {all_ibm}")

## Conte as ações da IBM
ibm_shares = sum(s['shares'] for s in portfolio if s['name'] == 'IBM')
print(f"Total IBM shares: {ibm_shares}")

As expressões geradoras também são úteis para operações de string. Veja como unir valores em uma tupla:

s = ('GOOG', 100, 490.10)
## Isso falharia: ','.join(s)
## Use uma expressão geradora para converter todos os itens em strings
joined = ','.join(str(x) for x in s)
print(joined)  ## Saída: 'GOOG,100,490.1'

A principal vantagem de usar expressões geradoras nesses exemplos é que nenhuma lista intermediária é criada, resultando em um código mais eficiente em termos de memória.

Resumo

Neste laboratório, você aprendeu várias técnicas poderosas de iteração em Python. Primeiro, você dominou a iteração básica e o desempacotamento de sequências, usando loops for para iterar sobre sequências e desempacotá-las em variáveis individuais. Em segundo lugar, você explorou funções embutidas como enumerate() para rastrear índices durante a iteração e zip() para emparelhar elementos de diferentes sequências.

Essas técnicas são fundamentais para uma programação Python eficiente. Elas permitem que você escreva um código mais conciso, legível e eficiente em termos de memória. Ao dominar esses padrões de iteração, você pode lidar com tarefas de processamento de dados de forma mais eficaz em seus projetos Python.