Nas aulas anteriores, você aprendeu os fundamentos das listas em Python: como criar, acessar, modificar e iterar sobre elas. Agora é hora de elevar seu conhecimento com técnicas avançadas que tornam seu código mais expressivo, eficiente e seguro. Vamos explorar list comprehensions para construir listas de forma elegante, ordenar dados com critérios personalizados, entender a diferença entre cópia rasa e profunda para evitar efeitos colaterais inesperados, e trabalhar com listas aninhadas para representar estruturas mais complexas.

Esses tópicos são essenciais para qualquer programador Python que deseje escrever código profissional e de alto desempenho. Prepare-se para mergulhar em exemplos práticos e exercícios que consolidarão seu aprendizado.

List comprehensions

List comprehensions são uma forma concisa de criar listas aplicando uma expressão a cada item de uma sequência (ou iterável) e, opcionalmente, filtrando elementos com uma condição. Elas substituem loops for tradicionais com append em uma única linha, tornando o código mais legível e, muitas vezes, mais rápido.

A sintaxe básica é: [expressão for item in iterável if condição]. A expressão é avaliada para cada item que satisfizer a condição (se houver). Exemplos comuns incluem gerar quadrados, filtrar pares, ou transformar strings. List comprehensions são preferíveis quando a lógica é simples; para expressões complexas, um loop tradicional pode ser mais claro.

# Exemplo 1: quadrados dos números de 1 a 5
quadrados = [x**2 for x in range(1, 6)]
print(quadrados)  # [1, 4, 9, 16, 25]

# Exemplo 2: filtrar números pares
pares = [x for x in range(10) if x % 2 == 0]
print(pares)  # [0, 2, 4, 6, 8]

# Exemplo 3: converter temperaturas de Celsius para Fahrenheit
celsius = [0, 10, 20, 30]
fahrenheit = [(c * 9/5) + 32 for c in celsius]
print(fahrenheit)  # [32.0, 50.0, 68.0, 86.0]

# Exemplo 4: aninhamento (produto cartesiano)
pares = [(x, y) for x in [1,2,3] for y in [4,5,6]]
print(pares)  # [(1,4), (1,5), (1,6), (2,4), (2,5), (2,6), (3,4), (3,5), (3,6)]

Observação: embora seja possível criar list comprehensions com múltiplos níveis de aninhamento, evite exageros para manter a legibilidade. Para casos complexos, considere usar funções auxiliares ou loops tradicionais.

Ordenação (sort, sorted)

Python oferece duas formas principais de ordenar listas: o método .sort(), que modifica a lista original (in-place) e retorna None, e a função sorted(), que retorna uma nova lista ordenada sem alterar a original. Ambos aceitam os parâmetros opcionais key e reverse. O parâmetro key especifica uma função que extrai um valor de comparação de cada elemento; reverse=True ordena em ordem decrescente.

É importante entender a diferença entre sort e sorted para evitar modificações indesejadas. Além disso, o parâmetro key é extremamente poderoso para ordenar por critérios personalizados, como o comprimento de strings, um atributo de objeto, ou uma chave de dicionário.

# Exemplo 1: ordenação básica
numeros = [3, 1, 4, 1, 5, 9, 2, 6]
numeros.sort()
print(numeros)  # [1, 1, 2, 3, 4, 5, 6, 9]

# Exemplo 2: sorted() preserva a lista original
original = [3, 1, 4]
ordenada = sorted(original)
print(original)   # [3, 1, 4]
print(ordenada)   # [1, 3, 4]

# Exemplo 3: ordenação decrescente
numeros = [3, 1, 4, 1, 5]
numeros.sort(reverse=True)
print(numeros)  # [5, 4, 3, 1, 1]

# Exemplo 4: usando key (ordenar strings pelo comprimento)
palavras = ['banana', 'maçã', 'abacaxi', 'uva']
palavras.sort(key=len)
print(palavras)  # ['uva', 'maçã', 'banana', 'abacaxi']

# Exemplo 5: ordenar lista de tuplas pelo segundo elemento
pessoas = [('Ana', 25), ('João', 20), ('Maria', 30)]
pessoas.sort(key=lambda p: p[1])
print(pessoas)  # [('João', 20), ('Ana', 25), ('Maria', 30)]

# Exemplo 6: sorted() com key e reverse simultâneos
notas = [('João', 8), ('Ana', 9), ('Pedro', 7)]
notas_ordenadas = sorted(notas, key=lambda x: x[1], reverse=True)
print(notas_ordenadas)  # [('Ana', 9), ('João', 8), ('Pedro', 7)]

Cópia (rasa vs profunda)

Ao copiar listas em Python, é crucial entender a diferença entre cópia rasa (shallow copy) e cópia profunda (deep copy). Uma cópia rasa cria um novo objeto de lista, mas os elementos dentro dela são referências aos mesmos objetos da lista original. Isso significa que, se os elementos forem mutáveis (como listas internas, dicionários, etc.), modificações neles afetarão ambas as listas. Já uma cópia profunda recursivamente copia todos os objetos aninhados, criando objetos independentes.

Para cópias rasas, você pode usar o método .copy(), a função list(), ou o fatiamento [:]. Para cópias profundas, use o módulo copy com copy.deepcopy(). A escolha correta evita bugs sutis quando você modifica listas aninhadas.

