15. Tuplas e Ranges: Sequências Imutáveis Simples
No Capítulo 14, exploramos listas (list) — o tipo de sequência versátil e mutável do Python. Agora vamos examinar outros dois tipos de sequência importantes: tuplas (tuple) e ranges (range). Enquanto listas se destacam ao armazenar coleções que mudam ao longo do tempo, tuplas fornecem sequências imutáveis que protegem dados contra modificação, e ranges oferecem formas eficientes em memória de representar sequências de números.
Entender quando usar cada tipo de sequência vai deixar seus programas mais eficientes, mais seguros e com intenção mais clara. Ao final deste capítulo, você vai saber como trabalhar com tuplas e ranges de forma eficaz, e vai entender as operações comuns que funcionam em todos os tipos de sequência do Python.
15.1) Criando e Usando Tuplas (O Significado da Vírgula)
Uma tupla (tuple) é uma sequência ordenada e imutável de itens. Assim como listas, tuplas podem conter qualquer tipo de dado e mantêm a ordem dos elementos. Porém, diferentemente de listas, depois que você cria uma tupla, não dá para modificar seu conteúdo.
Criando Tuplas com Parênteses
A forma mais comum de criar uma tupla é colocando valores separados por vírgula dentro de parênteses:
# Uma tupla de notas de prova de alunos
scores = (85, 92, 78, 95)
print(scores) # Output: (85, 92, 78, 95)
print(type(scores)) # Output: <class 'tuple'>
# Uma tupla de tipos de dados mistos
student_info = ("Alice", 20, "Computer Science", 3.8)
print(student_info) # Output: ('Alice', 20, 'Computer Science', 3.8)
# Uma tupla vazia
empty = ()
print(empty) # Output: ()
print(len(empty)) # Output: 0Tuplas usam parênteses () como sintaxe literal, enquanto listas usam colchetes []. Essa distinção visual ajuda você a reconhecer imediatamente com qual tipo está trabalhando.
A Vírgula Cria a Tupla, Não os Parênteses
Aqui vai um detalhe crucial que surpreende muitos iniciantes: a vírgula é o que realmente cria uma tupla, não os parênteses. Os parênteses geralmente são opcionais e servem principalmente para deixar a tupla mais visível ou para agrupá-la em expressões.
# Todas estas criam a mesma tupla
coordinates_1 = (10, 20)
coordinates_2 = 10, 20 # Não precisa de parênteses!
print(coordinates_1) # Output: (10, 20)
print(coordinates_2) # Output: (10, 20)
print(coordinates_1 == coordinates_2) # Output: True
# A vírgula é o que importa
x = (42) # Isso é apenas o inteiro 42 entre parênteses
y = (42,) # Isso é uma tupla contendo um elemento
print(type(x)) # Output: <class 'int'>
print(type(y)) # Output: <class 'tuple'>
print(y) # Output: (42,)Os parênteses em (42) são apenas parênteses de agrupamento, como em expressões matemáticas. Para criar uma tupla com um único elemento, você precisa incluir uma vírgula no final: (42,). Essa vírgula diz ao Python que você quer uma tupla, e não apenas uma expressão agrupada.
Quando os Parênteses São Necessários
Embora a vírgula crie a tupla, os parênteses se tornam necessários em certas situações para evitar ambiguidade:
# Sem parênteses, isto ficaria confuso
def get_dimensions():
return 1920, 1080 # Retorna uma tupla
width, height = get_dimensions()
print(f"Screen: {width}x{height}") # Output: Screen: 1920x1080
# Parênteses necessários ao passar tuplas como argumentos de função
print((1, 2, 3)) # Output: (1, 2, 3)
# Sem parênteses, o Python veria três argumentos separados
# Parênteses necessários em expressões complexas
result = (10, 20) + (30, 40) # Concatenação de tuplas
print(result) # Output: (10, 20, 30, 40)Criando Tuplas de Um Único Elemento
A exigência da vírgula no final para tuplas de um único elemento frequentemente pega iniciantes de surpresa:
# Erro comum: esquecer a vírgula
not_a_tuple = ("Python")
print(type(not_a_tuple)) # Output: <class 'str'>
print(not_a_tuple) # Output: Python
# Correto: incluir a vírgula no final
is_a_tuple = ("Python",)
print(type(is_a_tuple)) # Output: <class 'tuple'>
print(is_a_tuple) # Output: ('Python',)
# A vírgula funciona mesmo sem parênteses
also_a_tuple = "Python",
print(type(also_a_tuple)) # Output: <class 'tuple'>
print(also_a_tuple) # Output: ('Python',)Por que o Python exige essa sintaxe aparentemente estranha? Porque parênteses já têm outro significado em Python — eles agrupam expressões. Sem a vírgula, o Python não tem como distinguir entre (42) como um número agrupado e (42) como uma tupla.
Acessando Elementos de uma Tupla
Tuplas suportam as mesmas operações de indexação e fatiamento que listas:
# Tupla com informações do aluno
student = ("Bob", 22, "Physics", 3.6)
# Acessando elementos individuais (indexação começando em zero)
name = student[0]
age = student[1]
major = student[2]
gpa = student[3]
print(f"{name} is {age} years old") # Output: Bob is 22 years old
print(f"Major: {major}, GPA: {gpa}") # Output: Major: Physics, GPA: 3.6
# Indexação negativa também funciona
last_item = student[-1]
print(f"Last item: {last_item}") # Output: Last item: 3.6
# Fatiamento extrai uma nova tupla
first_two = student[:2]
print(first_two) # Output: ('Bob', 22)
print(type(first_two)) # Output: <class 'tuple'>Toda técnica de indexação e fatiamento que você aprendeu com listas no Capítulo 14 funciona de forma idêntica com tuplas. A principal diferença é que tuplas não podem ser modificadas depois de criadas.
15.2) Packing e Unpacking de Tuplas
Uma das funcionalidades mais poderosas e elegantes das tuplas é a capacidade de empacotar (packing) vários valores juntos e desempacotá-los (unpacking) em variáveis separadas. Esse recurso deixa o código Python notavelmente conciso e legível.
Packing de Tuplas
Empacotamento de tuplas (tuple packing) acontece quando você cria uma tupla colocando vários valores juntos, separados por vírgulas:
# Empacotando valores em uma tupla
coordinates = 10, 20, 30
print(coordinates) # Output: (10, 20, 30)
# Empacotando tipos diferentes
user_data = "Alice", 25, "alice@example.com"
print(user_data) # Output: ('Alice', 25, 'alice@example.com')
# Empacotando valores de retorno de função
def get_statistics(numbers):
total = sum(numbers)
count = len(numbers)
average = total / count
return total, count, average # Empacota três valores em uma tupla
stats = get_statistics([85, 90, 78, 92, 88])
print(stats) # Output: (433, 5, 86.6)Quando uma função retorna vários valores separados por vírgulas, o Python automaticamente os empacota em uma tupla. É por isso que funções podem parecer retornar vários valores — elas na verdade estão retornando uma única tupla contendo esses valores.
Unpacking de Tuplas
Desempacotamento de tuplas (tuple unpacking) é o processo inverso: extrair valores de uma tupla para variáveis separadas:
# Desempacotamento básico
point = (100, 200)
x, y = point
print(f"x = {x}, y = {y}") # Output: x = 100, y = 200
# Desempacotamento funciona com qualquer sequência, não só tuplas
name, age, email = ["Bob", 30, "bob@example.com"]
print(f"{name} is {age} years old") # Output: Bob is 30 years old
# Desempacotando valores de retorno de função diretamente
total, count, average = get_statistics([95, 88, 92, 85])
print(f"Average of {count} scores: {average}") # Output: Average of 4 scores: 90.0O número de variáveis do lado esquerdo precisa corresponder ao número de elementos na sequência. Se não corresponder, o Python lança um ValueError:
# Isto vai causar um erro
coordinates = (10, 20, 30)
# x, y = coordinates # ValueError: too many values to unpack (expected 2)
# Isto também vai causar um erro
point = (5, 10)
# x, y, z = point # ValueError: not enough values to unpack (expected 3, got 2)Trocando Variáveis com Unpacking de Tuplas
O desempacotamento de tuplas permite uma forma elegante de trocar valores de variáveis sem precisar de uma variável temporária:
# Troca tradicional usando uma variável temporária
a = 10
b = 20
temp = a
a = b
b = temp
print(f"a = {a}, b = {b}") # Output: a = 20, b = 10
# Troca elegante do Python usando desempacotamento de tuplas
x = 100
y = 200
x, y = y, x # Troca em uma linha!
print(f"x = {x}, y = {y}") # Output: x = 200, y = 100
# Trocando mais de duas variáveis
first = "A"
second = "B"
third = "C"
first, second, third = third, first, second
print(first, second, third) # Output: C A BComo isso funciona? O Python avalia primeiro o lado direito, criando uma tupla (y, x), e então a desempacota nas variáveis do lado esquerdo. Isso acontece em um único passo, então nenhuma variável temporária é necessária.
Unpacking Estendido com o Operador Star
O Python fornece desempacotamento estendido (extended unpacking) usando o operador * para capturar vários elementos:
# Desempacotando com uma variável "resto"
scores = (95, 88, 92, 85, 90, 87)
first, second, *rest = scores
print(f"Top two: {first}, {second}") # Output: Top two: 95, 88
print(f"Others: {rest}") # Output: Others: [92, 85, 90, 87]
print(type(rest)) # Output: <class 'list'>
# O star pode aparecer em qualquer lugar
numbers = (1, 2, 3, 4, 5)
first, *middle, last = numbers
print(f"First: {first}") # Output: First: 1
print(f"Middle: {middle}") # Output: Middle: [2, 3, 4]
print(f"Last: {last}") # Output: Last: 5
# Capturando o começo
*beginning, second_last, last = numbers
print(f"Beginning: {beginning}") # Output: Beginning: [1, 2, 3]
print(f"Last two: {second_last}, {last}") # Output: Last two: 4, 5Repare que a variável com star sempre captura elementos como uma lista (list), mesmo quando você está desempacotando a partir de uma tupla. Se não houver elementos para capturar, a variável com star vira uma lista vazia:
# Quando não há nada para capturar
a, b, *rest = (10, 20)
print(rest) # Output: []
# Só é permitido um star por desempacotamento
# first, *middle, *end = (1, 2, 3, 4) # SyntaxError: multiple starred expressionsIgnorando Valores com Underscore
Às vezes você só precisa de certos valores de uma tupla. Por convenção, programadores Python usam underscore _ como nome de variável para indicar valores que querem ignorar:
# Fazendo parsing de uma string de data
date_string = "2024-03-15"
year, month, day = date_string.split("-")
print(f"Month: {month}") # Output: Month: 03
# Se só nos importamos com o mês
_, month, _ = date_string.split("-")
print(f"Month: {month}") # Output: Month: 03
# Com desempacotamento estendido
data = ("Alice", 25, "Engineer", "New York", "alice@example.com")
name, age, *_, email = data
print(f"{name} ({age}): {email}") # Output: Alice (25): alice@example.comO underscore é só um nome de variável comum, mas usá-lo sinaliza para outros programadores (e para você mesmo) que você está ignorando aqueles valores de propósito.
Exemplos Práticos de Packing e Unpacking
# Retornando vários valores de cálculos
def calculate_rectangle_properties(width, height):
"""Calculate area and perimeter of a rectangle."""
area = width * height
perimeter = 2 * (width + height)
return area, perimeter # Empacotamento
# Desempacotando os resultados
rect_area, rect_perimeter = calculate_rectangle_properties(5, 3)
print(f"Area: {rect_area}, Perimeter: {rect_perimeter}") # Output: Area: 15, Perimeter: 16
# Iterando com desempacotamento
students = [
("Alice", 85),
("Bob", 92),
("Carol", 78)
]
for name, score in students: # Desempacotamento no loop
print(f"{name}: {score}")
# Output:
# Alice: 85
# Bob: 92
# Carol: 78Empacotamento e desempacotamento de tuplas deixam o código Python mais legível e expressivo. Em vez de acessar elementos da tupla por índice (student[0], student[1]), você pode desempacotá-los em variáveis com nomes significativos.
15.3) Tuplas São Imutáveis: Quando Isso É Útil
A característica definidora das tuplas é a imutabilidade (immutability) — uma vez criada, o conteúdo de uma tupla não pode ser alterado. Você não pode adicionar, remover ou modificar elementos. Essa imutabilidade pode parecer uma limitação, mas ela traz benefícios importantes.
O Que Imutabilidade Significa na Prática
# Criando uma tupla
coordinates = (10, 20, 30)
print(coordinates) # Output: (10, 20, 30)
# Tentar modificar gera um erro
# coordinates[0] = 15 # TypeError: 'tuple' object does not support item assignment
# Tentar adicionar elementos gera um erro
# coordinates.append(40) # AttributeError: 'tuple' object has no attribute 'append'
# Tentar remover elementos gera um erro
# del coordinates[1] # TypeError: 'tuple' object doesn't support item deletionQuando o Python diz que tuplas não suportam atribuição de item, isso significa que você não pode mudar o que está armazenado em nenhuma posição na tupla. A estrutura da tupla fica fixa na criação.
Comparando Listas Mutáveis e Tuplas Imutáveis
# Listas são mutáveis - você pode alterá-las
shopping_list = ["milk", "bread", "eggs"]
shopping_list[1] = "butter" # Modifica um elemento
shopping_list.append("cheese") # Adiciona um elemento
print(shopping_list) # Output: ['milk', 'butter', 'eggs', 'cheese']
# Tuplas são imutáveis - você não pode alterá-las
product_dimensions = (10, 20, 5) # width, height, depth in cm
# product_dimensions[0] = 12 # TypeError: cannot modify
# product_dimensions.append(3) # AttributeError: no append method
# Para "mudar" uma tupla, você precisa criar uma nova
new_dimensions = (12, 20, 5) # Cria uma tupla completamente nova
print(new_dimensions) # Output: (12, 20, 5)Por Que a Imutabilidade É Útil
A imutabilidade oferece vários benefícios práticos:
1. Integridade e Segurança dos Dados
Quando você passa uma tupla para uma função (function), você sabe que a função não pode modificar seus dados por acidente:
def calculate_distance(point1, point2):
"""Calculate distance between two 2D points."""
x1, y1 = point1
x2, y2 = point2
dx = x2 - x1
dy = y2 - y1
# Mesmo que quiséssemos, não conseguimos modificar as tuplas de entrada
return (dx**2 + dy**2) ** 0.5
start = (0, 0)
end = (3, 4)
distance = calculate_distance(start, end)
print(f"Distance: {distance}") # Output: Distance: 5.0
print(f"Start point unchanged: {start}") # Output: Start point unchanged: (0, 0)Com listas, você precisaria se preocupar se uma função poderia modificar seus dados. Com tuplas, você tem a garantia de que não vai.
2. Usando Tuplas como Chaves de Dicionário
Como vamos explorar mais no Capítulo 17, chaves de dicionário (dictionary) precisam ser hashable (hashable) — elas devem ter um valor de hash que nunca muda. Objetos imutáveis como tuplas podem ser chaves de dicionário; objetos mutáveis como listas não podem:
# Tuplas podem ser chaves de dicionário
locations = {
(0, 0): "Origin",
(10, 20): "Point A",
(30, 40): "Point B"
}
print(locations[(10, 20)]) # Output: Point A
# Listas não podem ser chaves de dicionário
# locations_bad = {
# [0, 0]: "Origin" # TypeError: unhashable type: 'list'
# }3. Sinalizando Intenção
Usar uma tupla em vez de uma lista comunica para outros programadores (e para você) que esses dados não devem mudar:
# Valores de cor RGB - estes nunca deveriam mudar
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# Parâmetros de conexão com banco - configuração fixa
DB_CONFIG = ("localhost", 5432, "myapp", "production")
# Coordenadas geográficas - um local não muda
EIFFEL_TOWER = (48.8584, 2.2945) # latitude, longitudeQuando você vê uma tupla no código, você imediatamente sabe que esses dados devem permanecer constantes. Quando você vê uma lista, você sabe que ela pode ser modificada.
4. Benefícios de Performance
Como tuplas são imutáveis, o Python pode otimizá-las de maneiras que não consegue otimizar listas. Vamos aprender sobre o módulo sys no Capítulo 27, mas por enquanto saiba apenas que sys.getsizeof() nos diz quanta memória um objeto usa:
import sys
# Tuplas usam menos memória do que listas equivalentes
tuple_data = (1, 2, 3, 4, 5)
list_data = [1, 2, 3, 4, 5]
print(f"Tuple size: {sys.getsizeof(tuple_data)} bytes") # Output: Tuple size: 80 bytes (may vary by Python version)
print(f"List size: {sys.getsizeof(list_data)} bytes") # Output: List size: 104 bytes (may vary by Python version)
# Criar tuplas é mais rápido
import timeit
tuple_time = timeit.timeit("(1, 2, 3, 4, 5)", number=1000000)
list_time = timeit.timeit("[1, 2, 3, 4, 5]", number=1000000)
print(f"Tuple creation: {tuple_time:.4f} seconds")
print(f"List creation: {list_time:.4f} seconds")
# Example output: Tuple creation: 0.0055 seconds, List creation: 0.0292 seconds15.4) A Armadilha da Imutabilidade: Quando Tuplas Contêm Itens Mutáveis
Embora as tuplas em si sejam imutáveis, elas podem conter objetos mutáveis como listas ou dicionários. Isso cria uma distinção sutil, mas importante: a estrutura da tupla é fixa, mas o conteúdo dos objetos mutáveis dentro dela ainda pode mudar.
Entendendo a Distinção
# Uma tupla contendo uma lista
student_data = ("Alice", 20, [85, 90, 78]) # name, age, scores
print(student_data) # Output: ('Alice', 20, [85, 90, 78])
# Não podemos reatribuir elementos da tupla
# student_data[0] = "Bob" # TypeError: 'tuple' object does not support item assignment
# Mas PODEMOS modificar a lista dentro da tupla
student_data[2].append(92) # Adiciona uma nova nota
print(student_data) # Output: ('Alice', 20, [85, 90, 78, 92])
student_data[2][0] = 88 # Modifica uma nota existente
print(student_data) # Output: ('Alice', 20, [88, 90, 78, 92])O que está acontecendo aqui? A tupla armazena três referências: uma para a string "Alice", uma para o inteiro 20 e uma para um objeto lista. A estrutura da tupla — quais objetos ela referencia — não pode mudar. Mas o objeto lista em si é mutável, então seu conteúdo pode mudar.
Visualizando a Diferença
# A estrutura da tupla é fixa
data = ("Python", [1, 2, 3])
# Isso tenta mudar o que a tupla referencia - NÃO PERMITIDO
# data[1] = [4, 5, 6] # TypeError
# Isso modifica a lista que a tupla referencia - PERMITIDO
data[1].append(4)
print(data) # Output: ('Python', [1, 2, 3, 4])
# A tupla ainda referencia o mesmo objeto lista
# Só o conteúdo da lista mudou, não qual lista a tupla apontaPense assim: uma tupla é como uma fileira de caixas, e cada caixa contém uma referência para um objeto. As caixas em si ficam travadas no lugar (imutáveis), mas se uma caixa contém uma referência para um objeto mutável, esse objeto ainda pode mudar.
Tuplas com Dicionários
O mesmo princípio se aplica a dicionários dentro de tuplas:
# Tupla contendo um dicionário
user_profile = ("alice", {"email": "alice@example.com", "age": 25})
print(user_profile) # Output: ('alice', {'email': 'alice@example.com', 'age': 25})
# Não dá para mudar qual dicionário a tupla referencia
# user_profile[1] = {"email": "newemail@example.com"} # TypeError
# Mas dá para modificar o próprio dicionário
user_profile[1]["age"] = 26
user_profile[1]["city"] = "New York"
print(user_profile) # Output: ('alice', {'email': 'alice@example.com', 'age': 26, 'city': 'New York'})Por Que Isso Importa para Chaves de Dicionário
Tuplas podem ser usadas como chaves de dicionário somente se todos os seus elementos forem hashable. Embora tuplas em si sejam imutáveis, uma tupla que contém objetos mutáveis (como listas) não é hashable de forma alguma e, portanto, não pode ser usada como chave de dicionário.
# Isto funciona mas é perigoso
tuple_with_list = ("key", [1, 2, 3])
# data = {tuple_with_list: "value"} # TypeError: unhashable type: 'list'Use apenas tuplas que contenham objetos totalmente imutáveis (strings, números, frozensets, outras tuplas) como chaves de dicionário.
Criando Tuplas Verdadeiramente Imutáveis
Se você precisa de uma tupla completamente imutável, garanta que todo o seu conteúdo também seja imutável:
# Tupla totalmente imutável - apenas tipos imutáveis
point_3d = (10, 20, 30) # All integers
rgb_color = (255, 128, 0) # All integers
coordinates = ((10, 20), (30, 40)) # Tuple of tuples
# Estas são seguras para usar como chaves de dicionário
color_names = {
(255, 0, 0): "Red",
(0, 255, 0): "Green",
(0, 0, 255): "Blue"
}
# Tuplas aninhadas continuam imutáveis
nested = ((1, 2), (3, 4))
# nested[0][0] = 5 # TypeError: 'tuple' object does not support item assignmentQuando Conteúdo Mutável É Intencional
Às vezes você realmente quer uma tupla com conteúdo mutável — por exemplo, quando você tem uma estrutura fixa de registro, mas um campo precisa mudar:
# Registro de aluno com identidade fixa, mas notas mudando
def create_student(name, student_id):
"""Create a student record with empty grade list."""
return (name, student_id, []) # name and ID fixed, grades can change
student = create_student("Alice", "S12345")
print(student) # Output: ('Alice', 'S12345', [])
# A identidade do aluno é fixa
print(f"Student: {student[0]} (ID: {student[1]})") # Output: Student: Alice (ID: S12345)
# Mas podemos adicionar notas à medida que elas são obtidas
student[2].append(85)
student[2].append(92)
student[2].append(78)
print(f"Grades: {student[2]}") # Output: Grades: [85, 92, 78]
# A estrutura da tupla protege name e ID de mudanças acidentais
# enquanto permite que a lista de notas cresçaEsse padrão é útil quando você quer proteger alguns dados enquanto permite que outros dados mudem. Só fique atento à diferença entre a imutabilidade da tupla e a mutabilidade do seu conteúdo.
15.5) Quando Usar Tuplas em Vez de Listas
Escolher entre tuplas e listas é uma decisão de design importante. Embora ambas sejam sequências, elas servem a propósitos diferentes e comunicam intenções diferentes.
Use Tuplas para Dados Fixos e Heterogêneos
Tuplas funcionam melhor quando você tem um número fixo de itens que representam uma única entidade lógica, frequentemente com tipos diferentes:
# Registro de aluno: nome, idade, curso, GPA
student = ("Alice", 20, "Computer Science", 3.8)
# Coordenadas geográficas: latitude, longitude
location = (40.7128, -74.0060) # New York City
# Cor RGB: vermelho, verde, azul
color = (255, 128, 0)
# Conexão com banco: host, port, database, username
db_connection = ("localhost", 5432, "myapp", "admin")
# Data: ano, mês, dia
date = (2024, 3, 15)Cada tupla representa um "registro" completo em que a posição de cada elemento tem um significado específico. O primeiro elemento é sempre o nome, o segundo é sempre a idade, e assim por diante.
Use Listas para Coleções Homogêneas
Listas funcionam melhor quando você tem um número variável de itens similares que você pode adicionar, remover ou reordenar:
# Lista de compras - itens do mesmo tipo (strings)
shopping_list = ["milk", "bread", "eggs", "butter"]
shopping_list.append("cheese") # Adiciona mais itens conforme necessário
shopping_list.remove("bread") # Remove itens
# Notas de prova - itens do mesmo tipo (números)
test_scores = [85, 92, 78, 95, 88]
test_scores.append(90) # Adiciona nova nota
test_scores.sort() # Reordena as notas
# Nomes de usuário - itens do mesmo tipo (strings)
active_users = ["alice", "bob", "carol"]
active_users.extend(["dave", "eve"]) # Adiciona vários usuáriosListas são para coleções em que o número de itens pode mudar e em que cada item cumpre o mesmo papel.
Tuplas para Valores de Retorno de Funções
Quando uma função retorna vários valores relacionados, tuplas são a escolha natural:
def get_user_info(user_id):
"""Retrieve user information from database."""
# Simula busca no banco de dados
return "Alice", "alice@example.com", 25, "New York"
# Desempacota a tupla retornada
name, email, age, city = get_user_info(101)
print(f"{name} from {city}") # Output: Alice from New York
def calculate_statistics(numbers):
"""Calculate min, max, and average of numbers."""
if not numbers:
return None, None, None
minimum = min(numbers)
maximum = max(numbers)
average = sum(numbers) / len(numbers)
return minimum, maximum, average
# Desempacota os resultados
min_val, max_val, avg_val = calculate_statistics([85, 92, 78, 95, 88])
print(f"Range: {min_val} to {max_val}, Average: {avg_val}")
# Output: Range: 78 to 95, Average: 87.6Retornar tuplas deixa claro que esses valores são relacionados e devem ser considerados juntos.
Tuplas para Chaves de Dicionário
Quando você precisa de chaves compostas em um dicionário, tuplas são essenciais:
# Notas de alunos por disciplina e semestre
grades = {
("CS101", "Fall2023"): 85,
("CS101", "Spring2024"): 90,
("MATH201", "Fall2023"): 88,
("MATH201", "Spring2024"): 92
}
# Busca uma nota específica
course = "CS101"
semester = "Spring2024"
grade = grades[(course, semester)]
print(f"Grade in {course} ({semester}): {grade}") # Output: Grade in CS101 (Spring2024): 90
# Coordenadas de grid como chaves de dicionário
grid = {
(0, 0): "Start",
(5, 3): "Obstacle",
(10, 10): "Goal"
}
position = (5, 3)
if position in grid:
print(f"At {position}: {grid[position]}") # Output: At (5, 3): ObstacleListas não podem ser chaves de dicionário porque são mutáveis, mas tuplas podem.
Tuplas para Configuração Imutável
Quando você tem dados de configuração que nunca devem mudar, tuplas sinalizam essa intenção:
# Configurações do aplicativo que devem permanecer constantes
APP_CONFIG = (
"MyApp", # Nome do aplicativo
"1.0.0", # Versão
"production", # Ambiente
True, # Modo de debug
8080 # Porta
)
# Paleta de cores para UI - estas cores são fixas
COLOR_PALETTE = (
(255, 0, 0), # Vermelho primário
(0, 128, 255), # Azul primário
(255, 255, 255), # Branco
(0, 0, 0) # Preto
)
# Endpoints de API - estas URLs não mudam
API_ENDPOINTS = (
"https://api.example.com/users",
"https://api.example.com/products",
"https://api.example.com/orders"
)Guia de Decisão
# Use TUPLAS quando:
# 1. Os dados representam um único registro com estrutura fixa
employee = ("E001", "Alice", "Engineering", 75000)
# 2. Retornar vários valores de uma função
def divide_with_remainder(a, b):
return a // b, a % b
# 3. Precisa usar como chaves de dicionário
cache = {(5, 10): 50, (3, 7): 21}
# 4. Os dados não devem ser modificados
SCREEN_RESOLUTION = (1920, 1080)
# Use LISTAS quando:
# 1. Coleção de itens similares que pode mudar
tasks = ["Write code", "Test code", "Deploy code"]
tasks.append("Document code")
# 2. Precisa adicionar, remover ou reordenar itens
scores = [85, 90, 78]
scores.sort()
scores.append(92)
# 3. Todos os itens têm o mesmo propósito
usernames = ["alice", "bob", "carol"]
# 4. O tamanho da coleção não é conhecido de antemão
results = []
for i in range(10):
results.append(i * 2)15.6) Entendendo Objetos range em Profundidade
Agora que entendemos quando usar tuplas versus listas, vamos explorar o terceiro tipo de sequência imutável do Python: ranges. O tipo range representa uma sequência imutável de números. Diferentemente de listas e tuplas que armazenam todos os seus elementos em memória, objetos range geram números sob demanda, tornando-os extremamente eficientes em memória para representar sequências grandes.
Criando Objetos range
A função range() cria objetos range em três formas:
# Um argumento: range(stop)
# Gera números de 0 até (mas sem incluir) stop
numbers = range(5)
print(list(numbers)) # Output: [0, 1, 2, 3, 4]
# Dois argumentos: range(start, stop)
# Gera números de start até (mas sem incluir) stop
numbers = range(2, 7)
print(list(numbers)) # Output: [2, 3, 4, 5, 6]
# Três argumentos: range(start, stop, step)
# Gera números de start até stop, incrementando de step em step
numbers = range(0, 10, 2)
print(list(numbers)) # Output: [0, 2, 4, 6, 8]
# Step negativo para contar para trás
numbers = range(10, 0, -1)
print(list(numbers)) # Output: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]Repare que convertemos ranges para listas com list() para ver seu conteúdo. Um objeto range em si não exibe todos os seus valores quando é impresso:
r = range(5)
print(r) # Output: range(0, 5)
print(type(r)) # Output: <class 'range'>Como Objetos range Funcionam
Objetos range não armazenam todos os seus valores em memória. Em vez disso, eles calculam cada valor quando necessário:
import sys
# Um range representando um milhão de números
large_range = range(1000000)
print(f"Range size: {sys.getsizeof(large_range)} bytes") # Output: Range size: 48 bytes (may vary by Python version)
# Uma lista contendo um milhão de números
large_list = list(range(1000000))
print(f"List size: {sys.getsizeof(large_list)} bytes") # Output: List size: 8000056 bytes (approximately 8MB)
# O range é minúsculo; a lista é enorme!Um objeto range armazena apenas três valores: start, stop e step. Ele calcula cada número na sequência quando você pede. Isso torna ranges incrivelmente eficientes para sequências grandes.
Usando range em Loops for
Como aprendemos no Capítulo 12, ranges são mais comumente usados com loops (loop) for:
# Contando de 0 a 4
for i in range(5):
print(f"Count: {i}")
# Output:
# Count: 0
# Count: 1
# Count: 2
# Count: 3
# Count: 4
# Contando de 1 a 10
for i in range(1, 11):
print(i, end=" ")
print() # Output: 1 2 3 4 5 6 7 8 9 10
# Contando de dois em dois
for i in range(0, 20, 2):
print(i, end=" ")
print() # Output: 0 2 4 6 8 10 12 14 16 18
# Contando de trás para frente
for i in range(5, 0, -1):
print(f"T-minus {i}")
# Output:
# T-minus 5
# T-minus 4
# T-minus 3
# T-minus 2
# T-minus 1Indexação e Fatiamento de Objetos range
Objetos range suportam indexação e fatiamento como outras sequências:
# Criando um range
numbers = range(10, 50, 5) # 10, 15, 20, 25, 30, 35, 40, 45
# Indexação
print(numbers[0]) # Output: 10
print(numbers[3]) # Output: 25
print(numbers[-1]) # Output: 45
# Fatiamento retorna um novo range
subset = numbers[2:5]
print(subset) # Output: range(20, 35, 5)
print(list(subset)) # Output: [20, 25, 30]
# Comprimento
print(len(numbers)) # Output: 8Verificando Pertinência
Você pode verificar se um número está em um range usando o operador in:
# Números pares de 0 a 20
evens = range(0, 21, 2)
print(10 in evens) # Output: True
print(15 in evens) # Output: False
print(20 in evens) # Output: True
# Isto é muito eficiente - o Python não gera todos os números
# Ele calcula se o número estaria na sequência
large_range = range(0, 1000000, 3)
print(999999 in large_range) # Output: True (instant, no iteration needed)O Python consegue determinar a pertinência matematicamente sem gerar todos os números, tornando essa operação extremamente rápida mesmo para ranges enormes.
Ranges Vazios e Reversos
# Range vazio - stop é igual a start
empty = range(5, 5)
print(list(empty)) # Output: []
print(len(empty)) # Output: 0
# Range vazio - impossível alcançar stop com o step dado
impossible = range(1, 10, -1) # Não dá para contar para cima com step negativo
print(list(impossible)) # Output: []
# Range reverso
backwards = range(10, 0, -1)
print(list(backwards)) # Output: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
# Reverso com números negativos
negative_range = range(-5, -15, -2)
print(list(negative_range)) # Output: [-5, -7, -9, -11, -13]Quando Usar range vs Listas
# Use range quando:
# 1. Você precisa de uma sequência de números para iteração
for i in range(100):
# Processa algo 100 vezes
pass
# 2. Você precisa de índices para uma sequência
items = ["a", "b", "c", "d"]
for i in range(len(items)):
print(f"Index {i}: {items[i]}")
# 3. Eficiência de memória importa em sequências grandes
# Isto usa memória mínima
for i in range(1000000):
if i % 100000 == 0:
print(i)
# Use listas quando:
# 1. Você precisa armazenar os valores reais
squares = [1, 3, 5, 7, 10]
# 2. Você precisa modificar a sequência
numbers = list(range(5))
numbers[2] = 100 # Modifica um valor
numbers.append(200) # Adiciona um valor
# 3. Você precisa usar a sequência várias vezes com operações diferentes
data = list(range(10))
print(sum(data))
print(max(data))
print(sorted(data, reverse=True))Objetos range são um exemplo perfeito da eficiência do Python. Eles fornecem todos os benefícios de uma sequência sem o custo de memória de armazenar cada elemento.
15.7) Convertendo Entre Listas, Tuplas e Ranges
O Python facilita converter entre diferentes tipos de sequência. Entender essas conversões ajuda você a escolher o tipo certo para cada situação e transformar dados quando necessário.
Convertendo para Listas
A função list() converte qualquer sequência em uma lista:
# Tupla para lista
student_tuple = ("Alice", 20, "CS")
student_list = list(student_tuple)
print(student_list) # Output: ['Alice', 20, 'CS']
print(type(student_list)) # Output: <class 'list'>
# Agora podemos modificar
student_list[1] = 21
student_list.append(3.8)
print(student_list) # Output: ['Alice', 21, 'CS', 3.8]
# Range para lista
numbers = range(5)
numbers_list = list(numbers)
print(numbers_list) # Output: [0, 1, 2, 3, 4]
# String para lista (cada caractere vira um elemento)
text = "Python"
chars = list(text)
print(chars) # Output: ['P', 'y', 't', 'h', 'o', 'n']Converter para lista é útil quando você precisa modificar uma sequência ou quando precisa usar métodos específicos de lista como append(), sort() ou remove().
Convertendo para Tuplas
A função tuple() converte qualquer sequência em uma tupla:
# Lista para tupla
scores_list = [85, 90, 78, 92]
scores_tuple = tuple(scores_list)
print(scores_tuple) # Output: (85, 90, 78, 92)
print(type(scores_tuple)) # Output: <class 'tuple'>
# Agora é imutável
# scores_tuple[0] = 88 # TypeError: 'tuple' object does not support item assignment
# Range para tupla
numbers = range(1, 6)
numbers_tuple = tuple(numbers)
print(numbers_tuple) # Output: (1, 2, 3, 4, 5)
# String para tupla
text = "Hi"
chars_tuple = tuple(text)
print(chars_tuple) # Output: ('H', 'i')Converter para tupla é útil quando você quer proteger dados contra modificação ou quando precisa usar uma sequência como chave de dicionário.
15.8) Operações Comuns de Sequências em Strings, Listas, Tuplas e Ranges
Os tipos de sequência do Python — strings, listas, tuplas e ranges — compartilham muitas operações comuns. Entender essas operações compartilhadas ajuda você a trabalhar de forma eficiente com qualquer tipo de sequência.
Comprimento, Mínimo e Máximo
Todas as sequências suportam as funções len(), min() e max():
# Strings
text = "Python"
print(len(text)) # Output: 6
print(min(text)) # Output: P (smallest character by Unicode value)
print(max(text)) # Output: y (largest character by Unicode value)
# Listas
numbers = [45, 12, 78, 23, 56]
print(len(numbers)) # Output: 5
print(min(numbers)) # Output: 12
print(max(numbers)) # Output: 78
# Tuplas
scores = (85, 92, 78, 95, 88)
print(len(scores)) # Output: 5
print(min(scores)) # Output: 78
print(max(scores)) # Output: 95
# Ranges
nums = range(10, 50, 5)
print(len(nums)) # Output: 8
print(min(nums)) # Output: 10
print(max(nums)) # Output: 45Para min() e max() funcionarem, os elementos precisam ser comparáveis. Você não pode encontrar o mínimo de uma lista que contém strings e números:
mixed = [1, "hello", 3]
# print(min(mixed)) # TypeError: '<' not supported between instances of 'str' and 'int'Indexação e Indexação Negativa
Todas as sequências suportam indexação com índices positivos e negativos:
# Indexação positiva (baseada em 0)
text = "Python"
numbers = [10, 20, 30, 40, 50]
coords = (5, 10, 15)
values = range(0, 100, 10)
print(text[0]) # Output: P
print(numbers[2]) # Output: 30
print(coords[1]) # Output: 10
print(values[3]) # Output: 30
# Indexação negativa (a partir do final)
print(text[-1]) # Output: n (last character)
print(numbers[-2]) # Output: 40 (second from end)
print(coords[-3]) # Output: 5 (third from end, which is first)
print(values[-1]) # Output: 90 (last value in range)Índices negativos contam a partir do final: -1 é o último elemento, -2 é o penúltimo, e assim por diante.
Teste de Pertinência com in e not in
Todas as sequências suportam teste de pertinência:
# Strings - verifica substrings
text = "Python Programming"
print("Python" in text) # Output: True
print("Java" in text) # Output: False
print("gram" in text) # Output: True (substring)
print("PYTHON" not in text) # Output: True (case-sensitive)
# Listas
fruits = ["apple", "banana", "cherry", "date"]
print("banana" in fruits) # Output: True
print("grape" in fruits) # Output: False
print("apple" not in fruits) # Output: False
# Tuplas
coordinates = (10, 20, 30, 40)
print(20 in coordinates) # Output: True
print(25 in coordinates) # Output: False
print(50 not in coordinates) # Output: True
# Ranges - muito eficiente, sem iteração necessária
numbers = range(0, 100, 2) # Even numbers 0 to 98
print(50 in numbers) # Output: True
print(51 in numbers) # Output: False (odd number)
print(100 in numbers) # Output: False (stop is exclusive)Para ranges, o Python consegue determinar a pertinência matematicamente sem checar cada elemento, tornando isso extremamente rápido até para ranges enormes.
Concatenação e Repetição
Strings, listas e tuplas suportam concatenação com + e repetição com *:
# Concatenação com +
text1 = "Hello"
text2 = " World"
print(text1 + text2) # Output: Hello World
list1 = [1, 2, 3]
list2 = [4, 5, 6]
print(list1 + list2) # Output: [1, 2, 3, 4, 5, 6]
tuple1 = (10, 20)
tuple2 = (30, 40)
print(tuple1 + tuple2) # Output: (10, 20, 30, 40)
# Repetição com *
print("Ha" * 3) # Output: HaHaHa
print([0] * 5) # Output: [0, 0, 0, 0, 0]
print((1, 2) * 3) # Output: (1, 2, 1, 2, 1, 2)Importante: Ranges não suportam concatenação nem repetição:
r1 = range(5)
r2 = range(5, 10)
# combined = r1 + r2 # TypeError: unsupported operand type(s) for +: 'range' and 'range'
# Para combinar ranges, converta para listas ou tuplas primeiro
combined = list(r1) + list(r2)
print(combined) # Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]Contando Ocorrências
O método count() retorna quantas vezes um elemento aparece:
# Strings - conta ocorrências de substring
text = "Mississippi"
print(text.count("s")) # Output: 4
print(text.count("ss")) # Output: 2
print(text.count("i")) # Output: 4
# Listas
numbers = [1, 2, 3, 2, 4, 2, 5]
print(numbers.count(2)) # Output: 3
print(numbers.count(6)) # Output: 0
# Tuplas
grades = (85, 90, 85, 92, 85, 88)
print(grades.count(85)) # Output: 3
print(grades.count(95)) # Output: 0
# Ranges não têm método count(), mas você pode converter antes
nums = range(0, 20, 2)
nums_list = list(nums)
print(nums_list.count(10)) # Output: 1Encontrando o Índice de Elementos
O método index() retorna a posição da primeira ocorrência:
# Strings
text = "Python Programming"
print(text.index("P")) # Output: 0 (first P)
print(text.index("Pro")) # Output: 7 (substring position)
# print(text.index("Java")) # ValueError: substring not found
# Listas
fruits = ["apple", "banana", "cherry", "banana"]
print(fruits.index("banana")) # Output: 1 (first occurrence)
print(fruits.index("cherry")) # Output: 2
# print(fruits.index("grape")) # ValueError: 'grape' is not in list
# Tuplas
coordinates = (10, 20, 30, 20, 40)
print(coordinates.index(20)) # Output: 1 (first occurrence)
print(coordinates.index(40)) # Output: 4
# Ranges não têm método index(), mas você pode converter antes
nums = range(10, 50, 5)
nums_list = list(nums)
print(nums_list.index(25)) # Output: 3Se o elemento não for encontrado, index() lança um ValueError. Para evitar isso, verifique com in primeiro:
fruits = ["apple", "banana", "cherry"]
search_fruit = "grape"
if search_fruit in fruits:
position = fruits.index(search_fruit)
print(f"{search_fruit} found at position {position}")
else:
print(f"{search_fruit} not found")
# Output: grape not foundIteração com Loops for
Todas as sequências podem ser iteradas com loops for:
# Strings - iterar sobre caracteres
for char in "Python":
print(char, end=" ")
print() # Output: P y t h o n
# Listas
for fruit in ["apple", "banana", "cherry"]:
print(f"I like {fruit}")
# Output:
# I like apple
# I like banana
# I like cherry
# Tuplas
for score in (85, 90, 78):
print(f"Score: {score}")
# Output:
# Score: 85
# Score: 90
# Score: 78
# Ranges
for i in range(1, 6):
print(f"Count: {i}")
# Output:
# Count: 1
# Count: 2
# Count: 3
# Count: 4
# Count: 5Operações de Comparação
Sequências podem ser comparadas usando ==, !=, <, >, <= e >=:
# Igualdade
print([1, 2, 3] == [1, 2, 3]) # Output: True
print((1, 2, 3) == (1, 2, 3)) # Output: True
print("abc" == "abc") # Output: True
# Desigualdade
print([1, 2, 3] != [1, 2, 4]) # Output: True
print((1, 2) != (1, 2)) # Output: False
# Comparação lexicográfica (elemento por elemento)
print([1, 2, 3] < [1, 2, 4]) # Output: True (3 < 4)
print([1, 2, 3] < [1, 3, 0]) # Output: True (2 < 3)
print("apple" < "banana") # Output: True (alphabetical)
print((1, 2) < (1, 2, 3)) # Output: True (shorter is less if equal so far)
# Comparando tipos diferentes
print([1, 2, 3] == (1, 2, 3)) # Output: False (different types)A comparação funciona elemento por elemento, da esquerda para a direita. O primeiro elemento diferente determina o resultado.
Entender essas operações comuns permite que você escreva código que funciona com qualquer tipo de sequência, deixando seus programas mais flexíveis e reutilizáveis.
15.9) Fatiamento Avançado em Todos os Tipos de Sequência
Fatiamento (slicing) é um dos recursos mais poderosos do Python para trabalhar com sequências. Embora tenhamos apresentado o fatiamento básico no Capítulo 14, existem técnicas de fatiamento avançadas que funcionam em todos os tipos de sequência.
Revisão de Fatiamento Básico
O fatiamento extrai uma parte de uma sequência usando a sintaxe sequence[start:stop:step]:
# Fatiamento básico com strings
text = "Python Programming"
print(text[0:6]) # Output: Python
print(text[7:18]) # Output: Programming
print(text[7:]) # Output: Programming (from index 7 to end)
print(text[:6]) # Output: Python (from start to index 6)
# Fatiamento básico com listas
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[2:7]) # Output: [2, 3, 4, 5, 6]
print(numbers[:5]) # Output: [0, 1, 2, 3, 4]
print(numbers[5:]) # Output: [5, 6, 7, 8, 9]
# Fatiamento básico com tuplas
coordinates = (10, 20, 30, 40, 50, 60)
print(coordinates[1:4]) # Output: (20, 30, 40)
print(coordinates[:3]) # Output: (10, 20, 30)
print(coordinates[3:]) # Output: (40, 50, 60)
# Fatiamento básico com ranges
nums = range(0, 100, 10)
print(list(nums[2:5])) # Output: [20, 30, 40]Lembre: start é inclusivo, stop é exclusivo, e o resultado sempre é do mesmo tipo da sequência original.
Usando Step no Fatiamento
O terceiro parâmetro opcional step controla quantos elementos pular:
# A cada segundo elemento
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[::2]) # Output: [0, 2, 4, 6, 8]
print(numbers[1::2]) # Output: [1, 3, 5, 7, 9]
# A cada terceiro elemento
text = "abcdefghijklmnop"
print(text[::3]) # Output: adgjmp
# Step com start e stop
print(numbers[2:8:2]) # Output: [2, 4, 6]
print(text[1:10:2]) # Output: bdfhjStep Negativo: Invertendo Sequências
Um step negativo inverte a direção do fatiamento:
# Invertendo sequências inteiras
text = "Python"
print(text[::-1]) # Output: nohtyP
numbers = [1, 2, 3, 4, 5]
print(numbers[::-1]) # Output: [5, 4, 3, 2, 1]
coordinates = (10, 20, 30, 40)
print(coordinates[::-1]) # Output: (40, 30, 20, 10)
# Invertendo com step
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[::-2]) # Output: [9, 7, 5, 3, 1] (every second, backwards)
# Invertendo uma parte
text = "Python Programming"
print(text[7:18][::-1]) # Output: gnimmargorP (reverse "Programming")Ao usar step negativo, start e stop funcionam de forma diferente:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Com step negativo, start deve ser maior que stop
print(numbers[7:2:-1]) # Output: [7, 6, 5, 4, 3] (from 7 down to 3)
print(numbers[8:3:-2]) # Output: [8, 6, 4] (from 8 down to 4, step -2)
# Omitindo start/stop com step negativo
print(numbers[:5:-1]) # Output: [9, 8, 7, 6] (from end down to 6)
print(numbers[5::-1]) # Output: [5, 4, 3, 2, 1, 0] (from 5 down to start)Índices Negativos no Fatiamento
Você pode usar índices negativos para posições start e stop:
text = "Python Programming"
# Últimos 11 caracteres
print(text[-11:]) # Output: Programming
# Tudo exceto os últimos 11 caracteres
print(text[:-11]) # Output: Python
# De -15 a -5
print(text[-15:-5]) # Output: hon Progra
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Últimos 5 elementos
print(numbers[-5:]) # Output: [5, 6, 7, 8, 9]
# Tudo exceto os últimos 3 elementos
print(numbers[:-3]) # Output: [0, 1, 2, 3, 4, 5, 6]
# De -7 a -2
print(numbers[-7:-2]) # Output: [3, 4, 5, 6, 7]Fatiamento com Ranges
Fatiar um range retorna um novo objeto range:
# Fatiando ranges
numbers = range(0, 100, 5) # 0, 5, 10, 15, ..., 95
print(numbers) # Output: range(0, 100, 5)
# O slice retorna um novo range
subset = numbers[5:10]
print(subset) # Output: range(25, 50, 5)
print(list(subset)) # Output: [25, 30, 35, 40, 45]
# Com step
every_other = numbers[::2]
print(list(every_other)) # Output: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
# Step negativo
reversed_range = numbers[::-1]
print(list(reversed_range)) # Output: [95, 90, 85, ..., 5, 0]Slices Vazios e Casos de Borda
numbers = [1, 2, 3, 4, 5]
# Slices vazios (start >= stop com step positivo)
print(numbers[3:3]) # Output: []
print(numbers[5:10]) # Output: [] (stop beyond length)
print(numbers[10:20]) # Output: [] (both beyond length)
# Slices além dos limites da sequência são seguros
print(numbers[-100:100]) # Output: [1, 2, 3, 4, 5] (entire sequence)
print(numbers[2:100]) # Output: [3, 4, 5] (from 2 to end)
# Step negativo com start/stop incompatíveis
print(numbers[2:7:-1]) # Output: [] (can't go forward with negative step)
# Step igual a 0 não é permitido
# print(numbers[::0]) # ValueError: slice step cannot be zeroFatiamento para Copiar
O fatiamento cria uma nova sequência, o que fornece um jeito de copiar:
# Copiando com fatiamento
original = [1, 2, 3, 4, 5]
copy = original[:] # Slice do início ao fim
print(copy) # Output: [1, 2, 3, 4, 5]
# Modificar a cópia não afeta o original
copy[0] = 100
print(f"Original: {original}") # Output: Original: [1, 2, 3, 4, 5]
print(f"Copy: {copy}") # Output: Copy: [100, 2, 3, 4, 5]
# Isto funciona para tuplas também (cria uma nova tupla)
original_tuple = (1, 2, 3, 4, 5)
copy_tuple = original_tuple[:]
print(copy_tuple) # Output: (1, 2, 3, 4, 5)
# Para strings
text = "Python"
text_copy = text[:]
print(text_copy) # Output: PythonPorém, lembre-se do Capítulo 14 que isso cria uma cópia rasa (shallow copy).
# Limitação da cópia rasa
original = [[1, 2], [3, 4]]
copy = original[:]
# Modificar uma lista aninhada afeta ambas
copy[0][0] = 100
print(f"Original: {original}") # Output: Original: [[100, 2], [3, 4]]
print(f"Copy: {copy}") # Output: Copy: [[100, 2], [3, 4]]Tuplas e ranges são ferramentas essenciais no kit de sequências do Python. Tuplas fornecem dados estruturados e imutáveis que protegem informações contra modificações acidentais e permitem seu uso como chaves de dicionário. Ranges oferecem representações eficientes em memória para sequências de números, perfeitas para loops e sequências grandes. Entender quando usar cada tipo — e como converter entre eles — deixa seu código mais eficiente, mais seguro e com intenção mais clara.
As operações comuns compartilhadas por todos os tipos de sequência — indexação, fatiamento, iteração, teste de pertinência — formam uma interface consistente que torna o trabalho com qualquer sequência intuitivo. Técnicas avançadas de fatiamento oferecem formas poderosas e expressivas de extrair e manipular dados de sequências.
Conforme você continuar programando em Python, você vai se pegar naturalmente escolhendo o tipo de sequência certo para cada situação: listas para coleções que mudam, tuplas para registros fixos, ranges para sequências numéricas e strings para texto. Este capítulo te deu o conhecimento para fazer essas escolhas com confiança e usar cada tipo de forma eficaz.