14. Listas: Coleções Ordenadas de Itens
Até aqui neste livro, trabalhamos com partes individuais de dados: números únicos, strings e valores booleanos. Mas programas reais muitas vezes precisam trabalhar com coleções de itens relacionados — uma lista de nomes de alunos, uma série de leituras de temperatura, uma coleção de preços de produtos ou uma sequência de comandos do usuário. A lista(list) do Python é a ferramenta fundamental para armazenar e trabalhar com coleções ordenadas de dados.
Uma lista é uma sequência(sequence) que pode conter vários itens em uma ordem específica. Diferente de strings (que só podem conter caracteres), listas podem conter qualquer tipo de dado: números, strings, booleanos ou até outras listas. Listas também são mutáveis(mutable), o que significa que você pode mudar o conteúdo delas depois de criadas — adicionando itens, removendo itens ou modificando os que já existem.
Neste capítulo, vamos explorar como criar listas, acessar seus elementos, modificá-las e usá-las para resolver problemas práticos de programação. No final, você vai entender por que listas são uma das estruturas de dados mais poderosas e mais usadas no Python.
14.1) Criando Listas e Acessando Elementos
14.1.1) Criando Listas com Colchetes
A forma mais comum de criar uma lista é colocando os itens entre colchetes [], com os itens separados por vírgulas. Aqui vai um exemplo simples:
# Uma lista de nomes de alunos
students = ["Alice", "Bob", "Charlie", "Diana"]
print(students) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']Repare como o Python exibe a lista: ele mostra os colchetes e coloca aspas em volta de cada string. Isso é a representação(representation) da lista — como o Python te mostra o que tem dentro.
Listas podem conter qualquer tipo de dado. Aqui vai uma lista de notas de prova:
# Uma lista de notas inteiras
scores = [85, 92, 78, 95, 88]
print(scores) # Output: [85, 92, 78, 95, 88]Você pode até misturar tipos diferentes na mesma lista, embora isso seja menos comum na prática:
# Uma lista com tipos mistos (menos comum, mas válida)
mixed_data = ["Alice", 25, True, 3.14]
print(mixed_data) # Output: ['Alice', 25, True, 3.14]Uma lista vazia(empty list) não contém itens e é criada só com os colchetes:
# Uma lista vazia
empty = []
print(empty) # Output: []
print(len(empty)) # Output: 0A função len(), que usamos com strings, também funciona com listas — ela retorna o número de itens na lista.
14.1.2) Entendendo a Ordem e as Posições na Lista
Listas mantêm a ordem(order) em que você adiciona itens. O primeiro item que você coloca continua sendo o primeiro, o segundo continua sendo o segundo, e assim por diante. Essa ordenação é crucial porque permite que você acesse itens específicos pela posição (também chamada de índice(index)).
O Python usa indexação baseada em zero(zero-based indexing): o primeiro item está na posição 0, o segundo na posição 1, e assim por diante. Isso pode parecer estranho no começo, mas é uma convenção usada por muitas linguagens de programação.
Vamos ver como isso funciona na prática:
students = ["Alice", "Bob", "Charlie", "Diana"]
# Acesse o primeiro aluno (índice 0)
first_student = students[0]
print(first_student) # Output: Alice
# Acesse o terceiro aluno (índice 2)
third_student = students[2]
print(third_student) # Output: CharlieRepare que, para pegar o terceiro aluno, usamos o índice 2, não 3. Isso acontece porque a contagem começa em 0.
14.1.3) Acessando Elementos com Índices Positivos
Para acessar um elemento de uma lista, escreva o nome da lista seguido do índice entre colchetes: list_name[index]. O índice precisa ser um inteiro dentro do intervalo válido (0 até len(list) - 1).
Aqui vai um exemplo prático trabalhando com preços de produtos:
# Preços de produtos em dólares
prices = [19.99, 24.50, 15.75, 32.00, 8.99]
# Acesse preços específicos
first_price = prices[0]
last_index = len(prices) - 1 # Calcular o último índice válido
last_price = prices[last_index]
print(f"First product costs: ${first_price}") # Output: First product costs: $19.99
print(f"Last product costs: ${last_price}") # Output: Last product costs: $8.99Por que usamos len(prices) - 1 para o último índice? Porque se uma lista tem 5 itens, os índices são 0, 1, 2, 3, 4 — o último índice válido é sempre um a menos do que o comprimento.
Você também pode usar índices em expressões e cálculos:
scores = [85, 92, 78, 95, 88]
# Calcular a média das três primeiras notas
first_three_average = (scores[0] + scores[1] + scores[2]) / 3
print(f"Average of first three: {first_three_average}") # Output: Average of first three: 85.014.1.4) Índices Negativos: Contando a Partir do Final
O Python oferece um recurso conveniente: índices negativos(negative indices) permitem acessar itens a partir do final da lista. O índice -1 se refere ao último item, -2 ao penúltimo, e assim por diante.
students = ["Alice", "Bob", "Charlie", "Diana"]
# Acessar a partir do final
last_student = students[-1]
second_to_last = students[-2]
print(last_student) # Output: Diana
print(second_to_last) # Output: CharlieIsso é especialmente útil quando você quer o último item, mas não quer calcular len(list) - 1:
prices = [19.99, 24.50, 15.75, 32.00, 8.99]
# Essas duas abordagens são equivalentes
last_price_method1 = prices[len(prices) - 1]
last_price_method2 = prices[-1]
print(last_price_method1) # Output: 8.99
print(last_price_method2) # Output: 8.99Veja como índices positivos e negativos se mapeiam para os mesmos itens:
14.1.5) O que Acontece com Índices Inválidos
Se você tentar acessar um índice que não existe, o Python levanta um IndexError:
students = ["Alice", "Bob", "Charlie"]
# AVISO: Esta lista tem índices 0, 1, 2 (ou -3, -2, -1) - apenas para demonstração
# Tentar acessar o índice 3 causa um erro
# PROBLEMA: O índice 3 não existe em uma lista com 3 itens
# print(students[3]) # IndexError: list index out of rangeEsse erro é a forma do Python te dizer que você pediu um item que não está lá.
14.2) Indexação e Fatiamento de Listas
14.2.1) Entendendo o Básico do Fatiamento de Listas
Assim como podemos fatiar strings (como aprendemos no Capítulo 5), podemos fatiar(slice) listas para extrair partes delas. Um fatiamento cria uma nova lista contendo um subconjunto dos elementos da lista original. A sintaxe é list[start:stop], em que start é o índice onde o fatiamento começa (inclusivo) e stop é onde ele termina (exclusivo).
numbers = [10, 20, 30, 40, 50, 60, 70]
# Pegar elementos do índice 1 até (mas sem incluir) o índice 4
subset = numbers[1:4]
print(subset) # Output: [20, 30, 40]O fatiamento [1:4] inclui os índices 1, 2 e 3, mas para antes do índice 4. Essa regra de "stop é exclusivo" é a mesma do fatiamento de strings.
Vamos ver um exemplo prático com nomes de alunos:
students = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"]
# Pegar os três primeiros alunos
first_three = students[0:3]
print(first_three) # Output: ['Alice', 'Bob', 'Charlie']
# Pegar alunos do índice 2 ao 4
middle_group = students[2:5]
print(middle_group) # Output: ['Charlie', 'Diana', 'Eve']14.2.2) Omitindo Start ou Stop em Fatias
Você pode omitir o índice inicial para fatiar desde o começo, ou omitir o índice final para fatiar até o fim:
scores = [85, 92, 78, 95, 88, 91, 87]
# Do começo até o índice 3
first_few = scores[:3]
print(first_few) # Output: [85, 92, 78]
# Do índice 4 até o fim
last_few = scores[4:]
print(last_few) # Output: [88, 91, 87]
# A lista inteira (do começo ao fim)
all_scores = scores[:]
print(all_scores) # Output: [85, 92, 78, 95, 88, 91, 87]A fatia [:] cria uma cópia(copy) da lista inteira. Isso é útil quando você quer trabalhar com uma duplicata sem modificar a original — vamos explorar isso mais na seção 14.6.
14.2.3) Usando Índices Negativos em Fatias
Índices negativos funcionam em fatias do mesmo jeito que funcionam ao acessar um elemento único. Isso é especialmente útil para pegar itens do final:
students = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"]
# Pegar os três últimos alunos
last_three = students[-3:]
print(last_three) # Output: ['Diana', 'Eve', 'Frank']
# Pegar todos, exceto os dois últimos alunos
all_but_last_two = students[:-2]
print(all_but_last_two) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']
# Pegar do terceiro a partir do fim até o penúltimo
middle_from_end = students[-3:-1]
print(middle_from_end) # Output: ['Diana', 'Eve']14.2.4) Fatiamento com um Valor de Step
Você pode adicionar um terceiro parâmetro para controlar o passo(step) (quantos índices pular entre itens). A sintaxe completa é list[start:stop:step]:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# A cada segundo número começando do índice 0
evens = numbers[0:10:2]
print(evens) # Output: [0, 2, 4, 6, 8]
# A cada terceiro número começando do índice 1
every_third = numbers[1:10:3]
print(every_third) # Output: [1, 4, 7]Você também pode usar um passo negativo(negative step) para inverter a lista:
numbers = [1, 2, 3, 4, 5]
# Inverter a lista
reversed_numbers = numbers[::-1]
print(reversed_numbers) # Output: [5, 4, 3, 2, 1]A fatia [::-1] significa "começar no fim, ir até o começo, andando para trás de 1 em 1". Isso é um idiomatismo comum em Python para inverter sequências.
14.2.5) Fatias Nunca Causam IndexError
Diferente de acessar um único elemento, o fatiamento é bem tolerante. Se você especificar índices fora do intervalo da lista, o Python simplesmente ajusta eles para caber:
numbers = [10, 20, 30, 40, 50]
# Pedindo mais do que existe
extended_slice = numbers[2:100]
print(extended_slice) # Output: [30, 40, 50]
# Começando além do fim
empty_slice = numbers[10:20]
print(empty_slice) # Output: []Esse comportamento é útil porque significa que você não precisa se preocupar com limites exatos ao fatiar — o Python lida com casos de borda com elegância.
14.3) Modificando Listas e Métodos Comuns de Listas
14.3.1) Listas São Mutáveis: Alterando Elementos
Diferente de strings, que são imutáveis, listas são mutáveis — você pode mudar o conteúdo delas depois de criadas. Você pode modificar elementos individuais atribuindo novos valores a índices específicos:
# Comece com uma lista de preços
prices = [19.99, 24.50, 15.75, 32.00]
print(prices) # Output: [19.99, 24.5, 15.75, 32.0]
# Atualize o segundo preço (índice 1)
prices[1] = 22.99
print(prices) # Output: [19.99, 22.99, 15.75, 32.0]
# Atualize o último preço usando indexação negativa
prices[-1] = 29.99
print(prices) # Output: [19.99, 22.99, 15.75, 29.99]Essa mutabilidade é poderosa — significa que você pode atualizar dados no lugar sem criar novas listas. Porém, isso também significa que você precisa ter cuidado com mudanças não intencionais, o que vamos discutir na seção 14.6.
14.3.2) Adicionando Elementos com append()
O método append() adiciona um único item ao final de uma lista. Essa é uma das operações de lista mais usadas:
# Comece com um carrinho de compras vazio
cart = []
print(cart) # Output: []
# Adicione itens um a um
cart.append("Milk")
print(cart) # Output: ['Milk']
cart.append("Bread")
print(cart) # Output: ['Milk', 'Bread']
cart.append("Eggs")
print(cart) # Output: ['Milk', 'Bread', 'Eggs']Repare que append() modifica a lista no lugar(in place) — ele não retorna uma nova lista. O método retorna None, então você não precisa atribuir o resultado dele:
scores = [85, 92, 78]
result = scores.append(95)
print(scores) # Output: [85, 92, 78, 95]
print(result) # Output: None14.3.3) Inserindo Elementos em Posições Específicas com insert()
Enquanto append() sempre adiciona ao final, insert() permite que você adicione um item em qualquer posição. A sintaxe é list.insert(index, item):
students = ["Alice", "Charlie", "Diana"]
print(students) # Output: ['Alice', 'Charlie', 'Diana']
# Insira "Bob" no índice 1 (entre Alice e Charlie)
students.insert(1, "Bob")
print(students) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']Quando você insere em um índice, o item que estava naquela posição (e todos os itens depois dele) se deslocam para a direita:
numbers = [10, 20, 30, 40]
print(numbers) # Output: [10, 20, 30, 40]
# Insira 25 no índice 2
numbers.insert(2, 25)
print(numbers) # Output: [10, 20, 25, 30, 40]Você pode inserir no começo usando o índice 0:
priorities = ["Medium", "Low"]
priorities.insert(0, "High")
print(priorities) # Output: ['High', 'Medium', 'Low']Se você especificar um índice além do tamanho da lista, insert() simplesmente adiciona o item no final (como append()):
items = [1, 2, 3]
items.insert(100, 4)
print(items) # Output: [1, 2, 3, 4]14.3.4) Removendo Elementos com remove()
O método remove() remove a primeira ocorrência de um valor específico da lista:
fruits = ["apple", "banana", "cherry", "banana", "date"]
print(fruits) # Output: ['apple', 'banana', 'cherry', 'banana', 'date']
# Remova a primeira "banana"
fruits.remove("banana")
print(fruits) # Output: ['apple', 'cherry', 'banana', 'date']Repare que só a primeira "banana" foi removida — a segunda continua. Se você tentar remover um valor que não existe, o Python levanta um ValueError:
numbers = [10, 20, 30]
# AVISO: Tentando remover um valor que não existe - apenas para demonstração
# PROBLEMA: 40 não está na lista
# numbers.remove(40) # ValueError: list.remove(x): x not in listPara evitar esse erro, você pode checar se o item existe antes de removê-lo:
cart = ["Milk", "Bread", "Eggs"]
item_to_remove = "Butter"
if item_to_remove in cart:
cart.remove(item_to_remove)
print(f"Removed {item_to_remove}")
else:
print(f"{item_to_remove} not in cart")
# Output: Butter not in cart14.3.5) Removendo e Retornando Elementos com pop()
O método pop() remove um item em um índice específico e o retorna. Se você não especificar um índice, ele remove e retorna o último item:
scores = [85, 92, 78, 95, 88]
# Remova e pegue a última nota
last_score = scores.pop()
print(f"Removed: {last_score}") # Output: Removed: 88
print(scores) # Output: [85, 92, 78, 95]
# Remova e pegue a nota no índice 1
second_score = scores.pop(1)
print(f"Removed: {second_score}") # Output: Removed: 92
print(scores) # Output: [85, 78, 95]Isso é útil quando você precisa processar itens de uma lista um de cada vez:
tasks = ["Write code", "Test code", "Deploy code"]
while len(tasks) > 0:
current_task = tasks.pop(0) # Remover do começo
print(f"Working on: {current_task}")
# Output:
# Working on: Write code
# Working on: Test code
# Working on: Deploy code
print(tasks) # Output: []14.3.6) Estendendo Listas com extend()
O método extend() adiciona todos os itens de outra lista (ou qualquer iterável) ao final da lista atual:
primary_colors = ["red", "blue", "yellow"]
secondary_colors = ["green", "orange", "purple"]
# Adicione todas as cores secundárias às cores primárias
primary_colors.extend(secondary_colors)
print(primary_colors)
# Output: ['red', 'blue', 'yellow', 'green', 'orange', 'purple']Isso é diferente de append(), que adicionaria a lista inteira como um único elemento:
colors1 = ["red", "blue"]
colors2 = ["green", "orange"]
# Usando append (adiciona a lista como um elemento só)
colors1.append(colors2)
print(colors1) # Output: ['red', 'blue', ['green', 'orange']]
# Usando extend (adiciona cada elemento individualmente)
colors3 = ["red", "blue"]
colors3.extend(colors2)
print(colors3) # Output: ['red', 'blue', 'green', 'orange']14.3.7) Ordenando Listas com sort() e sorted()
O Python oferece duas formas de ordenar listas. O método sort() ordena a lista no lugar(in place) (modificando a original):
scores = [78, 95, 85, 92, 88]
scores.sort()
print(scores) # Output: [78, 85, 88, 92, 95]Para ordenar em ordem decrescente, use o parâmetro reverse:
scores = [78, 95, 85, 92, 88]
scores.sort(reverse=True)
print(scores) # Output: [95, 92, 88, 85, 78]A função sorted() (que vamos explorar mais no Capítulo 38) cria uma nova lista ordenada sem modificar a original:
original = [78, 95, 85, 92, 88]
sorted_scores = sorted(original)
print(original) # Output: [78, 95, 85, 92, 88]
print(sorted_scores) # Output: [78, 85, 88, 92, 95]A ordenação funciona com strings também, usando ordem alfabética:
names = ["Charlie", "Alice", "Diana", "Bob"]
names.sort()
print(names) # Output: ['Alice', 'Bob', 'Charlie', 'Diana']14.3.8) Invertendo Listas com reverse()
O método reverse() inverte a lista no lugar(in place):
numbers = [1, 2, 3, 4, 5]
numbers.reverse()
print(numbers) # Output: [5, 4, 3, 2, 1]Isso é diferente de ordenar em ordem reversa — reverse() simplesmente inverte a ordem atual, seja ela qual for:
mixed = [3, 1, 4, 1, 5]
mixed.reverse()
print(mixed) # Output: [5, 1, 4, 1, 3]Lembre que você também pode inverter uma lista usando fatiamento: list[::-1]. A diferença é que o fatiamento cria uma nova lista, enquanto reverse() modifica a original.
14.3.9) Encontrando Elementos com index() e count()
O método index() retorna a posição da primeira ocorrência de um valor:
students = ["Alice", "Bob", "Charlie", "Diana", "Bob"]
# Encontrar onde "Charlie" está
position = students.index("Charlie")
print(f"Charlie is at index {position}") # Output: Charlie is at index 2
# Encontrar o primeiro "Bob"
bob_position = students.index("Bob")
print(f"Bob is at index {bob_position}") # Output: Bob is at index 1Se o valor não existe, index() levanta um ValueError:
students = ["Alice", "Bob", "Charlie"]
# AVISO: Tentando encontrar um valor que não existe - apenas para demonstração
# PROBLEMA: 'Eve' não está na lista
# position = students.index("Eve") # ValueError: 'Eve' is not in listO método count() retorna quantas vezes um valor aparece:
numbers = [1, 2, 3, 2, 4, 2, 5]
twos = numbers.count(2)
print(f"The number 2 appears {twos} times") # Output: The number 2 appears 3 times
# count pode retornar 0 se o item não existir
sixes = numbers.count(6)
print(f"The number 6 appears {sixes} times") # Output: The number 6 appears 0 times14.3.10) Limpando Todos os Elementos com clear()
O método clear() remove todos os itens de uma lista, deixando-a vazia:
cart = ["Milk", "Bread", "Eggs", "Butter"]
print(cart) # Output: ['Milk', 'Bread', 'Eggs', 'Butter']
cart.clear()
print(cart) # Output: []
print(len(cart)) # Output: 0Isso é equivalente a atribuir uma lista vazia, mas clear() deixa mais explícita a intenção.
14.4) Excluindo Elementos de Lista com del
14.4.1) Usando del para Remover Elementos por Índice
A instrução del pode excluir elementos de uma lista em índices específicos:
students = ["Alice", "Bob", "Charlie", "Diana", "Eve"]
print(students) # Output: ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve']
# Excluir o elemento no índice 2
del students[2]
print(students) # Output: ['Alice', 'Bob', 'Diana', 'Eve']Diferente de pop(), del não retorna o valor removido — ele só exclui. Isso é útil quando você quer remover um item, mas não precisa usá-lo:
scores = [85, 92, 78, 95, 88]
# Remover a menor nota (no índice 2)
del scores[2]
print(scores) # Output: [85, 92, 95, 88]Você também pode usar índices negativos com del:
tasks = ["Task 1", "Task 2", "Task 3", "Task 4"]
# Excluir a última tarefa
del tasks[-1]
print(tasks) # Output: ['Task 1', 'Task 2', 'Task 3']14.4.2) Excluindo Fatias com del
A instrução del pode remover fatias inteiras de uma só vez:
numbers = [10, 20, 30, 40, 50, 60, 70]
print(numbers) # Output: [10, 20, 30, 40, 50, 60, 70]
# Excluir elementos do índice 2 ao 4 (índices 2, 3, 4)
del numbers[2:5]
print(numbers) # Output: [10, 20, 60, 70]Isso é especialmente útil para remover intervalos de elementos:
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Remover os três primeiros elementos
del data[:3]
print(data) # Output: [4, 5, 6, 7, 8, 9, 10]
# Remover os dois últimos elementos
del data[-2:]
print(data) # Output: [4, 5, 6, 7, 8]Você pode até excluir um elemento sim e outro não usando fatiamento com step:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Excluir a cada segundo elemento
del numbers[::2]
print(numbers) # Output: [1, 3, 5, 7, 9]14.4.3) Comparando del, remove() e pop()
Vamos deixar claro quando usar cada método de exclusão:
# Lista de exemplo para comparação
items = ["apple", "banana", "cherry", "date", "elderberry"]
# Use remove() quando você sabe o VALOR a excluir
items_copy1 = items.copy()
items_copy1.remove("cherry") # Remove o primeiro "cherry"
print(items_copy1) # Output: ['apple', 'banana', 'date', 'elderberry']
# Use pop() quando você sabe o ÍNDICE e precisa do valor
items_copy2 = items.copy()
removed_item = items_copy2.pop(2) # Remove e retorna o item no índice 2
print(f"Removed: {removed_item}") # Output: Removed: cherry
print(items_copy2) # Output: ['apple', 'banana', 'date', 'elderberry']
# Use del quando você sabe o ÍNDICE mas não precisa do valor
items_copy3 = items.copy()
del items_copy3[2] # Só remove o item no índice 2
print(items_copy3) # Output: ['apple', 'banana', 'date', 'elderberry']14.5) Iterando Sobre Listas com Loops for
14.5.1) Iteração Básica de Lista
Uma das operações mais comuns com listas é processar cada item em sequência. O loop for (que aprendemos no Capítulo 12) é perfeito para isso:
students = ["Alice", "Bob", "Charlie", "Diana"]
# Processar cada aluno
for student in students:
print(f"Hello, {student}!")
# Output:
# Hello, Alice!
# Hello, Bob!
# Hello, Charlie!
# Hello, Diana!A variável do loop (student neste caso) assume cada valor da lista, um por vez, em ordem. Você pode dar para essa variável qualquer nome que faça sentido:
scores = [85, 92, 78, 95, 88]
# Calcular e exibir a nota (grade) de cada score
for score in scores:
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
else:
grade = "C"
print(f"Score {score} is a {grade}")
# Output:
# Score 85 is a B
# Score 92 is a A
# Score 78 is a C
# Score 95 is a A
# Score 88 is a B14.5.2) Processando Itens Correspondentes de Múltiplas Listas
Às vezes você precisa trabalhar com dados relacionados armazenados em listas separadas. Vamos aprender sobre a função zip() em detalhes no Capítulo 38, mas aqui vai uma prévia rápida de como ela pode ajudar a processar itens correspondentes:
# Vamos aprender sobre zip() no Capítulo 38, mas por enquanto, aqui vai um exemplo simples
students = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
# Processar pares correspondentes
for student, score in zip(students, scores):
print(f"{student} scored {score}")
# Output:
# Alice scored 85
# Bob scored 92
# Charlie scored 78A função zip() emparelha elementos de múltiplas listas, o que é útil quando você tem dados relacionados em listas separadas. Vamos explorar isso e outras ferramentas de iteração em profundidade no Capítulo 38.
14.6) Copiando Listas e Evitando Referências Compartilhadas
14.6.1) Entendendo Referências de Lista
Quando você atribui uma lista a uma variável, o Python não cria uma cópia da lista — ele cria uma referência(reference) para o mesmo objeto de lista na memória. Isso significa que múltiplas variáveis podem se referir à mesma lista:
original = [1, 2, 3]
reference = original # As duas variáveis apontam para a MESMA lista
# Modificar por uma variável afeta a outra
reference.append(4)
print(original) # Output: [1, 2, 3, 4]
print(reference) # Output: [1, 2, 3, 4]Esse comportamento pode ser surpreendente se você espera que reference seja uma cópia independente. Vamos ver por que isso importa:
# Cenário: você quer acompanhar mudanças em um carrinho de compras
cart = ["Milk", "Bread"]
backup = cart # Tentando salvar o estado original
# Adicionar mais itens
cart.append("Eggs")
cart.append("Butter")
# Verificar o "backup"
print(backup) # Output: ['Milk', 'Bread', 'Eggs', 'Butter']O backup também mudou! Isso acontece porque backup e cart são dois nomes para o mesmo objeto de lista.
14.6.2) Criando Cópias Independentes com Fatiamento
Para criar uma cópia realmente independente, use fatiamento com [:]:
original = [1, 2, 3]
copy = original[:] # Cria uma NOVA lista com o mesmo conteúdo
# Modificar a cópia não afeta a original
copy.append(4)
print(original) # Output: [1, 2, 3]
print(copy) # Output: [1, 2, 3, 4]Agora vamos corrigir nosso exemplo do carrinho de compras:
cart = ["Milk", "Bread"]
backup = cart[:] # Criar uma cópia independente
# Adicionar mais itens ao carrinho
cart.append("Eggs")
cart.append("Butter")
# O backup continua sem alterações
print(cart) # Output: ['Milk', 'Bread', 'Eggs', 'Butter']
print(backup) # Output: ['Milk', 'Bread']14.6.3) Criando Cópias com o Método copy()
Listas também têm um método copy() que faz a mesma coisa que [:]:
original = [10, 20, 30]
copy = original.copy()
copy.append(40)
print(original) # Output: [10, 20, 30]
print(copy) # Output: [10, 20, 30, 40]Tanto [:] quanto copy() criam cópias rasas(shallow copies), o que vamos discutir a seguir.
14.6.4) A Limitação da Cópia Rasa
Tanto [:] quanto copy() criam cópias rasas(shallow copies). Isso significa que elas copiam a estrutura da lista, mas se a lista contiver outros objetos mutáveis (como outras listas), esses objetos internos ainda são compartilhados:
# Uma lista contendo listas
original = [[1, 2], [3, 4], [5, 6]]
copy = original[:]
# Modificar a estrutura da lista externa é independente
copy.append([7, 8])
print(original) # Output: [[1, 2], [3, 4], [5, 6]]
print(copy) # Output: [[1, 2], [3, 4], [5, 6], [7, 8]]
# Mas modificar uma lista interna afeta as duas!
copy[0].append(99)
print(original) # Output: [[1, 2, 99], [3, 4], [5, 6]]
print(copy) # Output: [[1, 2, 99], [3, 4], [5, 6], [7, 8]]Por que isso acontece? Porque a cópia rasa cria uma nova lista externa, mas as listas internas ainda são referências compartilhadas:
Para estruturas aninhadas, você precisaria de uma cópia profunda(deep copy), que vamos aprender quando explorarmos o módulo copy em capítulos posteriores. Por enquanto, saiba que cópias rasas funcionam perfeitamente para listas de itens imutáveis (números, strings, tuplas), mas exigem cuidado com estruturas mutáveis aninhadas.
14.6.5) Quando Referências Compartilhadas São Úteis
Às vezes você quer que múltiplas variáveis se refiram à mesma lista. Isso é útil quando você precisa modificar uma lista de diferentes partes do seu código:
# Uma função que modifica uma lista no lugar
def add_bonus_points(scores, bonus):
for i in range(len(scores)):
scores[i] = scores[i] + bonus
# A lista original é modificada
student_scores = [85, 92, 78]
add_bonus_points(student_scores, 5)
print(student_scores) # Output: [90, 97, 83]Isso funciona porque a função recebe uma referência para a lista original, não uma cópia. Vamos explorar isso mais quando estudarmos funções em detalhes na Parte V.
14.7) Usando enumerate() ao Percorrer Listas
14.7.1) A Necessidade de Ter Índice e Valor
Às vezes, ao iterar sobre uma lista, você precisa tanto do índice quanto do valor. Uma abordagem é usar range(len(list)):
students = ["Alice", "Bob", "Charlie", "Diana"]
for i in range(len(students)):
print(f"Student {i}: {students[i]}")
# Output:
# Student 0: Alice
# Student 1: Bob
# Student 2: Charlie
# Student 3: DianaIsso funciona, mas não é muito elegante. Você tem que usar students[i] para acessar cada valor, o que é menos legível do que iterar diretamente sobre os valores.
14.7.2) Usando enumerate() para um Código Mais Limpo
A função enumerate() fornece uma solução melhor. Ela retorna tanto o índice quanto o valor para cada item:
students = ["Alice", "Bob", "Charlie", "Diana"]
for index, student in enumerate(students):
print(f"Student {index}: {student}")
# Output:
# Student 0: Alice
# Student 1: Bob
# Student 2: Charlie
# Student 3: DianaA sintaxe for index, value in enumerate(list) desempacota cada par que enumerate() produz. Isso é bem mais legível do que usar range(len()).
14.7.3) Iniciando enumerate() em um Número Diferente
Por padrão, enumerate() começa a contar em 0. Você pode especificar um número inicial diferente com o parâmetro start:
students = ["Alice", "Bob", "Charlie", "Diana"]
# Começar a contagem em 1 em vez de 0
for position, student in enumerate(students, start=1):
print(f"Position {position}: {student}")
# Output:
# Position 1: Alice
# Position 2: Bob
# Position 3: Charlie
# Position 4: DianaIsso é útil quando você quer mostrar uma numeração amigável para humanos (começando em 1) em vez de uma indexação amigável para programadores (começando em 0).
Exemplos Práticos com enumerate()
Aqui vai um exemplo prático exibindo um menu numerado:
menu_items = ["New Game", "Load Game", "Settings", "Quit"]
print("Main Menu:")
for number, item in enumerate(menu_items, start=1):
print(f"{number}. {item}")
# Output:
# Main Menu:
# 1. New Game
# 2. Load Game
# 3. Settings
# 4. Quit14.7.4) Modificando Listas com enumerate()
Você pode usar enumerate() quando precisa modificar elementos da lista com base na posição deles:
# Adicionar bônus baseado na posição às notas
scores = [85, 92, 78, 95, 88]
for index, score in enumerate(scores):
# O primeiro aluno recebe 5 pontos de bônus, o segundo recebe 4, etc.
bonus = 5 - index
if bonus > 0:
scores[index] = score + bonus
print(scores) # Output: [90, 96, 81, 97, 89]14.8) Padrões Comuns com Listas: Buscar, Filtrar e Agregar Dados
14.8.1) Buscando Itens em Listas
Uma das tarefas mais comuns é verificar se uma lista contém um item específico. O operador in (que aprendemos no Capítulo 7) deixa isso simples:
students = ["Alice", "Bob", "Charlie", "Diana"]
# Verificar se um aluno está na lista
if "Charlie" in students:
print("Charlie is enrolled") # Output: Charlie is enrolled
if "Eve" not in students:
print("Eve is not enrolled") # Output: Eve is not enrolledPara encontrar a posição de um item, use o método index() (coberto na seção 14.3.9), mas lembre de checar se o item existe primeiro:
scores = [85, 92, 78, 95, 88]
target_score = 95
if target_score in scores:
position = scores.index(target_score)
print(f"Score {target_score} found at index {position}")
# Output: Score 95 found at index 3
else:
print(f"Score {target_score} not found")14.8.2) Encontrando os Valores Máximo e Mínimo
As funções embutidas max() e min() do Python funcionam com listas:
scores = [85, 92, 78, 95, 88, 91, 76]
highest_score = max(scores)
lowest_score = min(scores)
print(f"Highest score: {highest_score}") # Output: Highest score: 95
print(f"Lowest score: {lowest_score}") # Output: Lowest score: 7614.8.3) Calculando Agregados: Soma, Média e Contagem
Computar totais e médias é uma operação fundamental com listas:
scores = [85, 92, 78, 95, 88, 91, 76, 89]
# Calcular total e média
total = sum(scores)
count = len(scores)
average = total / count
print(f"Total: {total}") # Output: Total: 694
print(f"Count: {count}") # Output: Count: 8
print(f"Average: {average:.2f}") # Output: Average: 86.75Aqui vai um exemplo prático calculando o total de um carrinho de compras:
cart_items = ["Milk", "Bread", "Eggs", "Butter", "Cheese"]
prices = [3.99, 2.49, 4.99, 5.49, 6.99]
# Calcular o custo total
total_cost = sum(prices)
item_count = len(cart_items)
print(f"Items in cart: {item_count}")
print(f"Total cost: ${total_cost:.2f}")
# Output:
# Items in cart: 5
# Total cost: $23.9514.9) Mutabilidade de Listas e Truthiness em Condições
14.9.1) Entendendo a Mutabilidade de Listas na Prática
Vimos ao longo deste capítulo que listas são mutáveis — elas podem ser alteradas após a criação. Essa mutabilidade é o que torna listas tão poderosas para armazenar e manipular coleções de dados. Vamos consolidar nosso entendimento com um exemplo abrangente:
# Comece com uma lista de tarefas vazia
tasks = []
print(f"Initial tasks: {tasks}") # Output: Initial tasks: []
# Adicionar tarefas
tasks.append("Write code")
tasks.append("Test code")
tasks.append("Deploy code")
print(f"After adding: {tasks}")
# Output: After adding: ['Write code', 'Test code', 'Deploy code']
# Inserir uma tarefa urgente no começo
tasks.insert(0, "Review requirements")
print(f"After inserting: {tasks}")
# Output: After inserting: ['Review requirements', 'Write code', 'Test code', 'Deploy code']
# Concluir e remover a primeira tarefa
completed = tasks.pop(0)
print(f"Completed: {completed}") # Output: Completed: Review requirements
print(f"Remaining: {tasks}")
# Output: Remaining: ['Write code', 'Test code', 'Deploy code']
# Modificar uma tarefa
tasks[1] = "Test code thoroughly"
print(f"After modifying: {tasks}")
# Output: After modifying: ['Write code', 'Test code thoroughly', 'Deploy code']14.9.2) Mutabilidade vs Imutabilidade: Listas vs Strings
É importante entender a diferença entre listas mutáveis e strings imutáveis. Com strings, as operações criam novas strings em vez de modificar a original:
# Strings são imutáveis
text = "hello"
text.upper() # Cria uma nova string, não muda a original
print(text) # Output: hello (unchanged)
# Para "mudar" uma string, você precisa reatribuir
text = text.upper()
print(text) # Output: HELLO
# Listas são mutáveis
numbers = [1, 2, 3]
numbers.append(4) # Modifica a lista no lugar
print(numbers) # Output: [1, 2, 3, 4] (changed)Essa diferença afeta como você trabalha com esses tipos:
# Operações com string exigem reatribuição
name = "alice"
name = name.capitalize() # Precisa reatribuir para ver a mudança
print(name) # Output: Alice
# Operações com lista modificam no lugar
scores = [85, 92, 78]
scores.append(95) # Não precisa reatribuir
print(scores) # Output: [85, 92, 78, 95]14.9.3) Usando Listas em Contextos Booleanos
Listas têm truthiness: uma lista vazia é considerada False, e qualquer lista não vazia é considerada True. Isso é útil em instruções condicionais:
# Lista vazia é falsy
empty_cart = []
if empty_cart:
print("Cart has items")
else:
print("Cart is empty") # Output: Cart is empty
# Lista não vazia é truthy
cart_with_items = ["Milk", "Bread"]
if cart_with_items:
print("Cart has items") # Output: Cart has itemsEsse padrão é bastante usado para checar se uma lista tem elementos antes de processar:
students = ["Alice", "Bob", "Charlie"]
if students:
print(f"We have {len(students)} students")
for student in students:
print(f" - {student}")
else:
print("No students enrolled")
# Output:
# We have 3 students
# - Alice
# - Bob
# - Charlie14.9.4) Padrão Prático: Processar Até Esvaziar
A truthiness das listas permite um padrão útil para processar itens até que uma lista esteja vazia:
# Processar tarefas até não sobrar nenhuma
tasks = ["Task 1", "Task 2", "Task 3"]
while tasks: # Continuar enquanto a lista não estiver vazia
current_task = tasks.pop(0)
print(f"Processing: {current_task}")
print("All tasks completed!")
# Output:
# Processing: Task 1
# Processing: Task 2
# Processing: Task 3
# All tasks completed!14.9.5) Verificando Listas Vazias: Explícito vs Implícito
Existem duas formas de verificar se uma lista está vazia:
items = []
# Verificação implícita (Pythonic)
if not items:
print("List is empty") # Output: List is empty
# Verificação explícita (também válida)
if len(items) == 0:
print("List is empty") # Output: List is emptyA verificação implícita (if not items:) geralmente é preferida em Python porque é mais concisa e funciona com qualquer tipo de coleção. Porém, as duas abordagens estão corretas e você vai ver ambas em código real.
14.9.6) Mutabilidade e Comportamento de Funções
Quando você passa uma lista para uma função (o que vamos explorar em detalhes na Parte V), a função recebe uma referência para o mesmo objeto de lista. Isso significa que a função pode modificar a lista original:
def add_item(shopping_list, item):
shopping_list.append(item)
print(f"Added {item}")
# A lista original é modificada
cart = ["Milk", "Bread"]
print(f"Before: {cart}") # Output: Before: ['Milk', 'Bread']
add_item(cart, "Eggs") # Output: Added Eggs
print(f"After: {cart}") # Output: After: ['Milk', 'Bread', 'Eggs']Esse comportamento é diferente de tipos imutáveis como strings e números, em que o valor original não pode ser alterado por uma função. Entender essa distinção é crucial para escrever programas corretos.
Listas são uma das estruturas de dados mais fundamentais e versáteis do Python. Elas fornecem uma coleção ordenada e mutável que pode crescer e encolher conforme necessário, tornando-as perfeitas para armazenar e processar sequências de dados relacionados. Você aprendeu como criar listas, acessar seus elementos por indexação e fatiamento, modificá-las com vários métodos, iterar sobre elas de forma eficiente e entender sua natureza mutável.
Os padrões que exploramos — busca, filtragem, agregação e transformação de dados — formam a base para trabalhar com coleções em Python. Conforme você continuar aprendendo, vai descobrir formas ainda mais poderosas de trabalhar com listas, incluindo list comprehensions (Capítulo 35) e técnicas avançadas de iteração (Capítulos 36-37). Mas os fundamentos que você dominou neste capítulo vão te servir muito bem ao longo da sua jornada de programação em Python.