import copy

# Cópia rasa
original = [[1, 2], [3, 4]]
rasa = original.copy()
rasa[0][0] = 99
print(original)  # [[99, 2], [3, 4]]  # modificou!
print(rasa)      # [[99, 2], [3, 4]]

# Cópia profunda
profunda = copy.deepcopy(original)
profunda[0][0] = 100
print(original)   # [[99, 2], [3, 4]]  # não modificou
print(profunda)   # [[100, 2], [3, 4]]

# Outras formas de cópia rasa
rasa2 = list(original)
rasa3 = original[:]

Dicas: use cópia rasa quando os elementos são imutáveis (números, strings, tuplas) ou quando você não planeja modificar os elementos mutáveis internos. Para segurança total com estruturas aninhadas, prefira deepcopy.

Aninhamento

Listas podem conter outras listas como elementos, formando estruturas aninhadas (matrizes, tabelas, etc.). Para acessar elementos em listas aninhadas, usamos índices encadeados: matriz[linha][coluna]. A criação de listas aninhadas pode ser feita manualmente ou com list comprehensions aninhadas. É importante ter cuidado ao inicializar listas aninhadas para evitar compartilhamento de referências indesejado.

Um erro comum é usar [[]] * n para criar uma lista de listas, pois isso cria múltiplas referências para a mesma lista interna. Em vez disso, use uma list comprehension ou um loop.

# Criando uma matriz 3x3 manualmente
matriz = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]
print(matriz[1][2])  # 6

# Criando com list comprehension (identidade 4x4)
identidade = [[1 if i == j else 0 for j in range(4)] for i in range(4)]
print(identidade)
# [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]

# Erro comum: [[]]*n cria referências compartilhadas
ruim = [[0]] * 3
ruim[0][0] = 1
print(ruim)  # [[1], [1], [1]]  # todas as sublistas foram afetadas!

# Correto: usar list comprehension
bom = [[0] for _ in range(3)]
bom[0][0] = 1
print(bom)  # [[1], [0], [0]]

# Iterando sobre listas aninhadas
for linha in matriz:
    for elemento in linha:
        print(elemento, end=' ')
    print()

Boas práticas e observações finais

Ao trabalhar com listas avançadas, lembre-se de que a legibilidade é fundamental. Use list comprehensions apenas quando a lógica for simples e clara. Prefira sorted() quando precisar preservar a lista original. Sempre considere se uma cópia rasa é suficiente ou se uma cópia profunda é necessária para evitar efeitos colaterais. Para listas aninhadas, evite a multiplicação de listas e prefira inicializações explícitas.

Essas técnicas são amplamente utilizadas em processamento de dados, algoritmos e desenvolvimento web. Pratique com os exercícios abaixo para solidificar seu entendimento.

Referências

Exercícios

  1. Use uma list comprehension para gerar uma lista dos quadrados dos números pares de 1 a 20.

    ✓ Resposta:
    quadrados_pares = [x**2 for x in range(1, 21) if x % 2 == 0]
    print(quadrados_pares)  # [4, 16, 36, 64, 100, 144, 196, 256, 324, 400]
  2. Dada a lista de strings palavras = ['python', 'java', 'c', 'javascript', 'go'], ordene-a pelo comprimento das strings em ordem decrescente usando sorted().

    ✓ Resposta:
    palavras = ['python', 'java', 'c', 'javascript', 'go']
    ordenadas = sorted(palavras, key=len, reverse=True)
    print(ordenadas)  # ['javascript', 'python', 'java', 'go', 'c']
  3. Explique a diferença entre cópia rasa e cópia profunda. Dê um exemplo onde uma cópia rasa causa um efeito colateral indesejado.

    ✓ Resposta:

    Cópia rasa cria um novo objeto de lista, mas os elementos internos ainda são referências aos mesmos objetos. Já a cópia profunda cria cópias independentes de todos os objetos aninhados. Exemplo de efeito colateral:

    original = [[1, 2], [3, 4]]
    rasa = original.copy()
    rasa[0][0] = 99
    print(original)  # [[99, 2], [3, 4]]  # original foi alterado!
  4. Crie uma lista aninhada representando uma matriz 3x3 onde cada elemento é o produto do índice da linha pelo índice da coluna (ou seja, matriz[i][j] = i * j). Use uma list comprehension aninhada.

    ✓ Resposta:
    matriz = [[i * j for j in range(3)] for i in range(3)]
    print(matriz)  # [[0, 0, 0], [0, 1, 2], [0, 2, 4]]
  5. Dada a lista dados = [{'nome': 'Ana', 'nota': 8}, {'nome': 'João', 'nota': 9}, {'nome': 'Pedro', 'nota': 7}], ordene-a pela nota em ordem crescente usando sorted() e a função lambda.

    ✓ Resposta:
    dados = [{'nome': 'Ana', 'nota': 8}, {'nome': 'João', 'nota': 9}, {'nome': 'Pedro', 'nota': 7}]
    ordenados = sorted(dados, key=lambda x: x['nota'])
    print(ordenados)  # [{'nome': 'Pedro', 'nota': 7}, {'nome': 'Ana', 'nota': 8}, {'nome': 'João', 'nota': 9}]