16. Dicionários: Mapeando Chaves para Valores
Nos capítulos anteriores, aprendemos sobre listas e tuplas — coleções que armazenam itens em uma ordem específica e nos deixam acessá-los por posição. Mas e se quisermos buscar informações por algo mais significativo do que um número? E se quisermos encontrar a nota de um aluno pelo nome, ou o preço de um produto pelo ID, ou a definição de uma palavra pela própria palavra?
É aqui que entram os dicionários. Um dicionário é a estrutura de dados embutida do Python para armazenar pares chave-valor (key-value pairs). Em vez de acessar itens pela posição (como grades[0]), nós os acessamos pela chave (como grades["Alice"]). Isso torna os dicionários incrivelmente poderosos para organizar e recuperar dados em programas do mundo real.
Pense em um dicionário como um dicionário do mundo real ou uma lista telefônica: você procura uma palavra (a chave) para encontrar sua definição (o valor), ou procura um nome para encontrar um número de telefone. Os dicionários do Python funcionam da mesma forma — eles mapeiam chaves para valores, permitindo buscas rápidas e uma organização flexível dos dados.
16.1) Criando Dicionários e Acessando Valores
16.1.1) O Que É um Dicionário?
Um dicionário é uma coleção de pares chave-valor. Cada chave está associada a um valor, e você usa a chave para recuperar o valor. As chaves precisam ser únicas dentro de um dicionário — você não pode ter duas entradas com a mesma chave. Já os valores podem se repetir.
Aqui está a estrutura básica:
- Chaves: identificadores únicos usados para buscar valores (como nomes, IDs ou rótulos)
- Valores: os dados associados a cada chave (como notas, preços ou descrições)
Dicionários são:
- Mutáveis: você pode adicionar, modificar ou remover pares chave-valor após a criação
- Não ordenados (no Python 3.6 e anteriores) ou ordenados por inserção (no Python 3.7+): embora o Python moderno preserve a ordem em que você adiciona itens, você deve pensar em dicionários como coleções em que você acessa itens por chave, não por posição
- Dinâmicos: eles podem crescer e encolher conforme necessário
16.1.2) Criando Dicionários Vazios e Simples
A forma mais simples de criar um dicionário é usando chaves {} com pares chave-valor separados por dois-pontos:
# Dicionário vazio
empty_dict = {}
print(empty_dict) # Output: {}
print(type(empty_dict)) # Output: <class 'dict'>
# Dicionário com notas de alunos
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Dicionário com preços de produtos
prices = {"apple": 0.50, "banana": 0.30, "orange": 0.75}
print(prices) # Output: {'apple': 0.5, 'banana': 0.3, 'orange': 0.75}Observe a sintaxe: cada par chave-valor é escrito como key: value, e os pares são separados por vírgulas. As chaves aqui são strings ("Alice", "apple"), e os valores são números, mas tanto chaves quanto valores podem ser de muitos tipos diferentes.
Você também pode criar um dicionário usando o construtor dict():
# Usando dict() com argumentos nomeados
student = dict(name="Alice", age=20, major="Computer Science")
print(student) # Output: {'name': 'Alice', 'age': 20, 'major': 'Computer Science'}
# Usando dict() com uma lista de tuplas
colors = dict([("red", "#FF0000"), ("green", "#00FF00"), ("blue", "#0000FF")])
print(colors) # Output: {'red': '#FF0000', 'green': '#00FF00', 'blue': '#0000FF'}O construtor dict() é útil quando você está montando dicionários a partir de outras estruturas de dados ou quando quer usar identificadores do Python como chaves (sem aspas).
16.1.3) Acessando Valores pela Chave
Para recuperar um valor de um dicionário, use colchetes com a chave:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Acessar valores individuais
alice_grade = grades["Alice"]
print(alice_grade) # Output: 95
bob_grade = grades["Bob"]
print(bob_grade) # Output: 87Esta é a forma mais direta de acessar valores de um dicionário. Porém, se você tentar acessar uma chave que não existe, o Python levanta um KeyError:
grades = {"Alice": 95, "Bob": 87}
# AVISO: KeyError - apenas para demonstração
# print(grades["David"]) # PROBLEM: KeyError: 'David'Esse erro acontece porque "David" não é uma chave no dicionário. Vamos aprender como lidar com isso com segurança na próxima subseção.
16.1.4) Acesso Seguro com get()
Para evitar KeyError quando uma chave pode não existir, use o método get(). Ele retorna None (ou um valor padrão que você especificar) se a chave não for encontrada:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Acesso seguro com get()
alice_grade = grades.get("Alice")
print(alice_grade) # Output: 95
# Chave não existe - retorna None
david_grade = grades.get("David")
print(david_grade) # Output: None
# Fornecer um valor padrão
david_grade = grades.get("David", 0)
print(david_grade) # Output: 0
# Usando get() em lógica condicional
if grades.get("Eve") is None:
print("Eve is not in the grade book") # Output: Eve is not in the grade bookO método get() é mais seguro do que o acesso direto por colchetes quando você não tem certeza de que uma chave existe. O segundo argumento de get() é o valor padrão a ser retornado se a chave estiver ausente — se você não fornecer um, o padrão é None.
Aqui vai um exemplo prático mostrando quando get() é útil:
# Banco de dados de alunos com informações opcionais
students = {
"Alice": {"age": 20, "major": "CS"},
"Bob": {"age": 19}, # Bob ainda não declarou uma área
"Charlie": {"major": "Math"} # A idade de Charlie não foi registrada
}
# Acessar com segurança informações que podem estar ausentes
for name in ["Alice", "Bob", "Charlie"]:
student = students[name]
age = student.get("age", "Unknown")
major = student.get("major", "Undeclared")
print(f"{name}: Age {age}, Major {major}")
# Output:
# Alice: Age 20, Major CS
# Bob: Age 19, Major Undeclared
# Charlie: Age Unknown, Major Math16.1.5) Tipos de Chave Válidos
As chaves de um dicionário precisam ser hashable — um termo técnico que significa que o valor da chave não pode mudar. Na prática, isso significa:
Tipos de chave válidos (imutáveis):
- Strings:
"name","id_123" - Números:
42,3.14 - Tuplas (se contiverem apenas itens imutáveis):
(1, 2),("x", "y") - Booleanos:
True,False None
Tipos de chave inválidos (mutáveis):
- Listas:
[1, 2, 3]não podem ser chave - Dicionários:
{"a": 1}não podem ser chave - Conjuntos:
{1, 2, 3}não podem ser chave
# Chaves válidas
valid_dict = {
"name": "Alice", # Chave string
42: "answer", # Chave inteira
3.14: "pi", # Chave float
(1, 2): "coordinates", # Chave tupla
True: "yes", # Chave booleana
None: "nothing" # Chave None
}
print(valid_dict["name"]) # Output: Alice
print(valid_dict[42]) # Output: answer
print(valid_dict[(1, 2)]) # Output: coordinates
# AVISO: Chaves inválidas - apenas para demonstração
# invalid_dict = {[1, 2]: "list key"} # PROBLEM: TypeError: unhashable type: 'list'
# invalid_dict = {{1, 2}: "set key"} # PROBLEM: TypeError: unhashable type: 'set'Erro comum de iniciante: um erro frequente é tentar usar uma lista como chave de um dicionário porque isso parece lógico. Por exemplo, você pode querer usar coordenadas [x, y] como chave para armazenar dados de localização. Quando o Python levanta TypeError: unhashable type: 'list', iniciantes muitas vezes não entendem o porquê — afinal, a lista contém exatamente os dados que eles querem usar como chave.
O motivo é que listas são mutáveis (elas podem mudar), e o Python precisa que as chaves do dicionário sejam estáveis e não mudem. Se você precisa usar algo “tipo lista” como chave, converta para tupla primeiro: tuple([1, 2]) vira (1, 2), que pode ser usado como chave. Tuplas são imutáveis, então funcionam perfeitamente:
# Errado: tentar usar uma lista como chave
# locations = {[0, 0]: "origin", [1, 0]: "east"} # PROBLEM: TypeError
# Certo: converter para tupla
locations = {(0, 0): "origin", (1, 0): "east", (0, 1): "north"}
print(locations[(0, 0)]) # Output: origin
print(locations[(1, 0)]) # Output: eastOs valores, por outro lado, podem ser de qualquer tipo — mutáveis ou imutáveis:
# Valores podem ser de qualquer tipo
flexible_dict = {
"numbers": [1, 2, 3], # Valor lista
"nested": {"a": 1, "b": 2}, # Valor dicionário
"function": len, # Valor função
"mixed": (1, [2, 3], {"x": 4}) # Tupla contendo itens mutáveis
}
print(flexible_dict["numbers"]) # Output: [1, 2, 3]
print(flexible_dict["nested"]["a"]) # Output: 1Vamos explorar hashability com mais profundidade no Capítulo 17, mas por enquanto, lembre-se: use tipos imutáveis (strings, números, tuplas) como chaves, e você ficará bem.
16.2) Adicionando e Atualizando Entradas do Dicionário
16.2.1) Adicionando Novos Pares Chave-Valor
Adicionar uma nova entrada a um dicionário é simples — basta atribuir um valor a uma nova chave:
grades = {"Alice": 95, "Bob": 87}
print(grades) # Output: {'Alice': 95, 'Bob': 87}
# Adicionar um novo aluno
grades["Charlie"] = 92
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Adicionar outro aluno
grades["Diana"] = 88
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}Se a chave não existir, o Python a cria. Se ela existir, o Python atualiza o valor (vamos ver isso em seguida).
16.2.2) Atualizando Valores Existentes
Para atualizar um valor, basta atribuir um novo valor a uma chave existente:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Atualizar a nota do Bob
grades["Bob"] = 90
print(grades) # Output: {'Alice': 95, 'Bob': 90, 'Charlie': 92}
# Atualizar múltiplas notas
grades["Alice"] = 98
grades["Charlie"] = 94
print(grades) # Output: {'Alice': 98, 'Bob': 90, 'Charlie': 94}O Python não diferencia adicionar e atualizar — a sintaxe é idêntica. Se a chave existir, o valor é atualizado; se não, uma nova entrada é criada.
Aqui vai um exemplo prático mostrando tanto adicionar quanto atualizar:
# Controlar estoque
inventory = {"apple": 50, "banana": 30}
print("Initial inventory:", inventory) # Output: Initial inventory: {'apple': 50, 'banana': 30}
# Repor maçãs (atualizar existente)
inventory["apple"] = inventory["apple"] + 20
print("After restocking apples:", inventory) # Output: After restocking apples: {'apple': 70, 'banana': 30}
# Adicionar novo produto (adicionar nova chave)
inventory["orange"] = 40
print("After adding oranges:", inventory) # Output: After adding oranges: {'apple': 70, 'banana': 30, 'orange': 40}
# Vender algumas bananas (atualizar existente)
inventory["banana"] = inventory["banana"] - 10
print("After selling bananas:", inventory) # Output: After selling bananas: {'apple': 70, 'banana': 20, 'orange': 40}16.2.3) Usando update() para Mesclar Dicionários
O método update() adiciona múltiplos pares chave-valor de uma vez ou mescla outro dicionário no dicionário atual:
grades = {"Alice": 95, "Bob": 87}
print(grades) # Output: {'Alice': 95, 'Bob': 87}
# Adicionar múltiplos alunos de uma vez
new_students = {"Charlie": 92, "Diana": 88}
grades.update(new_students)
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}
# Atualizar existentes e adicionar novos
more_updates = {"Bob": 90, "Eve": 85} # A nota do Bob muda, Eve é nova
grades.update(more_updates)
print(grades) # Output: {'Alice': 95, 'Bob': 90, 'Charlie': 92, 'Diana': 88, 'Eve': 85}O método update() modifica o dicionário “in place”. Se uma chave já existir, seu valor é atualizado; se não, o par chave-valor é adicionado.
Você também pode passar argumentos nomeados para update():
student = {"name": "Alice", "age": 20}
print(student) # Output: {'name': 'Alice', 'age': 20}
# Atualizar usando argumentos nomeados
student.update(age=21, major="Computer Science")
print(student) # Output: {'name': 'Alice', 'age': 21, 'major': 'Computer Science'}Aqui vai um exemplo prático mesclando configurações:
# Configurações padrão
config = {
"theme": "light",
"font_size": 12,
"auto_save": True
}
# Preferências do usuário (sobrescrevem alguns padrões)
user_prefs = {
"theme": "dark",
"font_size": 14
}
# Mesclar preferências do usuário em config
config.update(user_prefs)
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}16.2.4) Usando setdefault() para Adicionar Apenas Se a Chave Não Existir
O método setdefault() é útil quando você quer adicionar um par chave-valor apenas se a chave ainda não existir. Se a chave existir, ele retorna o valor atual sem alterá-lo:
grades = {"Alice": 95, "Bob": 87}
# Adicionar Charlie (a chave não existe)
result = grades.setdefault("Charlie", 90)
print(result) # Output: 90
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 90}
# Tentar adicionar Alice (a chave existe - sem mudança)
result = grades.setdefault("Alice", 80)
print(result) # Output: 95 (existing value returned)
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 90} (unchanged)Isso é particularmente útil quando você quer garantir que todas as chaves de configuração obrigatórias existam com valores padrão, preservando quaisquer valores que o usuário já tenha personalizado:
# Configuração do aplicativo com padrões
config = {"theme": "light", "font_size": 12}
# Garantir que todas as configurações obrigatórias existam com padrões
config.setdefault("auto_save", True)
config.setdefault("language", "en")
config.setdefault("theme", "dark") # Não vai mudar - já existe
print(config)
# Output: {'theme': 'light', 'font_size': 12, 'auto_save': True, 'language': 'en'}
# Agora acessar com segurança todas as configurações
print(f"Theme: {config['theme']}") # Output: Theme: light
print(f"Auto-save: {config['auto_save']}") # Output: Auto-save: True
print(f"Language: {config['language']}") # Output: Language: en16.3) Removendo Entradas do Dicionário com del e pop()
16.3.1) Removendo Entradas com del
A instrução del remove um par chave-valor de um dicionário:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92, 'Diana': 88}
# Remover Charlie
del grades["Charlie"]
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Diana': 88}
# Remover outro aluno
del grades["Bob"]
print(grades) # Output: {'Alice': 95, 'Diana': 88}Se você tentar deletar uma chave que não existe, o Python levanta um KeyError:
grades = {"Alice": 95, "Bob": 87}
# AVISO: KeyError - apenas para demonstração
# del grades["Charlie"] # PROBLEM: KeyError: 'Charlie'Para deletar com segurança uma chave que pode não existir, verifique primeiro:
grades = {"Alice": 95, "Bob": 87}
# Exclusão segura
if "Charlie" in grades:
del grades["Charlie"]
else:
print("Charlie not found") # Output: Charlie not found16.3.2) Removendo e Recuperando com pop()
O método pop() remove uma chave e retorna seu valor. Isso é útil quando você precisa tanto remover uma entrada quanto usar o valor dela:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Remover e pegar a nota do Bob
bob_grade = grades.pop("Bob")
print(bob_grade) # Output: 87
print(grades) # Output: {'Alice': 95, 'Charlie': 92}
# Remover e usar o valor
charlie_grade = grades.pop("Charlie")
print(f"Charlie's final grade was {charlie_grade}") # Output: Charlie's final grade was 92
print(grades) # Output: {'Alice': 95}Assim como del, pop() levanta um KeyError se a chave não existir. Porém, você pode fornecer um valor padrão para retornar no lugar:
grades = {"Alice": 95, "Bob": 87}
# Pop com valor padrão (a chave não existe)
diana_grade = grades.pop("Diana", 0)
print(diana_grade) # Output: 0
print(grades) # Output: {'Alice': 95, 'Bob': 87} (unchanged)
# Pop com valor padrão (a chave existe)
alice_grade = grades.pop("Alice", 0)
print(alice_grade) # Output: 95
print(grades) # Output: {'Bob': 87}16.3.3) Removendo Todas as Entradas com clear()
O método clear() remove todos os pares chave-valor de um dicionário, deixando-o vazio:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(grades) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
# Limpar todas as entradas
grades.clear()
print(grades) # Output: {}
print(len(grades)) # Output: 0Isso é mais explícito do que reatribuir para um dicionário vazio (grades = {}), especialmente quando outras variáveis referenciam o mesmo dicionário:
# Demonstrar a diferença
grades = {"Alice": 95, "Bob": 87}
backup = grades # backup referencia o mesmo dicionário
# Usando clear() - afeta ambas as variáveis
grades.clear()
print(grades) # Output: {}
print(backup) # Output: {} (same dictionary was cleared)
# Resetar para o próximo exemplo
grades = {"Alice": 95, "Bob": 87}
backup = grades
# Reatribuir - afeta apenas grades
grades = {}
print(grades) # Output: {}
print(backup) # Output: {'Alice': 95, 'Bob': 87} (different dictionary now)Vamos explorar esse comportamento mais no Capítulo 18 quando discutirmos semântica de referência, mas por enquanto, lembre-se: clear() esvazia o dicionário existente, enquanto a reatribuição cria um novo dicionário vazio.
16.4) Objetos View do Dicionário: keys(), values() e items()
16.4.1) Entendendo Views de Dicionário
Dicionários fornecem três métodos que retornam objetos view (view objects) — objetos especiais que fornecem uma visão dinâmica das chaves, valores ou pares chave-valor do dicionário. Essas views refletem mudanças no dicionário automaticamente:
keys(): retorna uma visão de todas as chavesvalues(): retorna uma visão de todos os valoresitems(): retorna uma visão de todos os pares chave-valor (como tuplas)
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Obter visões
keys_view = grades.keys()
values_view = grades.values()
items_view = grades.items()
print(keys_view) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
print(values_view) # Output: dict_values([95, 87, 92])
print(items_view) # Output: dict_items([('Alice', 95), ('Bob', 87), ('Charlie', 92)])Isso não são listas — são objetos view. Mas você pode convertê-los em listas, se necessário:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Converter visões para listas
keys_list = list(grades.keys())
values_list = list(grades.values())
items_list = list(grades.items())
print(keys_list) # Output: ['Alice', 'Bob', 'Charlie']
print(values_list) # Output: [95, 87, 92]
print(items_list) # Output: [('Alice', 95), ('Bob', 87), ('Charlie', 92)]16.4.2) Views São Dinâmicas
Objetos view refletem mudanças no dicionário automaticamente:
grades = {"Alice": 95, "Bob": 87}
# Criar uma view
keys_view = grades.keys()
print(keys_view) # Output: dict_keys(['Alice', 'Bob'])
# Modificar o dicionário
grades["Charlie"] = 92
print(keys_view) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
# Remover uma entrada
del grades["Bob"]
print(keys_view) # Output: dict_keys(['Alice', 'Charlie'])Esse comportamento dinâmico é útil quando você precisa trabalhar com conteúdos do dicionário que podem mudar. Porém, se você precisa de uma cópia estática das chaves (um "snapshot"), converta a view em lista:
grades = {"Alice": 95, "Bob": 87}
# Criar um snapshot
keys_snapshot = list(grades.keys())
print(keys_snapshot) # Output: ['Alice', 'Bob']
# Modificar o dicionário
grades["Charlie"] = 92
print(keys_snapshot) # Output: ['Alice', 'Bob'] (unchanged)16.4.3) Trabalhando com keys()
O método keys() retorna uma view de todas as chaves do dicionário. Isso é útil para verificar quais chaves existem ou iterar sobre elas:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Obter todas as chaves
keys = grades.keys()
print(keys) # Output: dict_keys(['Alice', 'Bob', 'Charlie'])
# Verificar se uma chave existe
if "Alice" in keys:
print("Alice is in the grade book") # Output: Alice is in the grade book
# Contar chaves
print(f"Number of students: {len(keys)}") # Output: Number of students: 3Você também pode checar pertencimento diretamente no dicionário sem chamar keys():
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Estes são equivalentes
if "Alice" in grades.keys():
print("Found (using keys())")
if "Alice" in grades:
print("Found (direct check)") # This is more common and concise
# Output:
# Found (using keys())
# Found (direct check)16.4.4) Trabalhando com values()
O método values() retorna uma view de todos os valores do dicionário. Isso é útil quando você precisa processar valores sem se importar com suas chaves:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Obter todos os valores
values = grades.values()
print(values) # Output: dict_values([95, 87, 92, 88])
# Calcular estatísticas
total = sum(values)
count = len(values)
average = total / count
print(f"Total points: {total}") # Output: Total points: 362
print(f"Number of students: {count}") # Output: Number of students: 4
print(f"Average grade: {average}") # Output: Average grade: 90.5
# Encontrar maiores e menores notas
print(f"Highest grade: {max(values)}") # Output: Highest grade: 95
print(f"Lowest grade: {min(values)}") # Output: Lowest grade: 8716.4.5) Trabalhando com items()
O método items() retorna uma view dos pares chave-valor como tuplas. Esta é a view mais usada porque te dá tanto as chaves quanto os valores:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Obter todos os pares chave-valor
items = grades.items()
print(items) # Output: dict_items([('Alice', 95), ('Bob', 87), ('Charlie', 92)])
# Converter para lista para ver as tuplas com clareza
items_list = list(items)
print(items_list) # Output: [('Alice', 95), ('Bob', 87), ('Charlie', 92)]
# Acessar tuplas individuais
first_item = items_list[0]
print(first_item) # Output: ('Alice', 95)
print(first_item[0]) # Output: Alice
print(first_item[1]) # Output: 95A view items() é particularmente útil para iteração, que vamos cobrir em detalhes na próxima seção. Aqui vai uma prévia:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Processar cada par chave-valor
for name, grade in grades.items():
print(f"{name}: {grade}")
# Output:
# Alice: 95
# Bob: 87
# Charlie: 9216.5) Iterando Sobre Chaves, Valores e Itens
16.5.1) Iterando Sobre Chaves (Comportamento Padrão)
Quando você itera diretamente sobre um dicionário com um loop for, você itera sobre suas chaves:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Iterar sobre chaves (implícito)
for name in grades:
print(name)
# Output:
# Alice
# Bob
# CharlieIsso é equivalente a iterar sobre grades.keys():
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Iterar sobre chaves (explícito)
for name in grades.keys():
print(name)
# Output:
# Alice
# Bob
# CharlieAmbas as abordagens funcionam de forma idêntica. A versão implícita (sem .keys()) é mais comum e mais concisa.
Aqui vai um exemplo prático usando chaves para acessar valores:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Encontrar alunos que passaram (nota >= 90)
passing_students = []
for name in grades:
if grades[name] >= 90:
passing_students.append(name)
print("Students who passed:", passing_students) # Output: Students who passed: ['Alice', 'Charlie']16.5.2) Iterando Sobre Valores
Para iterar apenas sobre os valores, use values():
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Iterar sobre valores
for grade in grades.values():
print(grade)
# Output:
# 95
# 87
# 92
# 88Isso é útil quando você precisa processar valores, mas não se importa com qual chave eles estão associados:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Calcular total e média
total = 0
count = 0
for grade in grades.values():
total = total + grade
count = count + 1
average = total / count
print(f"Class average: {average}") # Output: Class average: 90.5
# Checar se todos os alunos passaram
all_passed = True
for grade in grades.values():
if grade < 60:
all_passed = False
break
if all_passed:
print("All students passed!") # Output: All students passed!16.5.3) Iterando Sobre Pares Chave-Valor com items()
O padrão de iteração mais comum e útil é usar items() para obter tanto chaves quanto valores:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Iterar sobre pares chave-valor
for name, grade in grades.items():
print(f"{name} scored {grade}")
# Output:
# Alice scored 95
# Bob scored 87
# Charlie scored 92Repare no desempacotamento de tupla: for name, grade in grades.items(). Cada item é uma tupla como ("Alice", 95), e nós a desempacotamos em duas variáveis. Isso é muito mais legível do que acessar índices de tupla:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Sem desempacotar (menos legível)
for item in grades.items():
print(f"{item[0]} scored {item[1]}")
# Com desempacotamento (mais legível)
for name, grade in grades.items():
print(f"{name} scored {grade}")
# Both produce the same output:
# Alice scored 95
# Bob scored 87
# Charlie scored 9216.5.4) Modificando Dicionários Durante a Iteração
Aviso: modificar o tamanho de um dicionário (adicionando ou removendo chaves) enquanto itera sobre ele pode causar erros ou comportamentos inesperados:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# AVISO: RuntimeError - apenas para demonstração
# for name in grades:
# if grades[name] < 90:
# del grades[name] # PROBLEM: RuntimeError: dictionary changed size during iterationNo Python moderno (3.7+), isso levanta imediatamente um RuntimeError assim que você tenta mudar o tamanho do dicionário. O Python detecta a modificação e para a execução para evitar comportamento imprevisível.
Em versões mais antigas do Python, isso poderia fazer o iterador:
- Pular itens que deveria processar
- Processar o mesmo item duas vezes
- Produzir resultados inconsistentes
Por isso o Python agora falha rápido com uma mensagem de erro clara.
Se você precisa modificar um dicionário durante a iteração, itere sobre uma cópia das chaves:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Seguro: iterar sobre uma cópia das chaves
for name in list(grades.keys()):
if grades[name] < 90:
del grades[name]
print(grades) # Output: {'Alice': 95, 'Charlie': 92}Ou monte um novo dicionário com apenas as entradas que você quer:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
# Montar um novo dicionário com entradas filtradas
high_grades = {}
for name, grade in grades.items():
if grade >= 90:
high_grades[name] = grade
print(high_grades) # Output: {'Alice': 95, 'Charlie': 92}A segunda abordagem costuma ser mais clara e segura. Vamos aprender formas ainda mais elegantes de fazer isso usando dictionary comprehensions no Capítulo 35.
16.6) Métodos Comuns de Dicionário
16.6.1) Verificando Chaves com in e not in
Os operadores in e not in verificam se uma chave existe em um dicionário:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
# Verificar se as chaves existem
if "Alice" in grades:
print("Alice is in the grade book") # Output: Alice is in the grade book
if "David" not in grades:
print("David is not in the grade book") # Output: David is not in the grade bookEsta é a forma preferida de checar a existência de uma chave antes de acessar valores. É mais legível e mais “Pythonic” do que usar get() e checar None:
grades = {"Alice": 95, "Bob": 87}
# Preferido: usando in
if "Alice" in grades:
print(f"Alice's grade: {grades['Alice']}") # Output: Alice's grade: 95
# Alternativa: usando get() e checando None
if grades.get("Alice") is not None:
print(f"Alice's grade: {grades['Alice']}") # Output: Alice's grade: 9516.6.2) Obtendo o Número de Entradas com len()
A função len() retorna o número de pares chave-valor em um dicionário:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(len(grades)) # Output: 3
# Dicionário vazio
empty = {}
print(len(empty)) # Output: 0
# Após modificações
grades["Diana"] = 88
print(len(grades)) # Output: 4
del grades["Bob"]
print(len(grades)) # Output: 3Isso é útil para verificar se um dicionário está vazio ou para relatar estatísticas:
grades = {"Alice": 95, "Bob": 87, "Charlie": 92, "Diana": 88}
if len(grades) == 0:
print("No students in grade book")
else:
total = sum(grades.values())
average = total / len(grades)
print(f"{len(grades)} students, average grade: {average}")
# Output: 4 students, average grade: 90.516.6.3) Copiando Dicionários com copy()
O método copy() cria uma cópia rasa (shallow copy) de um dicionário — um novo dicionário com os mesmos pares chave-valor:
original = {"Alice": 95, "Bob": 87}
duplicate = original.copy()
print(original) # Output: {'Alice': 95, 'Bob': 87}
print(duplicate) # Output: {'Alice': 95, 'Bob': 87}
# Modificar a cópia
duplicate["Charlie"] = 92
print(original) # Output: {'Alice': 95, 'Bob': 87} (unchanged)
print(duplicate) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}Isso é diferente de uma simples atribuição, que cria uma referência para o mesmo dicionário:
original = {"Alice": 95, "Bob": 87}
reference = original # Não é uma cópia - mesmo dicionário
# Modificar através da referência
reference["Charlie"] = 92
print(original) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92} (changed!)
print(reference) # Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}Vamos explorar cópias rasas vs profundas em detalhes no Capítulo 18. Por enquanto, lembre-se: use copy() quando você quiser um duplicado independente de um dicionário.
16.6.4) Mesclando Dicionários com o Operador | (Python 3.9+)
O Python 3.9 introduziu o operador | para mesclar dicionários. O operador | cria um novo dicionário combinando todas as chaves de ambos os dicionários. Para chaves duplicadas, o valor do lado direito sobrescreve o valor do lado esquerdo. Ambos os dicionários originais permanecem inalterados.
defaults = {"theme": "light", "font_size": 12, "auto_save": True}
user_prefs = {"theme": "dark", "font_size": 14}
# Mesclar dicionários (user_prefs sobrescreve defaults)
config = defaults | user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}
# Dicionários originais inalterados
print(defaults) # Output: {'theme': 'light', 'font_size': 12, 'auto_save': True}
print(user_prefs) # Output: {'theme': 'dark', 'font_size': 14}Aqui vai outro exemplo mostrando como isso é útil para combinar dados de múltiplas fontes:
# Informações de produto de dois fornecedores
supplier_a = {
"laptop": {"price": 999.99, "stock": 15},
"mouse": {"price": 29.99, "stock": 50}
}
supplier_b = {
"laptop": {"price": 949.99, "stock": 20}, # Preço e estoque melhores
"keyboard": {"price": 79.99, "stock": 30}
}
# Mesclar: dados de supplier_b sobrescrevem supplier_a para produtos correspondentes
combined = supplier_a | supplier_b
print(combined)
# Output: {'laptop': {'price': 949.99, 'stock': 20}, 'mouse': {'price': 29.99, 'stock': 50}, 'keyboard': {'price': 79.99, 'stock': 30}}
# Agora temos laptop de supplier_b (melhor negócio), mouse de supplier_a e keyboard de supplier_bO operador |= mescla “in place” (modifica o dicionário da esquerda):
config = {"theme": "light", "font_size": 12, "auto_save": True}
user_prefs = {"theme": "dark", "font_size": 14}
# Mesclar in place
config |= user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14, 'auto_save': True}Isso é equivalente a usar update(), mas mais conciso:
config = {"theme": "light", "font_size": 12}
user_prefs = {"theme": "dark", "font_size": 14}
# Estes são equivalentes
config.update(user_prefs)
# config |= user_prefs
print(config) # Output: {'theme': 'dark', 'font_size': 14}16.7) Dicionários Aninhados para Dados Estruturados
16.7.1) Criando Dicionários Aninhados
Por que usar dicionários aninhados? Imagine acompanhar informações de alunos. Você poderia criar dicionários separados: ages = {"Alice": 20, "Bob": 19}, majors = {"Alice": "CS", "Bob": "Math"}, gpas = {"Alice": 3.8, "Bob": 3.6}. Mas isso fica difícil de gerenciar — você precisa manter três dicionários sincronizados, e buscar todas as informações de um aluno exige três buscas separadas. Dicionários aninhados resolvem isso agrupando dados relacionados: um nome de aluno mapeia para todas as informações dele em uma única busca.
Um dicionário aninhado (nested dictionary) é um dicionário que contém outros dicionários como valores. Isso é útil para representar dados estruturados e hierárquicos:
# Registros de alunos com múltiplos atributos
students = {
"Alice": {
"age": 20,
"major": "Computer Science",
"gpa": 3.8
},
"Bob": {
"age": 19,
"major": "Mathematics",
"gpa": 3.6
},
"Charlie": {
"age": 21,
"major": "Physics",
"gpa": 3.9
}
}
print(students)
# Output: {'Alice': {'age': 20, 'major': 'Computer Science', 'gpa': 3.8}, 'Bob': {'age': 19, 'major': 'Mathematics', 'gpa': 3.6}, 'Charlie': {'age': 21, 'major': 'Physics', 'gpa': 3.9}}Cada nome de aluno mapeia para outro dicionário contendo seus atributos. Essa estrutura é muito mais flexível e fácil de manter do que usar dicionários separados para cada atributo.
16.7.2) Acessando Valores Aninhados
Para acessar valores aninhados, use múltiplos colchetes:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6}
}
# Acessar valores aninhados
alice_age = students["Alice"]["age"]
print(alice_age) # Output: 20
bob_major = students["Bob"]["major"]
print(bob_major) # Output: Mathematics
# Usar em expressões
print(f"Alice is {students['Alice']['age']} years old")
# Output: Alice is 20 years old
print(f"Bob's GPA: {students['Bob']['gpa']}")
# Output: Bob's GPA: 3.6Cada colchete acessa um nível do aninhamento. Primeiro students["Alice"] pega o dicionário interno, depois ["age"] pega o valor de idade desse dicionário.
16.7.3) Acessando Valores Aninhados com Segurança
Ao acessar dicionários aninhados, você precisa verificar se cada nível existe:
students = {
"Alice": {"age": 20, "major": "Computer Science"},
"Bob": {"age": 19} # Bob não declarou uma área
}
# Acesso inseguro - pode levantar KeyError
# print(students["Bob"]["major"]) # PROBLEM: KeyError: 'major'
# Acesso seguro com múltiplas verificações
if "Bob" in students:
if "major" in students["Bob"]:
print(f"Bob's major: {students['Bob']['major']}")
else:
print("Bob hasn't declared a major") # Output: Bob hasn't declared a major
# Usando get() para acesso aninhado seguro
bob_major = students.get("Bob", {}).get("major", "Undeclared")
print(f"Bob's major: {bob_major}") # Output: Bob's major: UndeclaredA cadeia de get() funciona porque, se "Bob" não existir, get() retorna um dicionário vazio {}, e então o segundo get() retorna com segurança "Undeclared".
16.7.4) Modificando Dicionários Aninhados
Você pode adicionar, atualizar ou remover entradas em dicionários aninhados:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6}
}
# Atualizar um valor aninhado
students["Alice"]["gpa"] = 3.9
print(f"Alice's new GPA: {students['Alice']['gpa']}") # Output: Alice's new GPA: 3.9
# Adicionar um novo atributo a um aluno existente
students["Bob"]["email"] = "bob@university.edu"
print(students["Bob"])
# Output: {'age': 19, 'major': 'Mathematics', 'gpa': 3.6, 'email': 'bob@university.edu'}
# Adicionar um novo aluno com dados aninhados
students["Charlie"] = {
"age": 21,
"major": "Physics",
"gpa": 3.7
}
print(f"Number of students: {len(students)}") # Output: Number of students: 3
# Remover um atributo
del students["Bob"]["email"]
print(students["Bob"])
# Output: {'age': 19, 'major': 'Mathematics', 'gpa': 3.6}16.7.5) Iterando Sobre Dicionários Aninhados
Você pode iterar sobre dicionários aninhados usando loops aninhados:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6},
"Charlie": {"age": 21, "major": "Physics", "gpa": 3.9}
}
# Iterar sobre alunos e seus atributos
for name, info in students.items():
print(f"\n{name}:")
for key, value in info.items():
print(f" {key}: {value}")
# Output:
# Alice:
# age: 20
# major: Computer Science
# gpa: 3.8
#
# Bob:
# age: 19
# major: Mathematics
# gpa: 3.6
#
# Charlie:
# age: 21
# major: Physics
# gpa: 3.9Aqui vai um exemplo prático encontrando alunos que atendem a certos critérios:
students = {
"Alice": {"age": 20, "major": "Computer Science", "gpa": 3.8},
"Bob": {"age": 19, "major": "Mathematics", "gpa": 3.6},
"Charlie": {"age": 21, "major": "Physics", "gpa": 3.9},
"Diana": {"age": 20, "major": "Computer Science", "gpa": 3.5}
}
# Encontrar alunos de CS com GPA acima de 3.7
print("High-performing CS students:")
for name, info in students.items():
if info["major"] == "Computer Science" and info["gpa"] >= 3.7:
print(f" {name} (GPA: {info['gpa']})")
# Output:
# High-performing CS students:
# Alice (GPA: 3.8)Dicionários são uma das estruturas de dados mais poderosas e versáteis do Python. Eles fornecem buscas rápidas, organização flexível e soluções elegantes para problemas comuns de programação. Conforme você continuar aprendendo Python, você vai encontrar dicionários em todo lugar — de configurações a processamento de dados e construção de aplicações complexas.
Os padrões que cobrimos neste capítulo — contagem, agrupamento, tabelas de consulta e transformação de dados — formam a base para trabalhar com dados estruturados em Python. No próximo capítulo, vamos explorar conjuntos, outro tipo de coleção que complementa dicionários ao trabalhar com dados únicos e não ordenados.