Python Review
Python Review
1. Indentação (Crucial!)
O que é? Indentação se refere aos espaços em branco (ou tabulações) no início de uma linha de código.
Por que é Crucial em Python? Diferente de muitas outras linguagens de programação que usam chaves {} (como C++, Java, JavaScript) ou palavras-
chave como begin / end para delimitar blocos de código (como loops, condicionais, funções, classes), Python usa a indentação para essa finalidade.
A indentação define a estrutura e o escopo do seu código. Não é apenas uma questão de estilo; é parte fundamental da sintaxe da linguagem. O
interpretador Python depende da indentação para saber quais instruções pertencem a qual bloco.
Como Funciona:
Um bloco de código começa após um dois-pontos ( : ) em uma instrução como if , else , elif , for , while , def , class .
Todas as linhas de código dentro desse bloco devem ter o mesmo nível de indentação (mais recuadas que a linha com os dois-pontos).
O padrão e a convenção mais aceita (definida no PEP 8 - Guia de Estilo para Código Python) é usar 4 espaços por nível de indentação. É altamente
recomendado configurar seu editor de texto para inserir 4 espaços quando você pressiona a tecla Tab.
O bloco de código termina quando a indentação retorna ao nível anterior (ou ao nível principal do script).
Exemplo:
idade = 25
nivel_acesso = "admin"
# Bloco do IF (indentado)
if idade >= 18:
print("Você é maior de idade.") # Pertence ao bloco IF
# Bloco IF aninhado (mais indentado)
if nivel_acesso == "admin":
print("Acesso administrativo concedido.") # Pertence ao bloco IF aninhado
# Fim do bloco IF aninhado
print("Verificação de idade completa.") # Pertence ao bloco IF principal
# Fim do bloco IF principal
Erros Comuns:
IndentationError: expected an indented block : Ocorre quando Python espera um bloco indentado após um : mas não o encontra
ou a indentação está faltando.
IndentationError: unindent does not match any outer indentation level : Ocorre quando uma linha está indentada de
forma inconsistente (por exemplo, 3 espaços em vez de 4, ou desalinhada com o bloco ao qual deveria pertencer).
TabError: inconsistent use of tabs and spaces in indentation : Ocorre se você misturar tabulações e espaços para indentar
dentro do mesmo bloco. Escolha um (preferencialmente espaços) e use-o consistentemente.
2. Comentários
O que são? Comentários são textos no seu código que são completamente ignorados pelo interpretador Python. Eles servem para explicar o código para
outros desenvolvedores (ou para você mesmo no futuro).
Como Escrever:
Comentários de Linha Única: Começam com o símbolo de hash ( # ). Tudo a partir do # até o final da linha é considerado um comentário.
Page 1 of 85
Comentários de Múltiplas Linhas (Docstrings): Embora Python não tenha uma sintaxe específica para comentários de bloco como /* ... */
em C, você pode usar strings de múltiplas linhas (delimitadas por três aspas duplas """ ou três aspas simples ''' ) para esse fim. Tecnicamente,
isso cria uma string literal, mas se ela não for atribuída a uma variável ou usada em uma expressão, o Python a ignora. É uma convenção comum
usar isso para comentários de bloco ou, mais importante, para docstrings (strings de documentação) que descrevem funções, classes ou módulos.
"""
Este é um exemplo
de um comentário que
ocupa várias linhas.
"""
def minha_funcao():
'''Esta é uma docstring para a função.
Explica o que a função faz.
'''
pass # pass é uma instrução nula, apenas para ter um bloco válido
3. Variáveis
O que são? Variáveis são nomes (identificadores) que você usa para se referir a um valor armazenado na memória do computador. Pense nelas como
etiquetas que você cola em caixas (valores).
Por que usar? Para armazenar dados (números, textos, listas, etc.) que podem ser usados e modificados ao longo do programa. Tornam o código mais
legível e reutilizável.
Regras de Nomenclatura:
Devem começar com uma letra (a-z, A-Z) ou um underscore ( _ ).
Podem conter letras, números (0-9) e underscores após o primeiro caractere.
São case-sensitive (distinguem maiúsculas de minúsculas): idade , Idade e IDADE são três variáveis diferentes.
Não podem ser iguais a palavras-chave reservadas do Python (como if , else , for , while , class , def , return , etc.).
Convenções (PEP 8):
Use nomes descritivos e significativos.
Use snake_case (letras minúsculas separadas por underscores) para nomes de variáveis e funções (ex: nome_completo ,
calcular_imposto ).
Evite nomes muito curtos e não descritivos como x , y , a (a menos que sejam contadores óbvios em loops, como i , j , k ).
Tipagem Dinâmica: Em Python, você não precisa declarar o tipo de uma variável antes de usá-la (como int idade; em C++). O tipo da variável é
determinado dinamicamente pelo valor que ela recebe no momento da atribuição. Uma mesma variável pode até referenciar valores de tipos diferentes
em momentos diferentes (embora isso geralmente não seja recomendado por questões de clareza).
4. Atribuição
# Atribuição simples
nome_usuario = "Bob" # A string "Bob" é atribuída à variável nome_usuario
idade = 30 # O inteiro 30 é atribuído à variável idade
altura = 1.75 # O float 1.75 é atribuído à variável altura
e_estudante = False # O booleano False é atribuído à variável e_estudante
# Reatribuição (a mesma variável pode receber um novo valor, possivelmente de outro tipo)
idade = 31
print(idade) # Saída: 31
# Atribuição múltipla
Page 2 of 85
x, y = 10, 20 # x recebe 10, y recebe 20 (desempacotamento de tupla)
a = b = c = 100 # a, b e c recebem o mesmo valor 100
preco = 50
preco *= 1.1 # Equivalente a: preco = preco * 1.1 (aumento de 10%)
print(preco) # Saída: 55.0
Compreender bem esses quatro conceitos (Indentação, Comentários, Variáveis e Atribuição) é o primeiro passo essencial para escrever qualquer código
Python funcional e legível. A indentação, em particular, requer atenção constante, pois é a base da estrutura do código na linguagem.
Inteiros ( int )
O que são: Representam números inteiros, ou seja, números sem parte fracionária (positivos, negativos ou zero).
Características:
Em Python 3, os inteiros têm precisão arbitrária, o que significa que eles podem crescer para acomodar números tão grandes quanto a
memória do seu sistema permitir. Você não precisa se preocupar com estouro (overflow) como em algumas outras linguagens para tipos
inteiros de tamanho fixo.
Exemplos:
idade = 30
ano = 2023
saldo_negativo = -100
numero_grande = 100_000_000_000 # Underscores podem ser usados para legibilidade
Operações Comuns: Adição ( + ), subtração ( - ), multiplicação ( * ), divisão ( / - sempre resulta em float ), divisão inteira ( // - descarta a
parte fracionária), módulo ( % - resto da divisão), exponenciação ( ** ).
O que são: Representam números reais, incluindo aqueles com uma parte decimal. Podem também ser expressos em notação científica (com "e"
ou "E").
Características:
Utilizam o padrão IEEE 754 para representação binária de ponto flutuante (geralmente dupla precisão de 64 bits).
Importante: Devido à forma como os números de ponto flutuante são armazenados em binário, eles podem ter problemas de precisão.
Pequenos erros de arredondamento podem ocorrer. Por exemplo, 0.1 + 0.2 pode não ser exatamente 0.3 . Tenha cuidado ao fazer
comparações diretas de igualdade ( == ) com floats; é muitas vezes melhor verificar se a diferença entre eles está dentro de uma pequena
tolerância.
Exemplos:
preco = 99.90
pi = 3.14159
temperatura = -5.5
distancia = 1.5e6 # Notação científica: 1.5 * 10^6 = 1,500,000.0
valor_calculado = 1 / 3 # Resulta em 0.3333333333333333
Operações Comuns: As mesmas operações aritméticas dos inteiros. Misturar int e float em uma operação geralmente resulta em um
float .
Page 3 of 85
Complexos ( complex )
O que são: Representam números complexos, que têm uma parte real e uma parte imaginária. A parte imaginária é denotada por um j ou J no
final.
Características: Menos comuns em programação geral, mas essenciais em domínios científicos e de engenharia.
Exemplos:
z = 3 + 5j
apenas_imaginario = 4j
apenas_real_complexo = -2 + 0j
Operações: Suportam as operações aritméticas padrão. Você pode acessar as partes real e imaginária usando os atributos .real e .imag .
2. Strings ( str )
Strings são usadas para representar dados textuais, que são sequências de caracteres Unicode.
Características Fundamentais:
Imutáveis: Uma vez que uma string é criada, ela não pode ser alterada diretamente. Qualquer operação que parece modificar uma string (como
replace ou concatenação) na verdade cria e retorna uma nova string.
Sequências Ordenadas: Os caracteres em uma string têm uma ordem definida e podem ser acessados por seu índice (posição).
Criação:
Podem ser criadas usando aspas simples ( '...' ), aspas duplas ( "..." ), ou aspas triplas (simples '''...''' ou duplas """...""" ).
Aspas simples e duplas são funcionalmente idênticas. Aspas triplas são úteis para strings que contêm quebras de linha ou para criar docstrings
(strings de documentação).
nome = 'Alice'
mensagem = "Olá, mundo!"
paragrafo = """Esta é uma string
que ocupa múltiplas
linhas."""
outra_multilinha = '''Também funciona
com aspas simples.'''
Concatenação:
primeiro_nome = "João"
ultimo_nome = "Silva"
nome_completo = primeiro_nome + " " + ultimo_nome
print(nome_completo) # Saída: João Silva
Atenção: Você só pode concatenar strings com outras strings. Para juntar uma string com um número ou outro tipo, você precisa primeiro
convertê-lo para string usando str() .
idade = 25
# print("Idade: " + idade) # Isso dará um TypeError
print("Idade: " + str(idade)) # Correto. Saída: Idade: 25
Fatiamento (Slicing):
Page 4 of 85
s = "Python"
print(s[0]) # Saída: P (primeiro caractere)
print(s[2]) # Saída: t
print(s[-1]) # Saída: n (último caractere)
print(s[1:4]) # Saída: yth (do índice 1 até, mas não incluindo, o 4)
print(s[:3]) # Saída: Pyt (do início até, mas não incluindo, o 3)
print(s[2:]) # Saída: thon (do índice 2 até o final)
print(s[::2]) # Saída: Pto (pega caracteres alternados, step 2)
print(s[::-1]) # Saída: nohtyP (inverte a string, step -1)
texto = "olá"
print(texto.upper()) # Saída: OLÁ
texto = "MUNDO"
print(texto.lower()) # Saída: mundo
find(substring) : Retorna o índice da primeira ocorrência da substring . Retorna -1 se não for encontrada.
replace(old, new) : Retorna uma nova string onde todas as ocorrências de old são substituídas por new .
split(separator) : Divide a string em uma lista de substrings, usando o separator como delimitador. Se nenhum separador for fornecido,
divide pelos espaços em branco.
dados = "nome,idade,cidade"
lista_dados = dados.split(',')
print(lista_dados) # Saída: ['nome', 'idade', 'cidade']
join(iterable) : O oposto de split . Junta os elementos de um iterável (como uma lista de strings) em uma única string, usando a string na
qual o método foi chamado como separador.
Outros úteis: strip() (remove espaços em branco do início/fim), startswith(prefix) , endswith(suffix) , isdigit() ,
isalpha() , capitalize() , title() .
Formatação:
Criar strings que incorporam valores de variáveis ou expressões. É preferível à concatenação manual por ser mais legível e eficiente.
f-strings (Formatted String Literals - Recomendado a partir do Python 3.6):
Sintaxe: Prefixe a string com f ou F . Coloque variáveis ou expressões diretamente dentro de chaves {} .
Vantagens: Conciso, legível, rápido e poderoso (permite expressões e formatação dentro das chaves).
Page 5 of 85
nome = "Carlos"
idade = 42
altura = 1.83
mensagem = f"Olá, meu nome é {nome}. Eu tenho {idade} anos e {altura:.2f}m de altura."
print(mensagem) # Saída: Olá, meu nome é Carlos. Eu tenho 42 anos e 1.83m de altura.
# Note a formatação :.2f para limitar o float a 2 casas decimais
Método .format() :
Sintaxe: String com placeholders {} (posicionais ou nomeados), seguido por .format() com os valores.
Vantagens: Disponível em versões mais antigas do Python (pré 3.6), oferece flexibilidade.
nome = "Diana"
idade = 28
# Usando índices posicionais
print("Nome: {}, Idade: {}".format(nome, idade))
# Usando índices explícitos
print("Idade: {1}, Nome: {0}".format(nome, idade))
# Usando nomes
print("Nome: {n}, Idade: {i}".format(n=nome, i=idade))
# Com formatação
valor = 123.456
print("Valor formatado: {:.2f}".format(valor)) # Saída: Valor formatado: 123.46
3. Booleanos ( bool )
usuario_logado = True
permissao_concedida = False
idade = 20
e_maior = idade >= 18 # e_maior será True
if usuario_logado:
print("Bem-vindo!")
lista_vazia = []
if not lista_vazia: # not False é True
print("A lista está vazia.")
nome = "Ana"
if nome: # String não vazia é Truthy
print(f"Olá, {nome}!")
O que é: Um tipo especial que possui um único valor: None . É usado para representar a ausência de valor ou um valor nulo/indefinido. Não é o mesmo
que False , 0 ou uma string vazia "" .
Características:
É um singleton: só existe um objeto None na memória. Todas as variáveis que recebem None apontam para este mesmo objeto.
Uso Comum:
Valor de retorno padrão de funções que não retornam explicitamente nada com return .
Para inicializar uma variável que pode ou não receber um valor mais tarde.
Page 6 of 85
Para indicar que algo não foi encontrado ou não está disponível.
Verificação: A forma idiomática e recomendada para verificar se uma variável é None é usando o operador de identidade is ou is not , em vez de
== ou != . Isso porque você quer verificar se a variável aponta para o único objeto None .
Exemplos:
if resultado is None:
print("Nenhum resultado encontrado.")
else:
print(f"Resultado: {resultado}")
def minha_funcao_sem_retorno(x):
print(f"Processando {x}")
# Sem 'return' explícito
retorno = minha_funcao_sem_retorno(10)
print(retorno) # Saída: None
print(retorno is None) # Saída: True
Compreender esses tipos primitivos é essencial, pois eles formam a base sobre a qual estruturas de dados mais complexas (como listas, dicionários, etc.) e
toda a lógica do seu programa serão construídas.
Operadores: Aritméticos ( + , - , * , / , // , % , ** ), de Comparação ( == , != , > , < , >= , <= ), Lógicos ( and , or , not ), de Atribuição ( = , += ,
-= , etc.), de Identidade ( is , is not ), de Associação ( in , not in ).
Com certeza! Vamos explorar cada categoria de operadores em Python com detalhes e exemplos:
1. Operadores Aritméticos
+ (Adição): Soma dois números. Se usado com strings, realiza a concatenação (junção).
soma = 10 + 5 # soma = 15
preco_total = 19.99 + 5.50 # preco_total = 25.49
nome_completo = "João" + " " + "Silva" # nome_completo = "João Silva"
# lista1 + lista2 # Também funciona para concatenar listas
- (Subtração): Subtrai o segundo número do primeiro. Também pode ser usado como operador unário para indicar um número negativo.
diferenca = 10 - 5 # diferenca = 5
saldo = 100.0 - 35.75 # saldo = 64.25
negativo = -10 # negativo = -10
* (Multiplicação): Multiplica dois números. Se usado com uma string e um inteiro, repete a string.
produto = 10 * 5 # produto = 50
area = 3.5 * 2.0 # area = 7.0
padrao = "=" * 10 # padrao = "=========="
# lista * 3 # Também funciona para repetir listas
/ (Divisão): Divide o primeiro número pelo segundo. Importante: Em Python 3, o resultado da divisão / é sempre um float , mesmo que a divisão
seja exata.
// (Divisão Inteira ou de Piso): Divide o primeiro número pelo segundo e descarta a parte fracionária (arredonda para baixo, em direção a menos
infinito). O tipo do resultado depende dos operandos (int // int -> int; se houver float -> float).
Page 7 of 85
negativo_inteiro = -7 // 2 # resultado = -4 (arredonda para baixo, -3.5 -> -4)
float_inteiro = 10.0 // 3 # resultado = 3.0 (float, pois um operando era float)
% (Módulo ou Resto): Retorna o resto da divisão do primeiro número pelo segundo. Útil para verificar divisibilidade ou para operações cíclicas.
Estes operadores comparam dois valores e retornam um valor Booleano ( True ou False ). São fundamentais para tomadas de decisão ( if ) e controle de
loops ( while ).
== (Igual a): Verifica se os valores de dois operandos são iguais. Não confunda com = (atribuição)!
> (Maior que): Verifica se o valor do operando esquerdo é maior que o do direito.
< (Menor que): Verifica se o valor do operando esquerdo é menor que o do direito.
>= (Maior ou igual a): Verifica se o valor do operando esquerdo é maior ou igual ao do direito.
<= (Menor ou igual a): Verifica se o valor do operando esquerdo é menor ou igual ao do direito.
3. Operadores Lógicos
Estes operadores são usados para combinar ou inverter expressões booleanas ( True / False ).
and (E Lógico): Retorna True se ambos os operandos forem verdadeiros (ou "truthy"). Se o primeiro operando for falso (ou "falsy"), ele retorna esse
primeiro operando sem avaliar o segundo (short-circuiting). Se o primeiro for verdadeiro, ele retorna o segundo operando.
Page 8 of 85
print(True and True) # Saída: True
print(True and False) # Saída: False
print(False and True) # Saída: False (True não é avaliado)
print(False and False) # Saída: False (segundo False não é avaliado)
idade = 25
tem_cnh = True
print(idade >= 18 and tem_cnh) # Saída: True
or (OU Lógico): Retorna True se pelo menos um dos operandos for verdadeiro (ou "truthy"). Se o primeiro operando for verdadeiro, ele retorna esse
primeiro operando sem avaliar o segundo (short-circuiting). Se o primeiro for falso, ele retorna o segundo operando.
tem_cartao = False
tem_dinheiro = True
print(tem_cartao or tem_dinheiro) # Saída: True
not (NÃO Lógico): Inverte o valor booleano do operando. Se o operando for verdadeiro, retorna False . Se for falso, retorna True .
porta_fechada = True
print(not porta_fechada) # Saída: False
4. Operadores de Atribuição
Estes operadores são usados para atribuir valores a variáveis. O = é o básico, e os outros são atalhos para modificar o valor de uma variável.
x = 10
nome = "Ana"
lista = [1, 2, 3]
+= (Atribuição com Adição): Adiciona o valor da direita ao valor atual da variável e atribui o resultado de volta à variável ( x = x + y ).
contador = 5
contador += 1 # contador agora é 6 (equivalente a contador = contador + 1)
saldo = 100.0
saldo += 50.5 # saldo agora é 150.5
Page 9 of 85
texto = "abc"
texto += "def" # texto agora é "abcdef"
estoque = 100
estoque -= 10 # estoque agora é 90
*= (Atribuição com Multiplicação): Multiplica o valor atual da variável pelo valor da direita ( x = x * y ).
valor = 5
valor *= 3 # valor agora é 15
/= (Atribuição com Divisão): Divide o valor atual da variável pelo valor da direita ( x = x / y ). O resultado é sempre float .
total = 100.0
total /= 4 # total agora é 25.0
//= (Atribuição com Divisão Inteira): Realiza a divisão inteira e atribui o resultado ( x = x // y ).
quantidade = 17
quantidade //= 5 # quantidade agora é 3
numero = 17
numero %= 5 # numero agora é 2
**= (Atribuição com Exponenciação): Eleva o valor atual da variável à potência da direita ( x = x ** y ).
base = 2
base **= 4 # base agora é 16
5. Operadores de Identidade
Estes operadores comparam se duas variáveis (ou operandos) se referem exatamente ao mesmo objeto na memória, não apenas se têm o mesmo valor.
a = [1, 2, 3]
b = a # b agora aponta para o MESMO objeto que a
c = [1, 2, 3] # c aponta para um NOVO objeto, embora com o mesmo conteúdo
n1 = None
n2 = None
print(n1 is None) # Saída: True (Forma preferida de checar por None)
print(n1 is n2) # Saída: True (Só existe um objeto None)
a = [1, 2]
b = [1, 2]
print(a is not b) # Saída: True
print(a is not a) # Saída: False
val = 10
Page 10 of 85
if val is not None:
print("Val não é None") # Forma preferida
Principal Uso: Verificar identidade com singletons como None , True e False . Para comparar valores, use == .
Estes operadores verificam se um valor está presente dentro de uma sequência (como string, lista, tupla) ou coleção (como set, dicionário - verifica chaves
por padrão).
not in : Retorna True se o valor à esquerda não for encontrado dentro da sequência/coleção à direita.
Compreender como e quando usar cada um desses operadores é crucial para escrever código Python eficaz, desde cálculos simples até lógicas complexas de
controle de fluxo e manipulação de dados.
Permitem que o programa execute diferentes blocos de código com base em condições booleanas (verdadeiro/falso).
if (Se)
Propósito: Executa um bloco de código somente se uma determinada condição for True .
Sintaxe:
if condicao:
# Bloco de código a ser executado se a condicao for True
# Este bloco DEVE ser indentado
instrucao_1
instrucao_2
# Código fora do bloco if (será executado independentemente da condição)
Explicação: A condicao é avaliada. Se o resultado for True (ou "truthy" - veja a nota sobre Truthiness abaixo), o bloco de código indentado
abaixo do if é executado. Se for False (ou "falsy"), o bloco é pulado e a execução continua após o bloco if .
Exemplo:
idade = 20
if idade >= 18:
Page 11 of 85
print("Você é maior de idade.") # Esta linha será executada
print("Pode entrar na festa.") # Esta linha também
temperatura = 15
if temperatura < 10:
print("Está muito frio!") # Esta linha NÃO será executada
else (Senão)
Propósito: Fornece um bloco de código alternativo que é executado somente se a condição do if (ou do último elif ) for False .
Sintaxe: Deve vir após um bloco if (ou elif ).
if condicao:
# Bloco executado se condicao for True
instrucao_if_true
else:
# Bloco executado se condicao for False
# Este bloco DEVE ser indentado
instrucao_if_false
Explicação: Se a condicao do if for True , o bloco if executa e o bloco else é pulado. Se a condicao do if for False , o bloco if é
pulado e o bloco else é executado. Apenas um dos blocos ( if ou else ) será executado.
Exemplo:
idade = 15
if idade >= 18:
print("Você é maior de idade.") # NÃO executa
else:
print("Você é menor de idade.") # Esta linha SERÁ executada
Propósito: Permite verificar múltiplas condições em sequência. É testado somente se a(s) condição(ões) anterior(es) do if e elif (s) foram
False . Você pode ter quantos elif s quiser.
Sintaxe: Deve vir após um if e antes de um else (opcional).
if condicao_1:
# Bloco executado se condicao_1 for True
instrucao_1
elif condicao_2:
# Bloco executado se condicao_1 for False E condicao_2 for True
instrucao_2
elif condicao_3:
# Bloco executado se condicao_1 e condicao_2 forem False E condicao_3 for True
instrucao_3
# ... pode ter mais elifs ...
else:
# Bloco executado se TODAS as condições anteriores (if/elif) forem False
instrucao_default
Explicação: Python avalia as condições na ordem em que aparecem ( if , elif , elif , ...). Assim que encontra uma condição True , executa o
bloco correspondente e ignora o restante da estrutura ( elif s e else subsequentes). Se nenhuma condição if ou elif for True , o bloco
else (se existir) é executado.
Exemplo:
nota = 75
Page 12 of 85
print("Conceito D")
else:
print("Conceito F")
# Saída: Conceito C
Nota sobre Truthiness: Em Python, não apenas True é considerado verdadeiro em um contexto if / elif / while . Valores "truthy" incluem números
diferentes de zero, strings não vazias, listas/tuplas/dicionários não vazios, etc. Valores "falsy" incluem False , None , zero ( 0 , 0.0 ), e coleções vazias
( "" , [] , () , {} ).
for (Para)
Propósito: Usado para iterar sobre os itens de uma sequência (como uma lista, tupla, string) ou qualquer outro objeto iterável (como um dicionário,
conjunto, ou um objeto retornado por range() ). Ele executa o bloco de código uma vez para cada item na sequência.
Sintaxe Básica:
nome = "Python"
for letra in nome: # 'letra' será "P", "y", "t", "h", "o", "n"
print(letra, end="-") # Saída: P-y-t-h-o-n-
print() # Nova linha
Usando range() : A função range() gera uma sequência de números, sendo muito útil com for para repetir algo um número específico de
vezes ou para iterar por índices.
range(stop) : Gera números de 0 até stop - 1 .
range(start, stop) : Gera números de start até stop - 1 .
range(start, stop, step) : Gera números de start até stop - 1 , pulando de step em step .
# Imprimir números de 0 a 4
for i in range(5):
print(i) # 0, 1, 2, 3, 4
# Imprimir números de 2 a 5
for i in range(2, 6):
print(i) # 2, 3, 4, 5
Iterando sobre Dicionários: Por padrão, itera sobre as chaves. Use .values() para os valores ou .items() para pares chave-valor.
Page 13 of 85
print("\nValores:")
for valor in pessoa.values():
print(valor) # Ana, 30, Rio
print("\nItens (chave-valor):")
for chave, valor in pessoa.items(): # Desempacotamento de tupla
print(f"{chave}: {valor}") # nome: Ana, idade: 30, cidade: Rio
else em for : Um bloco else opcional pode ser adicionado a um loop for . Ele é executado somente se o loop completar todas as suas
iterações sem ser interrompido por uma instrução break .
numeros = [1, 3, 5, 7]
for num in numeros:
if num % 2 == 0:
print(f"Número par encontrado: {num}")
break # Sai do loop
else: # Só executa se o break NÃO for atingido
print("Nenhum número par encontrado na lista.")
# Saída: Nenhum número par encontrado na lista.
numeros_com_par = [1, 3, 4, 7]
for num in numeros_com_par:
if num % 2 == 0:
print(f"Número par encontrado: {num}")
break
else:
print("Nenhum número par encontrado na lista.")
# Saída: Número par encontrado: 4
while (Enquanto)
Propósito: Executa um bloco de código repetidamente enquanto uma determinada condição for True .
Sintaxe:
while condicao:
# Bloco de código a ser executado ENQUANTO a condicao for True
# Este bloco DEVE ser indentado
instrucao_1
instrucao_2
# !!! É CRUCIAL que algo dentro do loop eventualmente
# faça a condicao se tornar False, senão cria um loop infinito !!!
Explicação: A condicao é avaliada antes de cada iteração. Se for True , o bloco é executado. Após a execução do bloco, a condição é verificada
novamente. O loop continua até que a condicao se torne False . Se a condição for False desde o início, o bloco nunca é executado.
Exemplo:
contador = 0
while contador < 5:
print(f"Contador é {contador}")
contador = contador + 1 # Importantíssimo! Atualiza a condição
print("Loop while terminado.")
# Saída:
# Contador é 0
# Contador é 1
# Contador é 2
# Contador é 3
# Contador é 4
# Loop while terminado.
Page 14 of 85
if tentativa != senha_correta:
print("Senha incorreta!")
print("Acesso concedido!")
Loop Infinito (Cuidado!): Se a condição do while nunca se tornar False , o loop executará para sempre (ou até você interromper manualmente
o programa - Ctrl+C no terminal).
Loops while True: são às vezes usados intencionalmente com um break interno para controlar a saída.
else em while : Similar ao for , um bloco else pode ser usado com while . Ele executa somente se o loop terminar porque sua condição
se tornou False , e não se ele foi interrompido por um break .
count = 5
while count > 0:
print(count)
count -= 1
# if count == 2: # Descomente estas linhas para ver a diferença
# break
else:
print("Contagem regressiva completa sem interrupção.")
# Saída (sem break): 5, 4, 3, 2, 1, Contagem regressiva completa sem interrupção.
# Saída (com break em count==2): 5, 4, 3 (o else não executa)
Estas instruções permitem alterar o fluxo normal dentro de loops ( for ou while ).
break (Interromper)
Propósito: Sai imediatamente do loop ( for ou while ) mais interno em que se encontra. A execução continua na primeira instrução após o loop.
Uso: Útil quando você encontra o que estava procurando ou quando uma condição de erro exige a parada da iteração.
Exemplo:
continue (Continuar)
Propósito: Pula o restante do código dentro do bloco do loop para a iteração atual e passa imediatamente para a próxima iteração (no for , pega o
próximo item; no while , reavalia a condição).
Uso: Útil para ignorar certos itens ou condições sem sair do loop completamente.
Exemplo:
Page 15 of 85
numeros = [1, 2, 3, 4, 5, 6]
for num in numeros:
if num % 2 == 0: # Se o número for par...
continue # ...pula o print e vai para o próximo número
print(f"Número ímpar: {num}")
# Saída:
# Número ímpar: 1
# Número ímpar: 3
# Número ímpar: 5
pass (Passar)
Propósito: É uma instrução nula, ou seja, não faz absolutamente nada. Funciona como um placeholder.
Uso: É utilizada onde a sintaxe do Python exige uma instrução, mas você ainda não tem (ou não precisa de) nenhuma lógica para colocar ali. Isso
evita IndentationError . Comum em blocos if , for , while , definições de funções ( def ) ou classes ( class ) que serão implementadas
depois.
Exemplo:
def funcao_a_ser_implementada():
pass # Evita erro de sintaxe por bloco vazio
class MinhaClasseVazia:
pass # Evita erro de sintaxe
numero = 5
if numero > 0:
# TODO: Adicionar lógica para números positivos depois
pass # Não faz nada por enquanto, mas o if é sintaticamente válido
else:
print("Número não positivo")
Diferença de continue : continue afeta o fluxo do loop (pula para a próxima iteração), enquanto pass não faz nada e a execução continua
normalmente para a próxima linha dentro do bloco atual (se houver) ou sai do bloco se for a última linha.
Dominar estas estruturas de controle de fluxo é fundamental para escrever programas Python que possam responder a diferentes situações, processar dados
de forma eficiente e realizar tarefas complexas.
Pense em uma lista como um contêiner ou uma coleção que pode armazenar vários itens (elementos) em uma sequência específica. É como uma lista de
compras, uma lista de tarefas ou uma lista de nomes – os itens têm uma ordem e você pode adicionar, remover ou modificar itens nela.
Características Principais:
1. Mutáveis (Mutable): Esta é uma das características mais importantes. "Mutável" significa que você pode alterar o conteúdo de uma lista depois de ela ter
sido criada. Você pode adicionar novos elementos, remover elementos existentes ou modificar os valores dos elementos que já estão na lista, tudo isso
sem criar uma nova lista do zero (a identidade do objeto lista permanece a mesma).
2. Ordenadas (Ordered): Os itens em uma lista mantêm a ordem em que foram adicionados. Cada item tem uma posição específica, chamada de índice. A
ordem não mudará a menos que você a modifique explicitamente (por exemplo, usando sort() ou reverse() ). Isso significa que [1, 2, 3] é
diferente de [3, 2, 1] .
3. Permitem Duplicatas: Uma lista pode conter elementos duplicados sem problemas. [1, 2, 2, 3, 3, 3] é uma lista válida.
4. Podem Conter Tipos de Dados Diferentes: Uma única lista pode armazenar itens de diferentes tipos de dados (inteiros, floats, strings, booleanos, outras
listas, objetos, etc.).
Criação de Listas:
Page 16 of 85
Usando Colchetes [] (Literal): A forma mais comum.
lista_vazia = []
numeros = [1, 2, 3, 4, 5]
nomes = ["Ana", "Bruno", "Carlos"]
misturada = [10, "Python", 25.5]
Usando o Construtor list() : Pode converter outros iteráveis (como tuplas, strings, ranges, etc.) em listas.
tupla = (1, 2, 3)
lista_de_tupla = list(tupla) # lista_de_tupla será [1, 2, 3]
texto = "abc"
lista_de_string = list(texto) # lista_de_string será ['a', 'b', 'c']
Acesso a Elementos:
Indexação (Indexing): Acessa um único elemento usando sua posição (índice) dentro de colchetes [] .
Índices começam em 0: O primeiro elemento está no índice 0, o segundo no índice 1, e assim por diante.
Índices Negativos: Você pode usar índices negativos para contar a partir do final da lista. -1 é o último elemento, -2 é o penúltimo, etc.
Fatiamento (Slicing): Extrai uma sub-lista (uma nova lista contendo uma parte da original) usando a sintaxe [start:stop:step] .
numeros = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Page 17 of 85
Alterando uma Fatia: Você pode substituir uma fatia da lista por outra sequência (o tamanho não precisa ser o mesmo).
Métodos são funções associadas a um objeto (neste caso, a lista). Eles são chamados usando a notação de ponto ( nome_da_lista.metodo() ). A maioria
desses métodos modifica a lista in-place (ou seja, alteram a lista original) e retornam None .
insert(indice, elemento) : Insere um único elemento na posição especificada pelo indice . Todos os elementos a partir desse índice são
deslocados para a direita. Modifica in-place.
numeros = [1, 2, 4, 5]
numeros.insert(2, 3) # Insere 3 no índice 2
print(numeros) # Saída: [1, 2, 3, 4, 5]
numeros.insert(0, 0) # Insere 0 no início
print(numeros) # Saída: [0, 1, 2, 3, 4, 5]
extend(iteravel) : Adiciona todos os elementos de um iteravel (como outra lista, tupla ou string) ao final da lista atual. Modifica in-place. É
diferente de append , que adicionaria o iterável como um único elemento.
lista1 = [1, 2, 3]
lista2 = [4, 5, 6]
lista1.extend(lista2)
print(lista1) # Saída: [1, 2, 3, 4, 5, 6]
pop(indice=-1) : Remove e retorna o elemento na posição indice . Se nenhum índice for fornecido, remove e retorna o último elemento
(comportamento de pilha). Modifica in-place. Causa IndexError se a lista estiver vazia ou o índice for inválido.
remove(valor) : Remove a primeira ocorrência do valor especificado da lista. Modifica in-place. Causa ValueError se o valor não for
encontrado na lista. Não retorna o valor removido.
Page 18 of 85
print(animais) # Saída: ['gato', 'pássaro', 'cachorro']
index(valor, start=0, end=len(list)) : Retorna o índice da primeira ocorrência do valor especificado. Você pode opcionalmente
fornecer índices start e end para limitar a busca a uma sub-lista. Causa ValueError se o valor não for encontrado.
numeros = [1, 2, 2, 3, 2, 4]
contagem_2 = numeros.count(2)
print(contagem_2) # Saída: 3
numeros = [5, 1, 4, 2, 3]
numeros.sort()
print(numeros) # Saída: [1, 2, 3, 4, 5]
numeros.sort(reverse=True)
print(numeros) # Saída: [5, 4, 3, 2, 1]
Nota: sort() só funciona se todos os elementos puderem ser comparados entre si (geralmente, todos do mesmo tipo ou tipos comparáveis).
minha_lista = [1, 2, 3, 4]
minha_lista.reverse()
print(minha_lista) # Saída: [4, 3, 2, 1]
copy() : Retorna uma cópia superficial (shallow copy) da lista. Isso significa que a nova lista é um objeto diferente, mas se a lista original contiver
outros objetos mutáveis (como outras listas), a cópia conterá referências aos mesmos objetos internos. Modificações nos objetos internos serão
refletidas em ambas as listas (original e cópia).
Page 19 of 85
# Alternativa para cópia superficial: fatiamento completo
copia_fatia = original[:]
print(copia_fatia is original) # False
Para cópias onde os objetos internos também são copiados (deep copy), use copy.deepcopy() .
minha_lista = [1, 2, 3]
minha_lista.clear()
print(minha_lista) # Saída: []
l1 = [1, 2]
l2 = [3, 4]
l3 = l1 + l2
print(l3) # Saída: [1, 2, 3, 4]
Repetição ( * ): Cria uma nova lista repetindo os elementos da lista um número de vezes. Cuidado com objetos mutáveis internos (eles serão referências
ao mesmo objeto).
l1 = [0] * 5
print(l1) # Saída: [0, 0, 0, 0, 0]
l2 = ["a", "b"] * 3
print(l2) # Saída: ['a', 'b', 'a', 'b', 'a', 'b']
Deletar Elementos ( del ): A instrução del pode remover elementos por índice ou fatias.
Listas podem conter outras listas, permitindo criar estruturas semelhantes a matrizes ou tabelas.
matriz = [
[1, 2, 3],
[4, 5, 6],
Page 20 of 85
[7, 8, 9]
]
Em resumo, listas são estruturas de dados incrivelmente úteis em Python devido à sua flexibilidade (mutabilidade, tipos mistos) e à vasta gama de métodos e
operações disponíveis para manipulação de dados ordenados. Elas são uma ferramenta essencial no arsenal de qualquer programador Python.
* Tuplas ( tuple ): Imutáveis, ordenadas, indexação, fatiamento.
* Dicionários ( dict ): Pares chave-valor, mutáveis, não ordenados (antes do Python 3.7, agora ordenados por inserção), acesso por chave, métodos ( keys ,
values , items , get , pop ).
* Conjuntos ( set ): Elementos únicos, não ordenados, operações de conjuntos (união, interseção, diferença).
Okay, vamos detalhar as Tuplas, Dicionários e Conjuntos em Python. São estruturas de dados fundamentais, cada uma com suas características e usos
específicos.
1. Tuplas ( tuple )
Pense em tuplas como "listas imutáveis". Elas são sequências ordenadas de itens, mas, uma vez criadas, não podem ser modificadas.
Características:
Imutáveis (Immutable): Este é o ponto chave. Depois que uma tupla é criada, você não pode adicionar, remover ou alterar seus elementos.
Qualquer operação que pareça modificar uma tupla (como concatenação) na verdade cria uma nova tupla.
Ordenadas (Ordered): Assim como as listas, os elementos em uma tupla mantêm a ordem em que foram definidos. (1, 2, 3) é diferente de
(3, 2, 1) .
Permitem Duplicatas: Uma tupla pode conter elementos repetidos: (1, 2, 2, 3) .
Podem Conter Tipos de Dados Diferentes: (1, "Python", 4.5, True) .
Criação:
tupla_vazia = ()
numeros = (1, 2, 3)
misturada = (10, "Olá", 3.14)
# Os parênteses são opcionais em muitos casos (tuple packing)
ponto = 10, 20 # Isso também cria uma tupla: (10, 20)
Tupla de Elemento Único: Requer uma vírgula , no final para diferenciá-la de uma expressão entre parênteses.
Usando o Construtor tuple() : Converte um iterável (lista, string, range, etc.) em uma tupla.
lista = [1, 2, 3]
tupla_de_lista = tuple(lista) # tupla_de_lista será (1, 2, 3)
texto = "abc"
tupla_de_string = tuple(texto) # tupla_de_string será ('a', 'b', 'c')
Page 21 of 85
minha_tupla = ('a', 'b', 'c', 'd')
print(minha_tupla[0]) # Saída: a
print(minha_tupla[-1]) # Saída: d
Fatiamento (Slicing): Usa [start:stop:step] para obter uma nova tupla contendo uma parte da original.
minha_tupla = (0, 1, 2, 3, 4, 5)
sub_tupla = minha_tupla[1:4] # sub_tupla será (1, 2, 3)
print(sub_tupla)
Segurança/Integridade: Garante que os dados não sejam acidentalmente modificados. Útil para representar coleções fixas de dados.
Chaves de Dicionário: Como são imutáveis, tuplas (que contêm apenas elementos imutáveis) podem ser usadas como chaves em dicionários,
enquanto listas não podem.
Performance: Em alguns casos, podem ser ligeiramente mais eficientes em termos de memória e velocidade de processamento do que listas
(embora a diferença seja muitas vezes pequena).
Retorno de Funções: Comum para retornar múltiplos valores fixos de uma função.
Desempacotamento (Unpacking): A atribuição múltipla funciona muito bem com tuplas.
2. Dicionários ( dict )
Dicionários são coleções que armazenam pares de chave-valor. Pense neles como um dicionário real (palavra -> definição) ou uma agenda (nome -> telefone).
Características:
Pares Chave-Valor (Key-Value): Cada item no dicionário consiste em uma chave única e seu valor associado.
Mutáveis (Mutable): Você pode adicionar, remover e modificar pares chave-valor após a criação do dicionário.
Ordenados por Inserção (Python 3.7+): A partir do Python 3.7, os dicionários lembram e mantêm a ordem em que os itens foram inseridos. Em
versões anteriores (3.6 e mais antigas), os dicionários eram não ordenados, e a ordem podia variar. É importante estar ciente dessa mudança.
Chaves Únicas e Imutáveis:
As chaves dentro de um dicionário devem ser únicas. Tentar adicionar uma chave que já existe atualizará o valor associado a ela.
As chaves devem ser de um tipo de dado imutável (como strings, números, tuplas contendo apenas imutáveis). Listas ou outros dicionários
não podem ser chaves.
Valores Podem Ser Qualquer Coisa: Os valores podem ser de qualquer tipo de dado (números, strings, listas, outros dicionários, objetos, etc.) e
podem ser duplicados.
Criação:
Page 22 of 85
copia_pessoa = dict(pessoa)
Acesso a Valores:
Usando a Chave entre Colchetes [] : A forma mais direta. Retorna o valor associado à chave. Causa KeyError se a chave não existir.
Usando o Método get(chave, default=None) : Forma mais segura. Retorna o valor se a chave existir, caso contrário, retorna o valor
default especificado (que é None se nenhum default for fornecido). Não causa KeyError .
Atribuição Direta: Se a chave já existe, atualiza o valor; se não existe, cria um novo par chave-valor.
Método update(outro_dicionario_ou_iteravel) : Mescla outro dicionário ou iterável de pares chave-valor no dicionário atual. Chaves
existentes são atualizadas.
Remoção de Itens:
pop(chave, default=None) : Remove o par chave-valor especificado e retorna o valor removido. Causa KeyError se a chave não existir e
default não for fornecido.
tel = pessoa.pop("telefone")
print(tel) # Saída: 9999-8888
print(pessoa) # 'telefone' foi removido
popitem() : Remove e retorna o último par (chave, valor) inserido (comportamento LIFO desde Python 3.7). Causa KeyError se o dicionário
estiver vazio.
ultimo_item = pessoa.popitem()
print(ultimo_item) # Saída: ('estado', 'RJ') (ou o último que foi adicionado)
print(pessoa)
Instrução del : Remove o par chave-valor usando a chave. Causa KeyError se a chave não existir.
del pessoa["idade"]
print(pessoa)
# del pessoa["chave_inexistente"] # Geraria KeyError
pessoa.clear()
print(pessoa) # Saída: {}
Page 23 of 85
Visualização e Iteração:
3. Conjuntos ( set )
Conjuntos são coleções não ordenadas de elementos únicos e imutáveis. Eles são otimizados para operações matemáticas de conjuntos (união, interseção,
etc.) e para verificar rapidamente se um elemento pertence ou não à coleção.
Características:
Elementos Únicos: Conjuntos não permitem elementos duplicados. Se você tentar adicionar um elemento que já existe, nada acontece.
Não Ordenados (Unordered): Os elementos em um conjunto não têm uma ordem definida ou garantida. Você não pode acessar elementos por
índice. Embora a implementação interna possa ter alguma ordem, você NUNCA deve depender dela.
Mutáveis: Você pode adicionar e remover elementos de um conjunto após sua criação. Existe também o frozenset , que é uma versão imutável
do conjunto.
Elementos Devem Ser Imutáveis: Assim como as chaves de dicionário, os elementos de um conjunto devem ser de tipos imutáveis (números,
strings, tuplas de imutáveis). Você não pode ter uma lista dentro de um conjunto.
Criação:
lista_com_duplicatas = [1, 2, 2, 3, 1, 4]
conjunto_unico = set(lista_com_duplicatas)
print(conjunto_unico) # Saída: {1, 2, 3, 4}
conjunto_de_string = set("abracadabra")
print(conjunto_de_string) # Saída: {'a', 'r', 'c', 'd', 'b'} (ordem não garantida)
Acesso a Elementos: Não há acesso por índice ( conjunto[0] gera erro). O principal meio de interação é verificar a presença de um elemento.
Modificação:
Page 24 of 85
add(elemento) : Adiciona um único elemento. Se já existir, não faz nada.
cores_set.remove("verde")
# cores_set.remove("preto") # Geraria KeyError
discard(elemento) : Remove um elemento se ele existir. Não causa erro se o elemento não for encontrado. (Mais seguro que remove ).
cores_set.discard("vermelho")
cores_set.discard("preto") # Não existe, mas não dá erro
print(cores_set)
pop() : Remove e retorna um elemento arbitrário do conjunto (não há garantia de qual será). Causa KeyError se o conjunto estiver vazio.
elemento_removido = cores_set.pop()
print(f"Removido: {elemento_removido}")
print(cores_set)
cores_set.clear()
print(cores_set) # Saída: set()
set_a = {1, 2, 3, 4}
set_b = {3, 4, 5, 6}
Page 25 of 85
Característica Lista ( list ) Tupla ( tuple ) Dicionário ( dict ) Conjunto ( set )
Ordenação Ordenada Ordenada Ordenado (Python 3.7+) / Não (antes) Não Ordenado
Acesso Índice numérico [i] Índice numérico [i] Chave [chave] ou .get(chave) Não por índice (teste in )
Coleção geral, sequência Sequência fixa, chave de Mapeamento chave-valor, dados Teste de membro, unicidade, ops.
Uso Principal
mutável dict estruturados conjuntos
Escolher a estrutura de dados correta (lista, tupla, dicionário ou conjunto) depende muito do que você precisa fazer com os dados: precisa modificar a
coleção? A ordem importa? Precisa garantir unicidade? Precisa associar valores a chaves? Entender essas diferenças é crucial para escrever código Python
eficiente e claro.
2. Funções e Modularidade:
Definição de Funções: def , parâmetros (posicionais, nomeados/keyword), argumentos ( *args , **kwargs ), valores padrão.
Escopo de Variáveis: Local, Global, Nonlocal.
Retorno de Valores: return .
Okay, vamos detalhar os conceitos de funções, escopo de variáveis e retorno de valores em Python, que são pilares para escrever código modular,
reutilizável e organizado.
Sintaxe Básica:
A definição de uma função começa com a palavra-chave def , seguida pelo nome da função, parênteses () , e dois-pontos : . O bloco de código
indentado abaixo da linha do def é o corpo da função.
def saudacao(nome):
"""Imprime uma saudação para o nome fornecido."""
Page 26 of 85
print(f"Olá, {nome}!")
Tipos de Parâmetros/Argumentos:
Argumentos Posicionais: São passados na ordem em que os parâmetros foram definidos. A correspondência é feita pela posição.
Argumentos Nomeados (Keyword Arguments): São passados especificando o nome do parâmetro seguido pelo valor ( nome=valor ). Isso
permite passar argumentos fora de ordem e melhora a legibilidade.
descrever_pet(tipo_animal="cachorro", nome_pet="Rex")
descrever_pet(nome_pet="Fifi", tipo_animal="gato") # Ordem não importa com keywords
# Misturando: Posicionais devem vir ANTES de nomeados
# descrever_pet("peixe", nome_pet="Nemo") # OK
# descrever_pet(nome_pet="Piu", "passarinho") # ERRO! Posicional após nomeado
Valores Padrão para Parâmetros: Você pode definir um valor padrão para um parâmetro na definição da função. Se um argumento para esse
parâmetro não for fornecido na chamada, o valor padrão será usado. Parâmetros com valores padrão devem vir após os parâmetros sem valores
padrão.
Cuidado com Padrões Mutáveis: Evite usar tipos mutáveis (como listas [] ou dicionários {} ) como valores padrão diretamente. O objeto padrão
é criado apenas uma vez quando a função é definida, não a cada chamada. Isso pode levar a comportamentos inesperados. A forma correta é usar
None como padrão e criar o objeto dentro da função se necessário.
# Forma CORRETA
def adiciona_lista_correto(item, lista=None):
if lista is None:
lista = [] # Cria uma nova lista a cada chamada, se necessário
lista.append(item)
return lista
Page 27 of 85
print(adiciona_lista_correto(2)) # Saída: [2] (listas separadas)
print(adiciona_lista_correto(3, [10, 20])) # Saída: [10, 20, 3]
Argumentos Arbitrários Posicionais ( *args ): Permite que uma função aceite um número variável de argumentos posicionais. Dentro da função,
args (o nome após o * é uma convenção, poderia ser outro) será uma tupla contendo todos os argumentos posicionais extras passados. O
*args deve vir após os parâmetros posicionais normais.
Argumentos Arbitrários Nomeados ( **kwargs ): Permite que uma função aceite um número variável de argumentos nomeados (keyword
arguments) que não correspondam a um nome de parâmetro formal. Dentro da função, kwargs (convenção) será um dicionário contendo esses
argumentos extras como pares chave-valor. O **kwargs deve ser o último parâmetro na definição da função.
imprimir_info("Bruno", 30)
# Saída:
# Nome: Bruno
# Idade: 30
# Informações Adicionais:
# (Nenhuma)
Page 28 of 85
print(f"kw_padrao={kw_padrao}")
print(f"kwargs={kwargs}")
2. Escopo de Variáveis
Variáveis criadas dentro de uma função (incluindo parâmetros) têm escopo local.
Elas só existem e podem ser acessadas dentro dessa função.
São criadas quando a função é chamada e destruídas quando a função termina (retorna).
def minha_funcao():
variavel_local = 10 # Escopo local
print(f"Dentro da função: {variavel_local}")
Variáveis definidas fora de qualquer função (no nível superior do script ou módulo) têm escopo global.
Elas podem ser acessadas (lidas) de qualquer lugar do módulo, incluindo dentro de funções, após terem sido definidas.
def mostrar_global():
# Pode ler a variável global diretamente
print(f"Dentro da função, lendo global: {variavel_global}")
Modificando Variáveis Globais ( global ): Se você tentar atribuir um novo valor a uma variável dentro de uma função que tem o mesmo nome de
uma variável global, por padrão, Python criará uma nova variável local com esse nome, sombreando a global dentro da função. Para modificar
explicitamente a variável global de dentro da função, você precisa usar a palavra-chave global .
contador_global = 0
def incrementar_sem_global():
# contador_global += 1 # ERRO! Tenta ler antes de atribuir localmente
contador_global = 0 # Cria uma NOVA variável local 'contador_global'
contador_global += 1
print(f"Dentro (sem global): {contador_global}") # Imprime a local (1)
def incrementar_com_global():
global contador_global # Indica que queremos usar a global
contador_global += 1
print(f"Dentro (com global): {contador_global}") # Imprime a global modificada
Page 29 of 85
print(f"Depois (com global) 2: {contador_global}")# Saída: 2
def externa():
x = "original externa" # Variável no escopo da função externa
def interna():
nonlocal x # Indica que queremos modificar o 'x' da função externa
x = "modificado pela interna"
print(f"Dentro da interna: {x}")
def interna_sem_nonlocal():
# nonlocal x # Sem isso, cria uma local
x = "variável local da interna_sem_nonlocal"
print(f"Dentro da interna_sem_nonlocal: {x}")
externa()
# Saída:
# Externa antes de chamar interna: original externa
# Dentro da interna: modificado pela interna
# Externa depois de chamar interna: modificado pela interna
#
# Externa antes de chamar interna_sem_nonlocal: reset externa
# Dentro da interna_sem_nonlocal: variável local da interna_sem_nonlocal
# Externa depois de chamar interna_sem_nonlocal: reset externa
Regra LEGB (Resolução de Nomes): Quando você usa um nome de variável, Python o procura na seguinte ordem:
Propósito: Permite que uma função envie um resultado (um valor ou objeto) de volta para o local onde foi chamada. Esse valor retornado pode ser usado
em expressões, atribuído a variáveis, etc.
Sintaxe: return expressao
Retornando um Único Valor:
def calcular_quadrado(numero):
return numero * numero
Page 30 of 85
Retornando Múltiplos Valores: Você pode listar múltiplos valores após return , separados por vírgula. Python automaticamente os empacota em uma
tupla.
import math
def calcular_circulo(raio):
area = math.pi * raio ** 2
circunferencia = 2 * math.pi * raio
return area, circunferencia # Retorna uma tupla (area, circunferencia)
def funcao_sem_retorno_explicito(x):
print(f"Processando {x}")
# Nenhum return aqui
def funcao_com_return_vazio():
print("Fazendo algo...")
return # Retorna None explicitamente
resultado1 = funcao_sem_retorno_explicito(10)
print(f"Resultado 1: {resultado1}") # Saída: Resultado 1: None
resultado2 = funcao_com_return_vazio()
print(f"Resultado 2: {resultado2}") # Saída: Resultado 2: None
Impacto do return no Fluxo: Quando uma instrução return é executada, a função imediatamente para sua execução e retorna o valor. Qualquer
código na função após a instrução return não será executado.
def verificar_numero(num):
if num > 0:
return "Positivo"
print("Esta linha NUNCA será impressa") # Código inacessível
elif num < 0:
return "Negativo"
else:
return "Zero"
Dominar funções, escopo e retorno é fundamental para escrever código Python bem estruturado, legível, testável e fácil de dar manutenção. Funções são a
principal ferramenta para quebrar problemas complexos em partes menores e gerenciáveis.
O que são?
Funções lambda são pequenas funções anônimas (sem nome definido através de def ) que podem ser criadas de forma concisa em Python. Elas são
Page 31 of 85
restritas a uma única expressão. O resultado dessa expressão é o valor que a função lambda retorna implicitamente.
Sintaxe:
Características e Uso:
Anônimas: Não têm um nome formal associado via def , embora você possa atribuí-las a uma variável (geralmente não recomendado para
funções mais complexas, onde def é preferível).
Uma Linha: São definidas em uma única linha de código.
Expressão Única: O corpo da lambda só pode conter uma expressão, não múltiplas instruções.
Retorno Implícito: O resultado da expressao é automaticamente retornado.
Casos de Uso Comuns: São frequentemente usadas onde uma função pequena e simples é necessária por um curto período, especialmente como
argumento para funções de ordem superior (funções que recebem outras funções como argumento), como map() , filter() , sorted() , ou
em callbacks de interfaces gráficas.
Exemplos:
# 3. Usando lambda com sorted() para ordenar pelo segundo elemento de tuplas
lista_tuplas = [(1, 'b'), (3, 'a'), (2, 'c')]
lista_ordenada = sorted(lista_tuplas, key=lambda item: item[1]) # Usa o segundo elemento ('b', 'a', 'c') com
print(lista_ordenada) # Saída: [(3, 'a'), (1, 'b'), (2, 'c')]
def cria funções nomeadas, que podem ser reutilizadas e referenciadas pelo nome. Lambdas são anônimas.
def pode conter múltiplas instruções, lógica complexa, docstrings. Lambdas são restritas a uma única expressão.
Para qualquer coisa além de uma operação muito simples, uma função def é geralmente mais legível e recomendada. Use lambdas com
moderação para manter a clareza do código.
O que são?
Docstrings são strings literais (geralmente delimitadas por aspas triplas """ ou ''' ) que aparecem como a primeira instrução na definição de um
módulo, função, classe ou método. Elas são usadas para documentar o que esse objeto faz.
Propósito:
Explicar o propósito e o uso do código (o "quê" e o "como", não necessariamente os detalhes internos da implementação).
Descrever parâmetros, valores de retorno, exceções levantadas.
Page 32 of 85
Servir como documentação oficial que pode ser acessada programaticamente e por ferramentas.
Sintaxe e Localização:
Args:
param1 (tipo): Descrição do primeiro parâmetro.
param2 (tipo): Descrição do segundo parâmetro.
Returns:
tipo: Descrição do valor retornado.
Raises:
TipoDeErro: Descrição da condição que levanta o erro.
"""
# Corpo da função aqui
resultado = param1 + param2
return resultado
class MinhaClasse:
"""Docstring da classe."""
def meu_metodo(self):
"""Docstring do método."""
pass
Acessando Docstrings:
print(minha_funcao.__doc__)
help(minha_funcao)
Docstring de Uma Linha: Para casos simples. A linha deve ser um resumo conciso, terminando com ponto. As aspas triplas ficam na mesma linha.
def funcao_simples():
"""Retorna a string 'Olá'."""
return "Olá"
Importância: Docstrings são cruciais para a legibilidade e manutenção do código. Elas facilitam o entendimento de como usar uma função ou classe
sem precisar ler todo o seu código-fonte e são usadas por ferramentas de geração automática de documentação (como Sphinx).
3. Módulos
Page 33 of 85
O que são?
Um módulo em Python é simplesmente um arquivo contendo código Python (definições de funções, classes e variáveis, além de código executável). O
nome do arquivo (sem a extensão .py ) torna-se o nome do módulo.
Propósito:
Importando Módulos: A instrução import (e suas variações) é usada para trazer o código de um módulo para o seu script atual.
import nome_do_modulo :
Importa o módulo inteiro, mas dá a ele um apelido (alias) mais curto ou conveniente. Útil para nomes de módulos longos ou para evitar
conflitos.
Importa apenas o item especificado (função, classe, variável) diretamente para o namespace atual.
Você pode usar o item_especifico diretamente, sem o prefixo do módulo.
Cuidado: Pode levar a conflitos de nomes se você importar itens com o mesmo nome de diferentes módulos ou se você já tiver uma
variável/função com esse nome.
# Se você definisse sua própria função 'sqrt' depois, ela sobrescreveria a importada
# def sqrt(x): return "Não é a raiz!"
# print(sqrt(9)) # Chamaria a sua função, não a do math
Importa um item específico e dá a ele um alias no namespace atual. Útil para evitar conflitos ou usar nomes mais curtos.
Importa todos os nomes públicos (aqueles que não começam com _ ) do módulo diretamente para o namespace atual.
Page 34 of 85
FORTEMENTE DESENCORAJADO! Polui o namespace atual, torna difícil saber de onde veio cada função/variável, aumenta muito o risco de
conflitos de nomes e dificulta a leitura e manutenção do código. Evite usar isso, exceto talvez em consoles interativos para testes rápidos.
Onde Python Procura Módulos? Quando você usa import , Python procura o módulo na seguinte ordem:
# import sys
# print(sys.path) # Mostra a lista de diretórios onde Python procura
1. Crie um Arquivo .py : Salve seu código Python em um arquivo com a extensão .py . Por exemplo, crie utilitarios.py :
# utilitarios.py
PI = 3.14159
2. Importe o Módulo: Em outro arquivo Python (por exemplo, principal.py ) no mesmo diretório (ou em um diretório que esteja no sys.path ),
importe seu módulo:
# principal.py
import utilitarios # Importa o arquivo utilitarios.py
s = utilitarios.somar(20, 7)
print(f"Soma via módulo: {s}")
d = utilitarios.subtrair(10, 4)
print(f"Subtração via módulo: {d}")
3. Executando:
Se você executar python principal.py , a saída será:
Page 35 of 85
Soma via módulo: 27
Subtração via módulo: 6
Acessando constante do módulo: 3.14159
Módulos são a base da organização e reutilização de código em Python, permitindo a criação de bibliotecas e programas complexos de forma estruturada.
Lambda e Docstrings são ferramentas úteis dentro desse contexto para criar pequenas funções e documentar seu código adequadamente.
1. Pacotes (Packages)
O que são?
Se um módulo é como um arquivo .py contendo código reutilizável, um pacote é como uma pasta (diretório) que contém módulos (e potencialmente
outros sub-pacotes). Pacotes são a forma de estruturar o namespace (espaço de nomes) dos módulos Python usando a notação de ponto
( pacote.modulo , pacote.subpacote.modulo ).
Organização: Para projetos maiores, colocar todo o código em um único diretório ou em poucos módulos grandes torna-se caótico. Pacotes
permitem agrupar módulos relacionados logicamente, criando uma estrutura hierárquica clara (ex: um pacote interface_grafica com
módulos botoes.py , menus.py ; um pacote dados com validacao.py , conexao_db.py ).
Namespace Hierárquico: Evitam conflitos de nomes entre módulos. Você pode ter um utils.py dentro do pacote interface_grafica e
outro utils.py dentro do pacote dados sem problemas, pois eles serão acessados como interface_grafica.utils e dados.utils .
Distribuição: Facilitam a criação de bibliotecas e frameworks que podem ser distribuídos e instalados por outros usuários (via pip ).
1. Crie um Diretório: Crie uma pasta com o nome que você deseja dar ao seu pacote (ex: meu_pacote ).
2. Adicione Módulos: Coloque seus arquivos .py (módulos) dentro desse diretório (ex: modulo1.py , modulo2.py ).
3. Crie o Arquivo __init__.py (Crucial!): Dentro do diretório do pacote ( meu_pacote/ ), crie um arquivo chamado exatamente
__init__.py .
O Papel do __init__.py :
Marcação: Historicamente, a presença do arquivo __init__.py era o que fazia o Python tratar um diretório como um pacote (um regular
package). Embora o Python 3.3 tenha introduzido namespace packages que podem não exigir __init__.py , para a maioria dos casos e para
compatibilidade, é uma prática padrão e recomendada incluir um __init__.py em cada diretório de pacote.
Inicialização do Pacote: O código dentro do __init__.py é executado automaticamente na primeira vez que qualquer parte do pacote é
importada. Isso permite realizar tarefas de inicialização para o pacote, como:
Definir variáveis a nível de pacote.
Importar sub-módulos ou funções específicas para torná-las acessíveis diretamente no nível do pacote (ex: fazer from . import
modulo1 dentro do __init__.py permite que o usuário faça import meu_pacote e depois acesse meu_pacote.modulo1 ).
Configurar logging, conexões, etc.
Controle de Importação ( __all__ ): Você pode definir uma lista chamada __all__ dentro do __init__.py . Essa lista especifica quais
nomes (módulos, funções, classes, variáveis) devem ser importados quando um usuário faz from nome_do_pacote import * . Se __all__
Page 36 of 85
não estiver definido, import * importará todos os nomes públicos (que não começam com _ ) definidos no __init__.py . Usar import *
é geralmente desaconselhado, mas __all__ dá ao autor do pacote controle sobre o que é exportado nesse caso.
Exemplo de Estrutura:
projeto/
├── principal.py
└── meu_pacote/ <-- Diretório do Pacote
├── __init__.py <-- Arquivo de inicialização do pacote
├── modulo1.py
├── modulo2.py
└── sub_pacote/ <-- Sub-pacote (também um diretório)
├── __init__.py <-- Arquivo de inicialização do sub-pacote
└── modulo_interno.py
# meu_pacote/__init__.py
print(f"Inicializando pacote 'meu_pacote'...")
# Torna a função 'func1' de modulo1 acessível como meu_pacote.func1
from .modulo1 import func1
# Define o que 'from meu_pacote import *' importará
__all__ = ["func1", "modulo2"] # Exporta func1 (importada acima) e o módulo modulo2
# meu_pacote/modulo1.py
def func1():
return "Função 1 de modulo1"
def _func_privada(): # Não será importada por 'import *' por causa do '_'
return "Privada"
# meu_pacote/modulo2.py
VAR_MOD2 = "Variável em modulo2"
# meu_pacote/sub_pacote/__init__.py
print(f"Inicializando sub-pacote 'sub_pacote'...")
from .modulo_interno import func_interna
# meu_pacote/sub_pacote/modulo_interno.py
def func_interna():
return "Função de modulo_interno dentro de sub_pacote"
# principal.py
print("--- Importando pacote completo ---")
import meu_pacote
Page 37 of 85
print(func_interna())
Importações Relativas: Dentro de um mesmo pacote, você pode usar importações relativas para importar outros módulos/sub-pacotes do mesmo
pacote, usando pontos ( . ):
from . import modulo_irmao : Importa modulo_irmao que está no mesmo diretório ( __init__.py ou outro módulo).
from .modulo_irmao import funcao : Importa funcao do modulo_irmao .
from .. import pacote_pai_modulo : Importa do diretório pai (sobe um nível).
from ..pacote_vizinho import modulo : Sobe um nível e entra em pacote_vizinho .
Nota: Importações relativas só funcionam quando o arquivo faz parte de um pacote e o código está sendo executado como parte desse pacote
(geralmente não funcionam se você executar o arquivo diretamente como script principal).
O que é?
A Biblioteca Padrão do Python é uma vasta coleção de módulos e pacotes que vêm incluídos em toda instalação padrão do Python. Ela incorpora a
filosofia "batteries included" (baterias incluídas) do Python, fornecendo ferramentas prontas para uso para uma ampla gama de tarefas comuns, sem a
necessidade de instalar pacotes de terceiros.
Reutilização e Eficiência: Evita que você precise "reinventar a roda" para tarefas comuns como operações matemáticas, manipulação de texto,
trabalho com arquivos, redes, datas, formatos de dados, etc.
Portabilidade: O código que usa apenas a biblioteca padrão funcionará em qualquer sistema onde o Python esteja instalado corretamente.
Confiabilidade: São módulos bem testados e mantidos pela comunidade principal do Python.
Base de Aprendizado: Explorar a biblioteca padrão é uma ótima maneira de aprender padrões de programação Python e ver exemplos de código
bem escrito.
Fundação: Muitos pacotes de terceiros são construídos sobre ou estendem a funcionalidade da biblioteca padrão.
math :
Propósito: Fornece acesso a funções matemáticas definidas pelo padrão C, além de constantes matemáticas. Lida principalmente com
números de ponto flutuante ( float ).
Itens Úteis:
math.sqrt(x) : Raiz quadrada de x .
math.pow(x, y) : x elevado à potência y (similar a x ** y , mas sempre retorna float).
math.sin(x) , math.cos(x) , math.tan(x) : Funções trigonométricas (esperam radianos).
math.radians(graus) , math.degrees(radianos) : Conversão entre graus e radianos.
math.log(x, base) , math.log10(x) , math.log2(x) : Logaritmos.
math.ceil(x) : Menor inteiro maior ou igual a x (teto).
math.floor(x) : Maior inteiro menor ou igual a x (piso).
math.pi : Constante Pi (π ≈ 3.14159...).
math.e : Constante de Euler (e ≈ 2.71828...).
import math
print(math.sqrt(16)) # Saída: 4.0
print(math.pi) # Saída: 3.141592653589793
print(math.ceil(4.2)) # Saída: 5
random :
Propósito: Implementa geradores de números pseudoaleatórios para várias distribuições. Útil para simulações, jogos, embaralhamento,
seleção aleatória.
Itens Úteis:
random.random() : Retorna um float aleatório no intervalo [0.0, 1.0) .
random.randint(a, b) : Retorna um inteiro aleatório N tal que a <= N <= b .
Page 38 of 85
random.randrange(start, stop[, step]) : Retorna um elemento selecionado aleatoriamente de range(start, stop,
step) .
random.choice(sequencia) : Retorna um elemento aleatório de uma sequência não vazia (lista, tupla, string).
random.shuffle(lista) : Embaralha os itens de uma lista in-place.
random.sample(populacao, k) : Retorna uma lista de k elementos únicos escolhidos da populacao sem reposição.
import random
print(random.randint(1, 10)) # Ex: 7
nomes = ["Ana", "Bia", "Caio"]
print(random.choice(nomes)) # Ex: Bia
random.shuffle(nomes)
print(nomes) # Ex: ['Caio', 'Ana', 'Bia']
os :
Propósito: Fornece uma maneira de usar funcionalidades dependentes do sistema operacional, como trabalhar com arquivos e diretórios,
variáveis de ambiente, processos, etc. Muitas funções relacionadas a caminhos estão no submódulo os.path .
Itens Úteis:
os.getcwd() : Retorna o diretório de trabalho atual (Current Working Directory).
os.listdir(caminho='.') : Retorna uma lista contendo os nomes das entradas no diretório dado por caminho .
os.mkdir(caminho) : Cria um diretório.
os.makedirs(caminho) : Cria um diretório, criando diretórios pais necessários.
os.remove(caminho) : Remove (deleta) um arquivo.
os.rmdir(caminho) : Remove um diretório vazio.
os.rename(src, dst) : Renomeia ou move um arquivo ou diretório.
os.environ : Um dicionário representando as variáveis de ambiente. os.environ.get('NOME_VAR') para ler.
os.path.join(path, *paths) : Junta componentes de caminho de forma inteligente (usando / ou \ conforme o SO). Muito
recomendado!
os.path.exists(caminho) : Verifica se um caminho existe.
os.path.isfile(caminho) : Verifica se é um arquivo.
os.path.isdir(caminho) : Verifica se é um diretório.
os.path.split(caminho) : Divide o caminho no diretório e nome do arquivo/diretório final.
os.path.splitext(caminho) : Divide o caminho no nome base e extensão.
import os
print(os.getcwd())
# os.mkdir("nova_pasta") # Cria a pasta
print(os.path.join("pasta", "subpasta", "arquivo.txt")) # Saída: pasta/subpasta/arquivo.txt ou pasta\sub
print(os.path.exists("principal.py")) # Provavelmente True
sys :
Propósito: Fornece acesso a algumas variáveis e funções mantidas ou usadas pelo interpretador Python. Permite interagir com o ambiente
de execução do Python.
Itens Úteis:
sys.argv : Lista de argumentos da linha de comando passados para um script Python. sys.argv[0] é o nome do script.
sys.path : Lista de strings que especifica os caminhos de busca para módulos. Pode ser modificada.
sys.platform : Identificador da plataforma (ex: 'linux', 'win32', 'darwin').
sys.version : String com a versão do interpretador Python.
sys.exit(codigo_saida=0) : Sai do interpretador Python. O codigo_saida opcional pode indicar sucesso (0) ou erro (outro
valor).
sys.stdin , sys.stdout , sys.stderr : Objetos de arquivo correspondendo aos fluxos padrão de entrada, saída e erro.
import sys
print(f"Argumentos: {sys.argv}")
print(f"Plataforma: {sys.platform}")
# if algum_erro_grave:
# sys.exit(1) # Termina o script com código de erro 1
datetime :
Page 39 of 85
Propósito: Fornece classes para trabalhar com datas e horas de maneiras simples e complexas.
Classes Principais:
datetime.date : Representa uma data (ano, mês, dia). date.today() .
datetime.time : Representa uma hora (hora, minuto, segundo, microssegundo, fuso horário).
datetime.datetime : Combinação de data e hora. datetime.now() , datetime.utcnow() .
datetime.timedelta : Representa uma duração, a diferença entre duas datas ou horas. Usado para aritmética de datas/horas.
Métodos Úteis:
strftime(formato) : Formata um objeto date/time/datetime como uma string de acordo com códigos de formato (ex: "%Y-%m-
%d %H:%M:%S" ).
strptime(string_data, formato) : Converte uma string em um objeto datetime com base em um formato.
hoje = date.today()
print(hoje) # Ex: 2023-10-27
agora = datetime.now()
print(agora) # Ex: 2023-10-27 10:30:00.123456
print(agora.strftime("%d/%m/%Y")) # Saída: 27/10/2023
data_str = "2024-01-15"
data_obj = datetime.strptime(data_str, "%Y-%m-%d").date()
print(data_obj) # Saída: 2024-01-15
json :
Propósito: Fornece mecanismos para codificar (serializar) objetos Python no formato JSON (JavaScript Object Notation) e decodificar
(desserializar) strings ou arquivos JSON de volta para objetos Python. Essencial para comunicação com APIs web e armazenamento de dados
simples.
Itens Úteis:
json.dumps(obj, indent=None) : Codifica o objeto Python obj em uma string JSON. indent pode ser usado para formatar
a saída de forma legível (ex: indent=4 ).
json.dump(obj, fp) : Codifica obj e escreve a string JSON no objeto arquivo fp (aberto para escrita).
json.loads(string_json) : Decodifica uma string JSON string_json em um objeto Python (dicts, lists, strings, numbers,
booleans, None).
json.load(fp) : Lê dados JSON do objeto arquivo fp (aberto para leitura) e os decodifica em um objeto Python.
import json
csv :
Propósito: Implementa classes para ler e escrever dados tabulares no formato CSV (Comma Separated Values). Lida com dialetos diferentes
(delimitadores, caracteres de citação).
Itens Úteis:
Page 40 of 85
csv.reader(arquivo_csv, delimiter=',', quotechar='"') : Retorna um objeto leitor que itera sobre as linhas no
arquivo_csv (um iterável, como um objeto arquivo aberto). Cada linha é retornada como uma lista de strings.
csv.writer(arquivo_csv, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) : Retorna um objeto
escritor para converter dados do usuário em strings delimitadas no objeto arquivo (aberto para escrita). writerow() escreve uma
linha, writerows() escreve múltiplas.
csv.DictReader(arquivo_csv) : Semelhante ao reader , mas cada linha é retornada como um dicionário, onde as chaves são
tiradas da primeira linha (cabeçalho).
csv.DictWriter(arquivo_csv, fieldnames) : Semelhante ao writer , mas opera com dicionários. Requer fieldnames
(lista de chaves/cabeçalhos). writeheader() escreve a linha de cabeçalho.
import csv
# Exemplo de leitura
# with open('dados.csv', 'r', newline='') as f:
# leitor = csv.reader(f)
# cabecalho = next(leitor) # Pega a primeira linha (cabeçalho)
# print(f"Cabeçalho: {cabecalho}")
# for linha in leitor:
# print(f"Linha: {linha}") # linha é uma lista ['Bob', '25'] etc.
Conhecer e saber usar pacotes para organizar seu próprio código e aproveitar a riqueza da biblioteca padrão são passos essenciais para se tornar um
programador Python proficiente. A documentação oficial do Python é um excelente recurso para explorar todos os módulos disponíveis na biblioteca padrão.
3. Programação Orientada a Objetos (OOP):
1. Conceitos Fundamentais
Classe (Class):
O que é? Uma classe é como um planta baixa, um molde ou um template para criar objetos. Ela define um tipo de objeto, especificando quais
atributos (dados/características) ele terá e quais métodos (ações/comportamentos) ele poderá realizar.
Analogia: Se você pensar em "Cachorro" como um conceito, a classe Cachorro seria a descrição geral de qualquer cachorro: ele tem um nome,
uma raça, uma idade (atributos) e pode latir, comer, dormir (métodos). A classe em si não é um cachorro real, é apenas a definição do que significa
ser um cachorro.
O que é? Um objeto é uma ocorrência concreta ou uma instância de uma classe. É o "produto" criado a partir da planta baixa (classe). Cada objeto
criado a partir da mesma classe terá a estrutura definida pela classe (mesmos tipos de atributos e métodos), mas pode ter valores diferentes para
seus atributos.
Analogia: Seguindo a analogia do cachorro, "Rex" (um labrador de 3 anos) e "Luna" (uma poodle de 5 anos) seriam objetos ou instâncias da classe
Cachorro . Ambos são cachorros (criados a partir da classe Cachorro ), mas são indivíduos distintos com nomes, raças e idades específicas.
Atributos (Attributes):
O que são? Atributos são variáveis associadas a uma classe ou a um objeto específico. Eles representam os dados ou o estado do objeto.
Tipos:
Atributos de Instância: São específicos de cada objeto individual. Se mudarmos o atributo nome do objeto rex , isso não afeta o atributo
nome do objeto luna . Geralmente são definidos dentro do método __init__ usando self.nome_atributo = valor .
Atributos de Classe: Pertencem à classe em si e são compartilhados por todas as instâncias dessa classe. Se um atributo de classe for
modificado, a mudança é refletida em todas as instâncias (a menos que uma instância tenha seu próprio atributo com o mesmo nome, o que
Page 41 of 85
"sombrearia" o atributo da classe para aquela instância específica). São definidos diretamente dentro da classe, fora de qualquer método.
Analogia: Para o objeto rex , os atributos de instância seriam nome = "Rex" , raca = "Labrador" , idade = 3 . Um atributo de classe
para Cachorro poderia ser especie = "Canis familiaris" , que seria o mesmo para rex e luna .
Métodos (Methods):
O que são? Métodos são funções definidas dentro de uma classe. Eles definem os comportamentos ou ações que um objeto dessa classe pode
realizar. Métodos frequentemente operam sobre os atributos do objeto (lendo ou modificando seu estado).
Parâmetro self (Crucial): O primeiro parâmetro de qualquer método de instância em Python é, por convenção, chamado de self . Ele
representa a própria instância (objeto) na qual o método está sendo chamado. Python passa esse argumento automaticamente quando você
chama objeto.metodo() . Você usa self dentro do método para acessar os atributos ( self.nome_atributo ) ou outros métodos
( self.outro_metodo() ) daquela instância específica.
Analogia: Para um objeto Cachorro , métodos poderiam ser latir() , comer(comida) , fazer_aniversario() . O método latir()
poderia imprimir "Rex está latindo!", usando o atributo nome (acessado via self.nome ). O método fazer_aniversario() poderia
incrementar o atributo idade (usando self.idade += 1 ).
Sintaxe: Para definir uma classe em Python, você usa a palavra-chave class seguida pelo nome da classe e dois-pontos. O corpo da classe (indentado)
contém as definições de atributos (geralmente de classe) e métodos.
Convenção de Nomenclatura (PEP 8): Nomes de classes devem usar CamelCase (ou CapWords ), começando com letra maiúscula e sem
underscores (ex: MinhaClasse , Cachorro , HttpRequest ).
Exemplo Básico:
3. Construtor ( __init__ )
O que é? O método __init__ é um método especial (reconhecido pelos underscores duplos, chamados de "dunder methods" ou "magic methods")
dentro de uma classe. Ele funciona como o construtor da classe.
Propósito: Sua principal função é inicializar um novo objeto (instância) assim que ele é criado. É aqui que você normalmente define e atribui os valores
iniciais aos atributos de instância específicos daquele objeto.
Execução Automática: O método __init__ é chamado automaticamente pelo Python toda vez que você cria uma nova instância da classe (usando
NomeDaClasse() ). Você não o chama diretamente pelo nome ( objeto.__init__() ).
Sintaxe e Parâmetros:
O primeiro parâmetro de __init__ sempre deve ser self , que representa a instância recém-criada.
Você pode definir parâmetros adicionais após self . Esses parâmetros receberão os valores que você passar ao criar a instância (ex:
Cachorro("Rex", "Labrador") ).
Dentro do __init__ , você usa self.nome_atributo = valor_parametro para criar os atributos de instância e atribuir os valores
iniciais a eles.
Exemplo com __init__ :
class Cachorro:
# Atributo de classe
especie = "Canis familiaris"
Page 42 of 85
# Criando e atribuindo ATRIBUTOS DE INSTÂNCIA usando 'self'
# Estes atributos pertencem a CADA objeto Cachorro individualmente
self.nome = nome_param
self.raca = raca_param
self.idade = idade_param # Usa o valor padrão 0 se não for fornecido
# Método de instância
def latir(self):
print(f"{self.nome} diz: Au au!") # Acessa o atributo 'nome' da instância
print("-" * 20)
print("-" * 20)
Resumo:
Entender esses conceitos é o primeiro passo crucial para trabalhar com Programação Orientada a Objetos em Python, o que permite criar código mais
organizado, reutilizável e fácil de manter, especialmente para aplicações complexas.
Page 43 of 85
1. self : Referência à Instância Atual
O que é? self é o primeiro parâmetro convencionalmente usado em métodos de instância dentro de uma classe Python. Ele não é uma palavra-chave
da linguagem (tecnicamente, você poderia usar outro nome, mas isso é extremamente desaconselhado e quebraria as convenções e a legibilidade).
Qual seu Propósito? Quando você define um método dentro de uma classe (como __init__ , latir , fazer_aniversario nos exemplos
anteriores), esse método precisa de uma forma de se referir ao objeto específico (instância) no qual ele está sendo chamado. self é essa referência.
Ele permite que o método acesse os atributos e outros métodos daquela instância particular.
Como Funciona? Quando você chama um método em um objeto (ex: meu_cachorro_rex.latir() ), Python automaticamente passa a instância
( meu_cachorro_rex , neste caso) como o primeiro argumento para o método. Você define o parâmetro self na assinatura do método para recebê-
lo, mas não o passa explicitamente na chamada.
Uso dentro do Método: Dentro do corpo do método, você usa self. para acessar atributos ou chamar outros métodos pertencentes à mesma
instância:
self.nome_atributo : Acessa o valor do atributo nome_atributo da instância atual.
self.outro_metodo() : Chama o outro_metodo pertencente à instância atual.
Exemplo Revisitado:
class Pessoa:
def __init__(self, nome_inicial):
# 'self' aqui é a instância recém-criada
# Criando o atributo de instância 'nome' nesta instância específica
self.nome = nome_inicial
def apresentar(self):
# 'self' aqui é a instância na qual 'apresentar()' foi chamado
# Acessando o atributo 'nome' DESTA instância específica
print(f"Olá, meu nome é {self.nome}.")
# Criando instâncias
p1 = Pessoa("Alice")
p2 = Pessoa("Bob")
Em Resumo: self é a ponte essencial entre um método de instância e os dados (atributos) e outros comportamentos (métodos) do objeto específico
ao qual ele pertence.
2. Pilares da OOP
Estes são os quatro princípios fundamentais que guiam o design e a implementação de sistemas orientados a objetos.
a) Encapsulamento (Encapsulation)
Conceito: Agrupar os dados (atributos) e os métodos (comportamentos) que operam nesses dados dentro de uma única unidade, a classe/objeto. Além
disso, envolve o princípio de ocultação de informações (information hiding), que significa restringir o acesso direto ao estado interno (atributos) de um
objeto, expondo apenas uma interface controlada (métodos públicos).
Propósito:
Organização: Mantém dados e funcionalidades relacionadas juntos.
Proteção: Protege os dados internos de serem modificados acidentalmente ou de forma inválida por código externo. Garante a integridade do
estado do objeto.
Page 44 of 85
Simplificação: Esconde a complexidade interna. O usuário do objeto interage com ele através de uma interface simples (métodos públicos) sem
precisar conhecer os detalhes da implementação.
Manutenibilidade: Se a implementação interna de um atributo ou método mudar, o código externo que usa a interface pública não precisa ser
alterado (desde que a interface permaneça a mesma).
Mecanismo em Python:
O agrupamento é inerente à definição da class .
A ocultação de informações é feita principalmente por convenções de nomenclatura:
_nome_atributo (underline simples): Convenção para indicar que um atributo ou método é para uso "interno" ou "protegido". Não há
restrição técnica de acesso, mas sinaliza aos outros desenvolvedores: "Não use isso diretamente de fora da classe ou subclasses, a menos
que saiba o que está fazendo".
__nome_atributo (underline duplo): Ativa o mecanismo de name mangling (desfiguração de nome). Python renomeia o atributo
internamente para _NomeDaClasse__nome_atributo . Isso torna mais difícil (mas não impossível) o acesso direto de fora da classe. É a
forma mais próxima de "privado" em Python, mas seu objetivo principal é evitar conflitos de nomes em subclasses (herança), não garantir
segurança total.
Métodos públicos (sem underline no início) formam a interface para interagir com o objeto. Métodos "getter" (para obter valores) e "setter" (para
definir valores com validação) podem ser usados para controle mais fino, embora o estilo Python muitas vezes prefira acesso direto a atributos (ou
o uso de properties ).
Exemplo:
class ContaBancaria:
def __init__(self, titular, saldo_inicial=0):
self.titular = titular # Atributo público
# Atributo "protegido" por convenção, indica uso interno
# Poderíamos usar __saldo para name mangling, mas _ é comum
self._saldo = saldo_inicial
b) Herança (Inheritance)
Page 45 of 85
Conceito: Um mecanismo que permite que uma nova classe (chamada subclasse, classe derivada ou classe filha) adquira (herde) propriedades
(atributos) e comportamentos (métodos) de uma classe existente (chamada superclasse, classe base ou classe pai).
Propósito:
Reutilização de Código: Evita a necessidade de reescrever código comum em múltiplas classes. A lógica da superclasse está disponível
automaticamente na subclasse.
Relação "É UM" (Is-A): Modela hierarquias do mundo real ou conceituais. Uma Cachorro é um Animal . Uma ContaPoupanca é uma
ContaBancaria .
Especialização: Permite que a subclasse estenda ou modifique o comportamento herdado para atender a necessidades específicas, adicionando
novos atributos/métodos ou sobrescrevendo (overriding) métodos existentes.
Mecanismo em Python:
Define-se a herança colocando o nome da superclasse entre parênteses após o nome da subclasse: class Subclasse(Superclasse): .
A subclasse tem acesso a todos os métodos e atributos (não mangled) da superclasse.
Sobrescrita de Método (Method Overriding): Se a subclasse define um método com o mesmo nome de um método na superclasse, a versão da
subclasse será usada para objetos da subclasse.
Função super() : É usada dentro da subclasse para chamar métodos da superclasse. É muito comum usá-la no __init__ da subclasse para
garantir que o construtor da superclasse seja executado, inicializando os atributos herdados. super().__init__(...) . Também pode ser
usada para chamar outras versões de métodos da superclasse.
Exemplo:
def comer(self):
print(f"{self.nome} está comendo.")
# Criando instâncias
animal_generico = Animal("Criatura")
rex = Cachorro("Rex", "Labrador")
print("-" * 20)
c) Polimorfismo (Polymorphism)
Conceito: Significa "muitas formas". Em OOP, refere-se à capacidade de objetos de diferentes classes responderem à mesma chamada de método de
maneiras específicas para cada classe. Permite tratar objetos de tipos diferentes de forma uniforme.
Propósito:
Page 46 of 85
Flexibilidade e Extensibilidade: Permite escrever código genérico que pode operar sobre uma variedade de tipos de objetos sem precisar saber o
tipo exato de cada um, desde que eles compartilhem uma interface comum (métodos com o mesmo nome). Facilita adicionar novas classes ao
sistema sem modificar o código existente que as utiliza.
Interfaces Comuns: Define um "contrato" que diferentes classes podem implementar à sua maneira.
Mecanismo em Python:
Frequentemente alcançado através do que é chamado de Duck Typing: "Se anda como um pato e grasna como um pato, então deve ser um pato".
Python se preocupa menos com o tipo explícito do objeto e mais com se ele possui os métodos ou atributos necessários para realizar uma
operação. Se objetos de classes diferentes têm um método com o mesmo nome, você pode chamar esse método neles, e cada um executará sua
própria implementação.
A Herança com sobrescrita de métodos (como no exemplo anterior com comer ) é uma forma comum de implementar polimorfismo.
Interfaces implícitas ou explícitas (usando Abstract Base Classes - ABCs do módulo abc ) também facilitam o polimorfismo.
Exemplo:
class Pato(Animal):
def __init__(self, nome):
super().__init__(nome)
def falar(self):
print(f"{self.nome} diz: Quack!")
# Função que funciona com qualquer objeto que tenha o método 'falar'
def fazer_animal_falar(animal_qualquer):
print(f"Tentando fazer {animal_qualquer.nome} falar...")
# Duck Typing em ação: não importa se é Gato, Pato ou Cachorro,
# desde que TENHA o método 'falar'.
animal_qualquer.falar()
print("-" * 20)
fazer_animal_falar(donald)
# Saída: Tentando fazer Donald falar...
# Saída: Donald diz: Quack!
fazer_animal_falar(toto)
# Saída: Tentando fazer Totó falar...
# Saída: Totó diz: Au au!
d) Abstração (Abstraction)
Page 47 of 85
Conceito: O processo de esconder os detalhes complexos da implementação e expor apenas as funcionalidades essenciais (a interface) de um objeto.
Foca no quê o objeto faz, em vez de como ele faz.
Propósito:
Simplificação: Torna os objetos mais fáceis de usar, pois o usuário só precisa se preocupar com a interface relevante.
Redução de Complexidade: Gerencia a complexidade ao dividir o sistema em componentes com interfaces bem definidas.
Isolamento de Mudanças: Permite que a implementação interna de um objeto mude sem afetar o código que o utiliza, desde que a interface
(abstração) seja mantida.
Mecanismo em Python:
A própria criação de classes com métodos públicos bem definidos já é uma forma de abstração. O usuário da classe ContaBancaria usa
depositar() e sacar() sem precisar saber como o saldo é armazenado ou atualizado internamente.
Classes Abstratas e Métodos Abstratos (Módulo abc ): Para uma abstração mais formal, Python oferece o módulo abc (Abstract Base Classes).
Você pode criar uma classe que herda de abc.ABC e marcar métodos como abstratos usando o decorador @abc.abstractmethod . Uma
classe abstrata não pode ser instanciada diretamente. Subclasses concretas são obrigadas a implementar todos os métodos abstratos herdados,
garantindo que elas forneçam a funcionalidade essencial definida pela abstração.
Exemplo (Conceitual e com ABC):
Conceitual: Pense em um controle remoto de TV. Ele oferece uma abstração: botões para ligar/desligar, mudar canal, ajustar volume. Você usa
esses botões (interface) sem precisar entender os circuitos eletrônicos, sinais infravermelhos ou protocolos de comunicação (implementação
complexa) que fazem a TV funcionar.
Com ABC:
import abc
@abc.abstractmethod
def calcular_perimetro(self):
"""Método obrigatório para calcular o perímetro."""
pass
class Circulo(FormaGeometrica):
def __init__(self, raio):
self.raio = raio
def calcular_area(self):
import math
return math.pi * self.raio ** 2
def calcular_perimetro(self):
import math
return 2 * math.pi * self.raio
ret = Retangulo(10, 5)
circ = Circulo(7)
Page 48 of 85
# Usando a abstração (interface comum)
for forma in formas:
# Não importa se é Retangulo ou Circulo, ambos TÊM calcular_area
print(f"Objeto: {type(forma).__name__}")
print(f" Área: {forma.calcular_area():.2f}")
print(f" Perímetro: {forma.calcular_perimetro():.2f}")
Esses quatro pilares trabalham juntos para criar softwares robustos, flexíveis, reutilizáveis e fáceis de manter. Dominá-los é essencial para a programação
orientada a objetos eficaz.
São métodos especiais que você define em suas classes para permitir que seus objetos se integrem com o comportamento embutido do Python. Eles não são
destinados a serem chamados diretamente pelo seu código (como meu_objeto.__str__() ), mas sim são chamados implicitamente pelo interpretador
Python em resposta a certas operações ou funções embutidas (como print() , len() , o operador + , acesso por índice [] , etc.).
Ao implementar esses métodos, você pode fazer com que seus objetos personalizados se comportem de maneira mais "Pythonica", agindo de forma
semelhante aos tipos de dados nativos (como listas, dicionários, números). Eles são a espinha dorsal do modelo de dados do Python.
class Ponto:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
# Retorna uma string fácil de ler para o usuário
return f"Coordenadas: ({self.x}, {self.y})"
p = Ponto(10, 20)
print(p) # Chama p.__str__() -> Saída: Coordenadas: (10, 20)
mensagem = f"O ponto é {p}" # Também chama p.__str__()
print(mensagem) # Saída: O ponto é Coordenadas: (10, 20)
Chamado por: repr(objeto) , quando você digita o nome do objeto no console interativo, em mensagens de depuração, e como fallback para
__str__ se __str__ não estiver definido.
Propósito: Retornar uma representação em string oficial, inequívoca do objeto. O ideal é que essa string, se passada para eval() , recrie um objeto
equivalente (embora nem sempre seja prático ou possível). Deve ser focada no desenvolvedor para depuração e logging.
Retorna: Uma string ( str ).
Comportamento Padrão: Se não implementado, retorna algo como <__main__.NomeDaClasse object at 0x... > .
Regra Geral: É recomendado implementar __repr__ para todas as classes que você criar. Se você só precisa de uma representação em string,
implemente __repr__ e deixe __str__ usar o fallback. Se precisar de representações diferentes para o usuário e o desenvolvedor, implemente
Page 49 of 85
ambos.
Exemplo:
class Ponto:
def __init__(self, x, y):
self.x = x
self.y = y
p = Ponto(3, 4)
print(str(p)) # Chama __str__ -> Saída: Coordenadas: (3, 4)
print(repr(p)) # Chama __repr__ -> Saída: Ponto(x=3, y=4)
print(p) # Em print, __str__ tem precedência -> Saída: Coordenadas: (3, 4)
# No console interativo:
# >>> p
# Ponto(x=3, y=4) # O console geralmente usa __repr__
# Exemplo de fallback:
class Vetor:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vetor({self.x}, {self.y})"
v = Vetor(1, -1)
print(v) # Como __str__ não existe, usa __repr__ -> Saída: Vetor(1, -1)
class DeckDeCartas:
def __init__(self):
naipes = ["Paus", "Ouros", "Copas", "Espadas"]
valores = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
# O estado interno é uma lista de cartas
self._cartas = [f"{v} de {n}" for n in naipes for v in valores]
def __len__(self):
# Retorna o número de cartas no deck (o tamanho da lista interna)
return len(self._cartas)
def embaralhar(self):
import random
random.shuffle(self._cartas)
def tirar_carta(self):
if len(self._cartas) > 0:
return self._cartas.pop()
else:
return None
Page 50 of 85
deck = DeckDeCartas()
print(f"Tamanho inicial do deck: {len(deck)}") # Chama deck.__len__() -> Saída: 52
deck.tirar_carta()
deck.tirar_carta()
print(f"Tamanho do deck após tirar 2 cartas: {len(deck)}") # Saída: 50
class Vetor2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vetor2D({self.x}, {self.y})"
v1 = Vetor2D(1, 2)
v2 = Vetor2D(3, 4)
v3 = v1 + v2 # Chama v1.__add__(v2)
__getitem__(self, key) :
class DeckDeCartas:
# ... (continuação do __init__)
def __init__(self):
naipes = ["Paus", "Ouros", "Copas", "Espadas"]
valores = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
self._cartas = [f"{v} de {n}" for n in naipes for v in valores]
def __len__(self):
return len(self._cartas)
# Permite acessar cartas por índice
def __getitem__(self, position):
return self._cartas[position] # Delega para a lista interna
Page 51 of 85
deck = DeckDeCartas()
primeira_carta = deck[0] # Chama deck.__getitem__(0)
ultima_carta = deck[-1] # Chama deck.__getitem__(-1)
print(f"Primeira carta: {primeira_carta}") # Saída: A de Paus
print(f"Última carta: {ultima_carta}") # Saída: K de Espadas
# fatia = deck[0:5] # Também usa __getitem__ com um objeto slice
__call__(self, ...) :
Métodos de Comparação:
__enter__(self) : Chamado ao entrar no bloco with . O valor retornado é atribuído à variável após as (se houver).
__exit__(self, exc_type, exc_val, exc_tb) : Chamado ao sair do bloco with , seja normalmente ou devido a uma exceção. Recebe
informações sobre a exceção (se ocorreu) e pode lidar com ela ou realizar limpeza (fechar arquivos, liberar recursos).
Em Resumo:
Dunder methods são a maneira como o Python permite que suas classes personalizadas se encaixem perfeitamente no ecossistema da linguagem. Ao
implementá-los corretamente, você cria objetos que são intuitivos de usar, se comportam como esperado com operadores e funções padrão, e se integram
bem com outras partes do Python e suas bibliotecas. Eles são essenciais para escrever código Python idiomático e poderoso. Sempre consulte a
documentação oficial do Python ("Data Model") para a lista completa e detalhes sobre cada método especial.
4. Manipulação de Arquivos e Erros:
Leitura e Escrita de Arquivos: open() , modos ( 'r' , 'w' , 'a' , 'b' ), o bloco with (essencial para gerenciamento de recursos).
Trabalhando com diferentes formatos: Texto, CSV, JSON.
Tratamento de Exceções: try , except , else , finally .
Tipos de Erros Comuns: TypeError , ValueError , IndexError , KeyError , FileNotFoundError .
Levantando Exceções: raise .
Criando Exceções Customizadas.
Okay, vamos detalhar cada um dos pontos sobre Manipulação de Arquivos e Erros em Python.
Manipular arquivos e lidar com erros de forma robusta são habilidades fundamentais para qualquer desenvolvedor Python, especialmente ao criar aplicações
que interagem com o mundo exterior (lendo dados, salvando configurações, processando logs, etc.).
file : O caminho (relativo ou absoluto) para o arquivo que você deseja abrir.
mode (opcional, padrão 'r'): Especifica como o arquivo será aberto. Os modos mais comuns são:
'r' (Read - Leitura): Abre o arquivo para leitura. Erro se o arquivo não existir. Este é o modo padrão.
Page 52 of 85
'w' (Write - Escrita): Abre o arquivo para escrita. Cria o arquivo se ele não existir. Sobrescreve o conteúdo se o arquivo já existir. Tenha
cuidado!
'a' (Append - Anexar): Abre o arquivo para escrita, mas adiciona o novo conteúdo ao final do arquivo existente. Cria o arquivo se ele não
existir.
'b' (Binary - Binário): Usado em conjunto com outros modos (ex: 'rb' , 'wb' ) para trabalhar com arquivos não-texto, como imagens,
executáveis, etc. Os dados são lidos/escritos como bytes.
'+' (Atualização): Usado em conjunto com outros modos (ex: 'r+' , 'w+' , 'a+' ) para permitir leitura e escrita no mesmo arquivo.
'r+' não cria o arquivo se não existir, 'w+' e 'a+' criam.
encoding (opcional): A codificação de caracteres a ser usada para arquivos de texto (ex: 'utf-8' , 'latin-1' , 'ascii' ). É altamente
recomendado especificar a codificação, especialmente 'utf-8' , que é muito comum e suporta uma vasta gama de caracteres. Se omitido,
Python usa uma codificação padrão do sistema, o que pode levar a erros ou comportamentos inesperados em diferentes máquinas.
Exemplo (Leitura):
except FileNotFoundError:
print("Erro: O arquivo 'meu_arquivo.txt' não foi encontrado.")
except Exception as e:
print(f"Ocorreu um erro inesperado ao ler o arquivo: {e}")
Page 53 of 85
print("Conteúdo anexado com sucesso ao 'novo_arquivo.txt'.")
except Exception as e:
print(f"Erro ao anexar ao arquivo: {e}")
Texto (.txt): Como visto nos exemplos acima, usamos os modos 'r' , 'w' , 'a' com encoding . A leitura/escrita é feita com strings.
CSV (Comma-Separated Values): Formato tabular comum. Python possui o módulo csv para facilitar a leitura e escrita.
Nome,Idade,Cidade
Alice,30,Nova York
Bob,25,Paris
Carlos,35,Londres
import csv
try:
with open('dados.csv', 'r', newline='', encoding='utf-8') as arquivo_csv:
# newline='' é importante para evitar linhas em branco extras
leitor_csv = csv.reader(arquivo_csv) # Cria um objeto leitor
cabecalho = next(leitor_csv) # Lê a primeira linha (cabeçalho)
print(f"Cabeçalho: {cabecalho}")
print("Dados:")
for linha in leitor_csv:
# Cada linha é uma lista de strings
print(linha)
# Ex: Acessando dados específicos
# print(f" - Nome: {linha[0]}, Idade: {linha[1]}, Cidade: {linha[2]}")
except FileNotFoundError:
print("Erro: Arquivo 'dados.csv' não encontrado.")
except Exception as e:
print(f"Erro ao ler o arquivo CSV: {e}")
import csv
dados_para_escrever = [
['Nome', 'Idade', 'Cidade'], # Cabeçalho
['Diana', 28, 'Berlim'],
['Ethan', 40, 'Tóquio']
]
try:
with open('saida.csv', 'w', newline='', encoding='utf-8') as arquivo_csv:
escritor_csv = csv.writer(arquivo_csv)
# Escreve todas as linhas de uma vez
escritor_csv.writerows(dados_para_escrever)
Page 54 of 85
# escritor_csv.writerow(dados_para_escrever[0]) # Cabeçalho
# escritor_csv.writerow(dados_para_escrever[1])
# escritor_csv.writerow(dados_para_escrever[2])
print("Arquivo 'saida.csv' escrito com sucesso.")
except Exception as e:
print(f"Erro ao escrever o arquivo CSV: {e}")
JSON (JavaScript Object Notation): Formato leve de troca de dados, muito usado em APIs web. Python tem o módulo json para serializar (Python ->
JSON) e desserializar (JSON -> Python) dados.
import json
dados_python = {
"nome": "Produto Exemplo",
"id": 12345,
"preco": 49.99,
"disponivel": True,
"tags": ["eletronico", "computador", "acessorio"],
"dimensoes": {
"altura": 10,
"largura": 20,
"profundidade": 5
}
}
try:
with open('dados.json', 'w', encoding='utf-8') as arquivo_json:
# json.dump escreve o objeto Python como JSON no arquivo
# ensure_ascii=False permite caracteres não-ASCII (como acentos)
# indent=4 formata o JSON para melhor leitura (indentação)
json.dump(dados_python, arquivo_json, ensure_ascii=False, indent=4)
print("Arquivo 'dados.json' escrito com sucesso.")
except Exception as e:
print(f"Erro ao escrever o arquivo JSON: {e}")
import json
try:
with open('dados.json', 'r', encoding='utf-8') as arquivo_json:
# json.load lê o conteúdo JSON do arquivo e o converte para um objeto Python
dados_carregados = json.load(arquivo_json)
except FileNotFoundError:
print("Erro: Arquivo 'dados.json' não encontrado.")
except json.JSONDecodeError:
print("Erro: O arquivo não contém JSON válido.")
except Exception as e:
print(f"Erro ao ler o arquivo JSON: {e}")
Exceções são erros que ocorrem durante a execução do programa. Se não forem tratadas, elas interrompem o fluxo normal e encerram o programa. O
tratamento de exceções permite que você lide com esses erros de forma controlada.
Page 55 of 85
try : O bloco de código onde você espera que um erro possa ocorrer é colocado dentro do try .
except <TipoDeErro> : Se um erro do tipo especificado ( <TipoDeErro> ) ocorrer dentro do bloco try , o código dentro do bloco except
correspondente é executado.
Você pode ter múltiplos blocos except para lidar com diferentes tipos de erro.
Um except sem especificar o tipo de erro captura qualquer exceção (geralmente não recomendado, é melhor ser específico).
Você pode capturar a instância do erro usando as variavel (ex: except ValueError as e: ).
else : (Opcional) O bloco else é executado somente se nenhuma exceção ocorrer no bloco try . É útil para código que deve rodar apenas se o try
foi bem-sucedido.
finally : (Opcional) O bloco finally é executado sempre, independentemente de ter ocorrido uma exceção ou não. É ideal para ações de limpeza
(como fechar conexões de rede ou arquivos, embora o with já faça isso para arquivos).
Exemplo Completo:
return resultado
# Testando
print(f"Resultado 10 / 2: {dividir(10, 2)}")
print("-" * 20)
print(f"Resultado 10 / 0: {dividir(10, 0)}")
print("-" * 20)
print(f"Resultado 10 / 'a': {dividir(10, 'a')}")
print("-" * 20)
Conhecer os tipos de erro comuns ajuda a escrever blocos except mais específicos e eficazes.
TypeError : Ocorre quando uma operação ou função é aplicada a um objeto de tipo inadequado.
try:
resultado = 5 + 'a' # Não se pode somar int com str
except TypeError as e:
print(f"TypeError: {e}") # output: unsupported operand type(s) for +: 'int' and 'str'
ValueError : Ocorre quando uma função recebe um argumento com o tipo correto, mas com um valor inadequado.
try:
numero = int('abc') # 'abc' é uma string (tipo certo), mas não representa um int (valor errado)
Page 56 of 85
except ValueError as e:
print(f"ValueError: {e}") # output: invalid literal for int() with base 10: 'abc'
IndexError : Ocorre ao tentar acessar um índice inválido em uma sequência (lista, tupla, string).
minha_lista = [1, 2, 3]
try:
print(minha_lista[5]) # Índice 5 não existe (índices válidos são 0, 1, 2)
except IndexError as e:
print(f"IndexError: {e}") # output: list index out of range
KeyError : Ocorre ao tentar acessar uma chave que não existe em um dicionário.
FileNotFoundError : Ocorre quando open() tenta abrir um arquivo para leitura ( 'r' ) que não existe no caminho especificado. (É uma subclasse
de IOError / OSError ).
try:
with open('arquivo_inexistente.txt', 'r') as f:
conteudo = f.read()
except FileNotFoundError as e:
print(f"FileNotFoundError: {e}") # output: [Errno 2] No such file or directory: 'arquivo_inexistente.txt
Às vezes, você mesmo quer sinalizar uma condição de erro no seu código. Você pode "levantar" (ou "lançar") uma exceção usando a palavra-chave raise . Isso
interrompe o fluxo normal e procura um bloco except que possa tratar essa exceção.
Exemplo:
def calcular_idade_media(idades):
if not isinstance(idades, list):
raise TypeError("O argumento 'idades' deve ser uma lista.")
if not idades: # Verifica se a lista está vazia
raise ValueError("A lista de idades não pode estar vazia.")
for idade in idades:
if not isinstance(idade, (int, float)) or idade < 0:
raise ValueError("Todas as idades na lista devem ser números não negativos.")
# Testando
try:
media1 = calcular_idade_media([20, 30, 40])
print(f"Média 1: {media1}")
Page 57 of 85
except (TypeError, ValueError) as e:
print(f"Erro ao calcular a média: {e}")
Para erros específicos do seu domínio de aplicação, pode ser útil criar suas próprias classes de exceção. Isso torna o tratamento de erros mais organizado e
legível.
Como criar: Defina uma nova classe que herda (direta ou indiretamente) da classe base Exception .
Exemplo:
class EmailInvalidoError(ErroValidacaoUsuario):
"""Exceção para formato de e-mail inválido."""
def __init__(self, email, mensagem="Formato de e-mail inválido fornecido"):
self.email = email
self.mensagem = f"{mensagem}: {email}"
super().__init__(self.mensagem) # Chama o construtor da classe pai (Exception)
class SenhaCurtaError(ErroValidacaoUsuario):
"""Exceção para senha muito curta."""
def __init__(self, tamanho_minimo, mensagem="Senha muito curta"):
self.tamanho_minimo = tamanho_minimo
self.mensagem = f"{mensagem}. Deve ter pelo menos {tamanho_minimo} caracteres."
super().__init__(self.mensagem)
# Testando
try:
validar_usuario("[email protected]", "senha1234")
validar_usuario("usuario_sem_arroba.com", "senhaforte") # Causa EmailInvalidoError
# validar_usuario("[email protected]", "curta") # Causa SenhaCurtaError
except EmailInvalidoError as e:
print(f"Erro de Validação (Email): {e}")
# print(f"Email problemático: {e.email}") # Acesso ao atributo customizado
except SenhaCurtaError as e:
print(f"Erro de Validação (Senha): {e}")
# print(f"Tamanho mínimo exigido: {e.tamanho_minimo}") # Acesso ao atributo customizado
except ErroValidacaoUsuario as e:
# Captura qualquer outro erro de validação que herde de ErroValidacaoUsuario
print(f"Erro de Validação Genérico: {e}")
except Exception as e:
print(f"Ocorreu um erro inesperado: {e}")
Page 58 of 85
Dominar a manipulação de arquivos e o tratamento de exceções é crucial para escrever programas Python confiáveis, robustos e fáceis de manter. O uso
correto do with para arquivos e uma abordagem estruturada para try/except/finally , juntamente com o conhecimento dos tipos de erro e a
capacidade de criar exceções customizadas, eleva significativamente a qualidade do seu código.
Estes são recursos poderosos da linguagem que permitem escrever código mais conciso, legível (em muitos casos) e eficiente, especialmente quando lidando
com sequências e iteração.
Comprehensions são uma sintaxe especial em Python para criar coleções (listas, dicionários ou sets) de forma concisa e elegante, muitas vezes substituindo
loops for mais verbosos. Elas são inspiradas na notação matemática de construção de conjuntos.
Ideia Central: Aplicar uma expressão a cada item de um iterável, opcionalmente filtrando itens com base em uma condição, e coletar os resultados em
uma nova coleção.
expressao : O que fazer com cada item para gerar o elemento da nova lista.
item : Variável que representa cada elemento do iteravel a cada iteração.
iteravel : A sequência ou coleção original (lista, tupla, range, string, etc.).
if condicao (Opcional): Um filtro. A expressao só é aplicada e o resultado incluído na nova_lista se a condicao for verdadeira para o
item .
Page 59 of 85
matriz = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# Forma tradicional
achatada_loop = []
for sublista in matriz:
for item in sublista:
achatada_loop.append(item)
print(f"Com loop: {achatada_loop}")
Dictionary Comprehensions: Cria um novo dicionário. A sintaxe é similar, mas usa {} e especifica chave: valor .
Formato Geral:
# Com loop
quadrados_dict_loop = {}
for i in range(5):
quadrados_dict_loop[i] = i * i
print(f"Com loop: {quadrados_dict_loop}")
Set Comprehensions: Cria um novo set (conjunto). A sintaxe usa {} como dicionários, mas especifica apenas uma expressao (como listas). Sets
automaticamente eliminam duplicatas.
Formato Geral:
numeros = [1, 2, 2, 3, 4, 4, 4, 5, 1]
# Com loop
quadrados_unicos_loop = set()
for n in numeros:
quadrados_unicos_loop.add(n * n)
print(f"Com loop: {quadrados_unicos_loop}")
Page 60 of 85
Benefícios das Comprehensions:
Cuidado: Evite comprehensions excessivamente complexas (múltiplos for e if aninhados). Nesses casos, um loop for tradicional pode ser mais
legível.
Generators são uma forma especial e muito eficiente em termos de memória de criar iteradores. Um iterador é um objeto que pode ser iterado (ou seja, você
pode usar um loop for nele) e que produz seus itens um de cada vez, sob demanda.
Ideia Central: Em vez de criar uma coleção inteira na memória de uma vez (como as comprehensions fazem), os generators produzem os valores apenas
quando solicitados (lazy evaluation / avaliação preguiçosa). Isso é extremamente útil para sequências muito grandes ou infinitas, onde armazenar tudo
na memória seria impraticável ou impossível.
São definidas como funções normais ( def ), mas em vez de usar return para devolver um valor final, elas usam a palavra-chave yield .
Quando uma função geradora é chamada, ela não executa o corpo da função imediatamente. Em vez disso, ela retorna um objeto gerador (um tipo
de iterador).
Cada vez que o método __next__() do gerador é chamado (o que acontece implicitamente em um loop for ou explicitamente com
next(gerador) ), a execução da função continua a partir do ponto onde parou (após o último yield ) até encontrar o próximo yield .
O valor especificado no yield é retornado.
O estado da função (variáveis locais) é preservado entre as chamadas next() .
Quando a função chega ao fim ou encontra um return (sem valor, ou o fim implícito), ela levanta uma exceção StopIteration , sinalizando
que não há mais itens a serem gerados (o loop for trata isso automaticamente).
def contador_simples(maximo):
print("-> Iniciando o gerador...")
n = 0
while n < maximo:
print(f"-> Yielding {n}")
yield n # Pausa aqui, retorna n, e preserva o estado (valor de n)
n += 1
print(f"-> Retomado após yield, n agora é {n}")
print("-> Fim do gerador.")
# StopIteration será levantado automaticamente aqui
try:
print(f"Chamada 1 next(): {next(meu_gerador)}")
print(f"Chamada 2 next(): {next(meu_gerador)}")
print(f"Chamada 3 next(): {next(meu_gerador)}") # Isso causará StopIteration
except StopIteration:
print("StopIteration capturada: O gerador terminou.")
Page 61 of 85
# Output:
# Usando em loop for:
# -> Iniciando o gerador...
# -> Yielding 0
# Loop for recebeu: 0
# -> Retomado após yield, n agora é 1
# -> Yielding 1
# Loop for recebeu: 1
# -> Retomado após yield, n agora é 2
# -> Yielding 2
# Loop for recebeu: 2
# -> Retomado após yield, n agora é 3
# -> Fim do gerador.
#
# Usando next() manually:
# <class 'generator'>
# -> Iniciando o gerador...
# -> Yielding 0
# Chamada 1 next(): 0
# -> Retomado após yield, n agora é 1
# -> Yielding 1
# Chamada 2 next(): 1
# -> Retomado após yield, n agora é 2
# -> Fim do gerador.
# StopIteration capturada: O gerador terminou.
def ler_linhas_grandes(caminho_arquivo):
try:
with open(caminho_arquivo, 'r', encoding='utf-8') as f:
for linha in f: # O próprio objeto arquivo é um iterador eficiente
yield linha.strip() # Processa e retorna uma linha de cada vez
except FileNotFoundError:
print(f"Erro: Arquivo {caminho_arquivo} não encontrado.")
# Poderia levantar uma exceção aqui também
Expressões Geradoras:
São sintaticamente muito parecidas com as List Comprehensions, mas usam parênteses () em vez de colchetes [] .
Elas também criam um objeto gerador (iterador) que produz os itens de forma preguiçosa (lazy).
Sintaxe:
# Generator expression (cria um objeto gerador, quase sem uso de memória inicial)
gerador_quadrados = (x * x for x in range(1000000))
print(f"Tipo do objeto gerado: {type(gerador_quadrados)}")
# print(f"Tamanho do gerador em memória: {sys.getsizeof(gerador_quadrados)} bytes") # Muito menor
Page 62 of 85
# print("Primeiros 5 quadrados do gerador:")
# count = 0
# for quad in gerador_quadrados:
# print(quad)
# count += 1
# if count >= 5:
# break
# Ou usa next()
# print(f"\nPróximo quadrado: {next(gerador_quadrados)}") # Continua de onde parou
Eficiência de Memória: O principal benefício. Ideal para conjuntos de dados muito grandes, streaming de dados ou sequências infinitas (ex:
números de Fibonacci).
Avaliação Preguiçosa (Lazy Evaluation): Os itens são gerados apenas quando necessários, o que pode economizar processamento se você não
precisar de todos os itens ou parar a iteração cedo.
Composabilidade: Geradores podem ser encadeados para criar pipelines de processamento de dados eficientes. A saída de um gerador pode ser a
entrada de outro, e os dados fluem item por item através do pipeline sem precisar armazenar resultados intermediários completos.
Exemplo de Pipeline:
def ler_arquivo(nome):
with open(nome, 'r') as f:
for linha in f:
yield linha
Em resumo, Comprehensions são ótimas para criar listas, dicionários e sets de forma concisa quando você precisa da coleção inteira na memória. Generators
(funções com yield ou expressões geradoras) são a escolha ideal quando a eficiência de memória é crucial, você está lidando com sequências muito
grandes ou infinitas, ou quer criar pipelines de dados preguiçosos. Ambos são ferramentas poderosas para escrever código Python mais expressivo e
eficiente.
5.3. Decorators ( @ )
Page 63 of 85
O que são?
Decorators são uma forma de metaprogramação em Python. Essencialmente, um decorator é uma função especial (ou classe) que recebe outra função
como argumento, adiciona alguma funcionalidade a ela (a "decoração") e retorna a função modificada ou uma nova função que encapsula a original. Eles
permitem alterar ou aprimorar o comportamento de funções ou métodos de forma limpa, reutilizável e sem modificar o código original da função
decorada.
Reutilização de Código (DRY - Don't Repeat Yourself): Aplicar a mesma lógica de pré/pós-processamento (como logging, verificação de
permissões, medição de tempo, caching) a múltiplas funções sem copiar e colar o código.
Separação de Responsabilidades: Mantém o código da função principal focado em sua tarefa principal, enquanto a lógica transversal (como
logging) fica no decorator.
Legibilidade: A sintaxe @nome_do_decorator acima da definição da função torna explícito que a função está sendo modificada ou aprimorada.
1. Funções de Ordem Superior: Decorators se baseiam no fato de que em Python, funções são objetos de primeira classe. Isso significa que podem
ser passadas como argumentos para outras funções, retornadas por outras funções e atribuídas a variáveis.
2. Sintaxe @ : A sintaxe @meu_decorator logo acima de def minha_funcao(): é um "açúcar sintático" (syntactic sugar) para a seguinte
operação:
def minha_funcao():
# ... corpo da função ...
minha_funcao = meu_decorator(minha_funcao)
Ou seja, a função original minha_funcao é passada para meu_decorator , e o nome minha_funcao agora se refere ao que quer que
meu_decorator retorne.
3. A Estrutura Típica de um Decorator (Função):
def meu_decorator(func_original):
# 'func_original' é a função que está sendo decorada (ex: minha_funcao)
Exemplo:
import time
import functools
def timer_decorator(func):
@functools.wraps(func)
Page 64 of 85
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
run_time = end_time - start_time
print(f"Função '{func.__name__}' executada em {run_time:.4f} segundos")
return result
return wrapper
def logger_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Log: Chamando '{func.__name__}' com argumentos {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"Log: '{func.__name__}' finalizada.")
return result
return wrapper
# Aplicando múltiplos decorators (a ordem importa: são aplicados de baixo para cima)
@logger_decorator # 2º a ser aplicado (mais externo)
@timer_decorator # 1º a ser aplicado (mais interno)
def processar_dados(n):
"""Função de exemplo que simula algum processamento."""
time.sleep(n) # Simula trabalho
return f"Processamento com n={n} concluído."
# Output esperado:
# Log: Chamando 'processar_dados' com argumentos (1.5,), {}
# Função 'processar_dados' executada em 1.50xx segundos <- Timer é executado 'dentro' do logger
# Log: 'processar_dados' finalizada.
# Resultado final: Processamento com n=1.5 concluído.
# Nome da função: processar_dados <- Preservado por @wraps
# Docstring: Função de exemplo que simula algum processamento. <- Preservado por @wraps
Decorators com Argumentos: Para criar um decorator que aceita argumentos (ex: @repeat(3) ), você precisa de uma camada extra de função (uma
"fábrica de decorators").
O que são?
Context Managers são objetos Python projetados para gerenciar recursos de forma segura e determinística, garantindo que ações de setup
(configuração inicial) e teardown (limpeza final) sejam executadas corretamente, mesmo se ocorrerem erros. Eles são usados principalmente com a
instrução with .
1. __enter__(self) :
É chamado no início da execução do bloco with .
Sua principal função é realizar a configuração do recurso (ex: abrir o arquivo, adquirir o lock).
O valor retornado por __enter__ é atribuído à variável especificada na cláusula as do with (se houver). Se não houver as , o valor de
retorno é descartado. Frequentemente, retorna self se o próprio objeto do context manager é o recurso a ser usado.
2. __exit__(self, exc_type, exc_val, exc_tb) :
Page 65 of 85
É chamado ao final da execução do bloco with , independentemente de como o bloco foi saído (normalmente, por exceção ou
return / break / continue ).
Sua principal função é realizar a limpeza do recurso (ex: fechar o arquivo, liberar o lock).
Os três argumentos ( exc_type , exc_val , exc_tb ) contêm informações sobre a exceção que causou a saída do bloco with , se
houver. Se o bloco saiu normalmente, esses três argumentos serão None .
Se __exit__ retornar True , ele indica que qualquer exceção que ocorreu foi tratada e suprimida (não será propagada para fora do
with ). Se retornar False (ou None , que é o padrão se não houver return ), a exceção (se houver) será propagada após a execução de
__exit__ .
import time
class TimerCM:
def __init__(self, nome="Bloco"):
self.nome = nome
print(f"Timer '{self.nome}': Entrando no bloco with...")
def __enter__(self):
print(f"Timer '{self.nome}': Iniciando cronômetro.")
self.start_time = time.perf_counter()
# Retorna o próprio objeto para possível uso com 'as'
# Poderia retornar qualquer outra coisa útil
return self
if exc_type:
# Se ocorreu uma exceção dentro do 'with'
print(f"Timer '{self.nome}': Uma exceção ocorreu: {exc_type.__name__}: {exc_val}")
# return False # Propaga a exceção (comportamento padrão)
# return True # Suprime (engole) a exceção
else:
# Se o bloco terminou normalmente
print(f"Timer '{self.nome}': Bloco concluído sem exceções.")
# Output esperado:
# --- Teste 1: Execução normal ---
# Timer 'Processo A': Entrando no bloco with...
# Timer 'Processo A': Iniciando cronômetro.
# Dentro do with. Objeto recebido: <class '__main__.TimerCM'>
Page 66 of 85
# Trabalho dentro do 'with' concluído.
# Timer 'Processo A': Saindo do bloco with.
# Timer 'Processo A': Tempo decorrido: 0.50xx segundos.
# Timer 'Processo A': Bloco concluído sem exceções.
#
# --- Teste 2: Com exceção ---
# Timer 'Processo B': Entrando no bloco with...
# Timer 'Processo B': Iniciando cronômetro.
# Dentro do with, antes da exceção.
# Timer 'Processo B': Saindo do bloco with.
# Timer 'Processo B': Tempo decorrido: 0.00xx segundos.
# Timer 'Processo B': Uma exceção ocorreu: ZeroDivisionError: division by zero
# Exceção ZeroDivisionError capturada FORA do with (como esperado).
@contextmanager
def timer_cm_func(nome="Bloco Func"):
print(f"Timer Func '{nome}': Setup (antes do yield)")
start_time = time.perf_counter()
try:
# O yield fornece o valor para a cláusula 'as' (pode ser None)
# A execução do bloco 'with' acontece aqui, onde o yield está
yield start_time # Exemplo: retorna o tempo de início
except Exception as e:
# Captura exceção ocorrida DENTRO do with
print(f"Timer Func '{nome}': Exceção no with: {type(e).__name__}")
raise # Re-levanta a exceção para ser tratada fora (ou __exit__ a suprimiria)
finally:
# Este bloco é executado SEMPRE (limpeza)
end_time = time.perf_counter()
elapsed = end_time - start_time
print(f"Timer Func '{nome}': Teardown (depois do yield/finally)")
print(f"Timer Func '{nome}': Tempo decorrido: {elapsed:.4f} segundos.")
# Usando
print("\n--- Teste com @contextmanager ---")
with timer_cm_func("Processo C") as inicio:
print(f"Dentro do with funcional. Tempo de início: {inicio:.4f}")
time.sleep(0.3)
O que são?
Expressões Regulares (Regex ou Regexp) são sequências de caracteres que definem um padrão de busca. Elas formam uma mini-linguagem
especializada para encontrar, extrair, substituir ou dividir strings com base nesses padrões. O módulo re em Python fornece as ferramentas para
trabalhar com regex.
Validação de Dados: Verificar se uma string está em um formato esperado (email, CPF, telefone, data, URL).
Extração de Dados (Parsing): Retirar informações específicas de textos não estruturados ou semi-estruturados (logs, HTML, configurações).
Busca e Substituição Avançada: Realizar substituições complexas baseadas em padrões, e não apenas em texto fixo.
Divisão de Strings (Splitting): Quebrar uma string em partes usando delimitadores mais complexos que um único caractere.
Page 67 of 85
1. Padrões (Patterns): A sequência de caracteres que define a regra de busca. Usa caracteres normais (que casam com eles mesmos) e
metacaracteres (que têm significado especial).
2. Metacaracteres Comuns:
. : Casa com qualquer caractere (exceto quebra de linha, por padrão).
^ : Casa com o início da string (ou início da linha no modo multiline).
$ : Casa com o fim da string (ou fim da linha no modo multiline).
* : Casa com 0 ou mais repetições do item anterior.
+ : Casa com 1 ou mais repetições do item anterior.
? : Casa com 0 ou 1 repetição do item anterior (torna opcional). Ou modo não-guloso (lazy) após * , + , ? .
{m} : Casa com exatamente m repetições.
{m,n} : Casa com entre m e n repetições.
[] : Define um conjunto de caracteres. Ex: [abc] casa com 'a', 'b' ou 'c'. [a-z] casa com qualquer letra minúscula. [^abc] casa com
qualquer caractere que não seja 'a', 'b' ou 'c'.
| : Operador OU. Ex: gato|cachorro casa com "gato" ou "cachorro".
() : Agrupa subpadrões e cria grupos de captura. O texto casado pelo grupo pode ser acessado separadamente.
\ : Caractere de escape. Usado para tratar um metacaractere como literal (ex: \. casa com um ponto literal) ou para indicar sequências
especiais.
3. Sequências Especiais Comuns:
\d : Casa com qualquer dígito decimal (equivale a [0-9] ).
\D : Casa com qualquer caractere não-dígito.
\w : Casa com qualquer caractere alfanumérico (letras, números e _ ). Equivalente a [a-zA-Z0-9_] .
\W : Casa com qualquer caractere não-alfanumérico.
\s : Casa com qualquer caractere de espaço em branco (espaço, tab, newline, etc.).
\S : Casa com qualquer caractere que não seja espaço em branco.
\b : Casa com uma fronteira de palavra (word boundary) - a posição entre um \w e um \W , ou início/fim de string.
\B : Casa com uma não-fronteira de palavra.
4. Raw Strings ( r"..." ): É altamente recomendado usar raw strings para definir padrões regex em Python. Isso evita que as barras invertidas \
sejam interpretadas pelo Python como sequências de escape antes de serem passadas para o motor regex. Ex: r"\bword\b" em vez de
"\\bword\\b" .
5. Funções Principais do Módulo re :
re.search(pattern, string, flags=0) : Procura o padrão em qualquer lugar da string. Retorna um objeto Match na primeira
ocorrência encontrada, ou None se não encontrar.
re.match(pattern, string, flags=0) : Procura o padrão apenas no início da string. Retorna um objeto Match se encontrar no
início, ou None caso contrário.
re.findall(pattern, string, flags=0) : Encontra todas as ocorrências não-sobrepostas do padrão na string. Retorna uma lista
de strings casadas. Se o padrão contiver grupos de captura, retorna uma lista de tuplas, onde cada tupla contém o texto capturado por cada
grupo.
re.finditer(pattern, string, flags=0) : Similar a findall , mas retorna um iterador que produz objetos Match para cada
ocorrência. Mais eficiente em memória para muitos matches.
re.sub(pattern, repl, string, count=0, flags=0) : Substitui as ocorrências do padrão na string pelo conteúdo de repl .
repl pode ser uma string (onde \1 , \2 etc. se referem a grupos capturados) ou uma função. Retorna a nova string com as substituições.
count limita o número de substituições.
re.split(pattern, string, maxsplit=0, flags=0) : Divide a string usando as ocorrências do padrão como delimitadores.
Retorna uma lista de strings.
re.compile(pattern, flags=0) : Compila um padrão regex em um objeto de padrão regex. Isso é útil para performance se você for
usar o mesmo padrão múltiplas vezes. O objeto compilado tem métodos equivalentes ( .search() , .match() , .findall() , etc.).
6. Objeto Match: Retornado por search , match e finditer . Métodos úteis:
.group(0) ou .group() : Retorna a string completa casada pelo padrão.
.group(n) : Retorna a string casada pelo n-ésimo grupo de captura ( (...) ).
.groups() : Retorna uma tupla com as strings casadas por todos os grupos de captura.
.start([group]) : Retorna o índice inicial do match (ou do grupo especificado).
.end([group]) : Retorna o índice final (exclusivo) do match (ou do grupo).
.span([group]) : Retorna uma tupla (start, end) .
Exemplo:
import re
Page 68 of 85
texto = "Contato: [email protected], fone (11) 98765-4321. Visite http://www.example.com. Outro email: an
Dominar Decorators, Context Managers e Expressões Regulares abre portas para escrever código Python muito mais robusto, reutilizável, eficiente e
poderoso, especialmente ao lidar com tarefas complexas de gerenciamento de recursos e manipulação de texto.
Page 69 of 85
5.6. Trabalhando com Datas e Horas ( datetime )
O módulo datetime é a principal ferramenta em Python para lidar com datas, horas, e intervalos de tempo. É fundamental para logging, agendamento,
análise de dados temporais, etc.
Principais Classes:
Criação de Objetos:
import datetime
# Data atual
hoje = datetime.date.today()
print(f"Hoje (date): {hoje}")
Manipulação:
Aritmética com timedelta : Você pode somar ou subtrair timedelta de objetos date ou datetime . Subtrair duas datas/datetimes
resulta em um timedelta .
Comparação: Objetos date e datetime podem ser comparados usando operadores padrão ( < , > , == , etc.).
Page 70 of 85
Acessando Componentes: .year , .month , .day , .hour , .minute , .second , .microsecond , .weekday() (0=Segunda,
6=Domingo), .isoformat() .
strftime (String Format Time): Converte um objeto datetime (ou date , time ) em uma string formatada.
strptime (String Parse Time): Converte uma string em um objeto datetime , baseado em um formato esperado.
agora_formatado_br = agora_naive.strftime(formato_br)
print(f"Agora formatado (BR): {agora_formatado_br}")
hoje_formatado_iso = hoje.strftime("%Y-%m-%d")
print(f"Hoje formatado (ISO): {hoje_formatado_iso}")
Naive vs Aware: Objetos datetime podem ser "naive" (ingênuos - sem informação de fuso horário) ou "aware" (cientes - com informação de fuso
horário associada via tzinfo ). É crucial usar objetos "aware" ao lidar com horas em diferentes locais ou armazenar momentos absolutos no
tempo. Sempre prefira armazenar e calcular usando UTC (aware) e converter para fusos locais apenas para exibição.
datetime.timezone.utc : Fuso horário UTC padrão.
Bibliotecas Externas ( pytz , zoneinfo ): Para lidar com fusos horários complexos (incluindo regras de horário de verão - DST), use bibliotecas
como pytz (mais antiga, muito usada) ou o módulo zoneinfo (disponível a partir do Python 3.9, usa o banco de dados IANA TZ do sistema
operacional).
Page 71 of 85
ZoneInfo = pytz.timezone # Adaptação simples para o exemplo
print("Usando pytz como fallback.")
except ImportError:
print("Nem zoneinfo nem pytz estão disponíveis. Instale pytz: pip install pytz")
ZoneInfo = None
if ZoneInfo:
# Criar um datetime aware em um fuso específico
tz_sp = ZoneInfo("America/Sao_Paulo")
agora_sp = datetime.datetime.now(tz_sp)
print(f"Agora em São Paulo (aware): {agora_sp}")
print(f"Offset e nome TZ: {agora_sp.tzname()} {agora_sp.utcoffset()}")
Serialização é o processo de converter um objeto Python em um formato que pode ser armazenado (em arquivo, banco de dados) ou transmitido (pela rede) e
posteriormente reconstruído (desserializado) de volta para um objeto Python.
pickle :
O que é: Módulo nativo do Python que serializa objetos Python em um fluxo de bytes (formato binário específico do Python).
Vantagens:
Pode serializar quase qualquer objeto Python, incluindo instâncias de classes customizadas, funções (com ressalvas), e objetos complexos
com referências circulares.
Relativamente eficiente para comunicação Python-para-Python.
Desvantagens:
INSEGURO: Desserializar (unpickle) dados de fontes não confiáveis pode executar código arbitrário malicioso. NUNCA use
pickle.load() ou pickle.loads() em dados que você não confia completamente.
Formato específico do Python, não interoperável com outras linguagens.
Pode haver problemas de compatibilidade entre diferentes versões do Python.
Não é humanamente legível.
Uso:
import pickle
dados_complexos = {
'nome': 'Exemplo Pickle',
'versao': 1.0,
'ativa': True,
'lista_numeros': [1, 2, 3, 4],
'sub_dict': {'a': None, 'b': datetime.date.today()}
}
# Desserializar de bytes
dados_reconstruidos = pickle.loads(dados_bytes)
print(f"Dados reconstruídos com Pickle: {dados_reconstruidos}")
print(f"Tipo do dado reconstruído: {type(dados_reconstruidos)}")
print(f"Data reconstruída: {dados_reconstruidos['sub_dict']['b']}")
Page 72 of 85
try:
with open('dados.pkl', 'wb') as f: # 'wb' -> write binary
pickle.dump(dados_complexos, f)
print("Dados salvos em dados.pkl")
# Desserializar de arquivo
with open('dados.pkl', 'rb') as f: # 'rb' -> read binary
dados_do_arquivo = pickle.load(f)
print(f"Dados lidos de dados.pkl: {dados_do_arquivo}")
except Exception as e:
print(f"Erro ao trabalhar com arquivo pickle: {e}")
O que é: Módulo nativo do Python para trabalhar com o formato JSON, que é um padrão de troca de dados baseado em texto, leve e humanamente
legível.
Vantagens:
Amplamente utilizado e interoperável com virtualmente todas as linguagens de programação modernas e APIs web.
Humanamente legível e fácil de depurar.
Seguro para desserializar dados de fontes externas (não executa código).
Desvantagens:
Suporta apenas um conjunto limitado de tipos de dados Python nativamente: dict , list , str , int , float , bool , None .
Não pode serializar diretamente objetos complexos como instâncias de classes customizadas, datas/horas, sets, bytes, etc., sem conversão
manual ou uso de encoders/decoders customizados.
Uso:
import json
import datetime # Para mostrar a limitação
dados_simples = {
'nome': 'Exemplo JSON',
'versao': 2.0,
'ativa': False,
'lista_numeros': [10, 20, 30],
'sub_dict': {'a': None, 'b': 'uma string'}
# 'data': datetime.date.today() # <<-- Isso causaria um TypeError com json.dumps
}
dados_com_data = dados_simples.copy()
dados_com_data['data_atual'] = datetime.date.today()
Page 73 of 85
print("Dados salvos em dados.json")
# Desserializar de arquivo
with open('dados.json', 'r', encoding='utf-8') as f: # 'r' -> read text
dados_do_arquivo_json = json.load(f)
print(f"Dados lidos de dados.json: {dados_do_arquivo_json}")
except Exception as e:
print(f"Erro ao trabalhar com arquivo JSON: {e}")
Use pickle para: Armazenamento temporário de estado Python, cache interno, comunicação entre processos Python confiáveis, quando
precisar serializar objetos complexos sem esforço extra. SEMPRE CIENTE DOS RISCOS DE SEGURANÇA.
Use json para: Comunicação com APIs web, armazenamento de configurações, troca de dados entre diferentes linguagens, quando a
legibilidade humana for importante, dados de fontes externas.
Escrever testes automatizados é fundamental para garantir a qualidade, a corretude e a manutenibilidade do software.
Testes Unitários: Focam em testar a menor unidade de código possível (geralmente uma função ou método) de forma isolada de suas dependências
(usando mocks/stubs se necessário).
unittest :
# arquivo: calculadora.py
def somar(a, b):
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError("Ambos os argumentos devem ser numéricos")
return a + b
# arquivo: test_calculadora_unittest.py
import unittest
from calculadora import somar # Supondo que calculadora.py está no mesmo diretório ou no PYTHONPATH
class TestCalculadora(unittest.TestCase):
def setUp(self):
# Código executado antes de CADA teste
print("\nExecutando setUp...")
def tearDown(self):
# Código executado depois de CADA teste
print("Executando tearDown...")
Page 74 of 85
def test_soma_positivos(self):
"""Testa a soma de dois números positivos."""
print("-> test_soma_positivos")
self.assertEqual(somar(2, 3), 5)
self.assertEqual(somar(10, 0), 10)
def test_soma_negativos(self):
"""Testa a soma envolvendo números negativos."""
print("-> test_soma_negativos")
self.assertEqual(somar(-1, -5), -6)
self.assertEqual(somar(-5, 5), 0)
def test_soma_float(self):
"""Testa a soma com números de ponto flutuante."""
print("-> test_soma_float")
self.assertAlmostEqual(somar(0.1, 0.2), 0.3, places=7) # Para floats, use assertAlmostEqual
def test_soma_tipo_invalido(self):
"""Testa se um TypeError é levantado com tipos inválidos."""
print("-> test_soma_tipo_invalido")
with self.assertRaises(TypeError):
somar('a', 5)
with self.assertRaisesRegex(TypeError, "numéricos"): # Verifica também a mensagem
somar(10, 'b')
pytest :
Framework de testes externo (precisa instalar: pip install pytest ), muito popular e poderoso.
Vantagens sobre unittest :
Menos Boilerplate: Não exige classes; testes podem ser simples funções com nomes test_* .
Asserções Naturais: Usa o assert padrão do Python, que fornece mensagens de erro muito mais detalhadas em caso de falha
(introspecção).
Fixtures: Sistema poderoso para gerenciar estado e dependências dos testes (setup/teardown mais flexível e reutilizável).
Plugins: Ecossistema rico de plugins para cobertura de código, testes web, etc.
Parametrização: Fácil de rodar o mesmo teste com múltiplos conjuntos de dados.
Estrutura:
Arquivos de teste geralmente nomeados test_*.py ou *_test.py .
Funções de teste nomeadas test_* .
Usa assert expressao diretamente.
Execução: Simplesmente execute pytest no terminal a partir do diretório raiz do projeto (ele descobre os testes automaticamente).
Exemplo (equivalente ao anterior):
# arquivo: test_calculadora_pytest.py
import pytest
from calculadora import somar # Mesma função 'somar' do exemplo anterior
def test_soma_negativos_pytest():
"""Testa a soma envolvendo números negativos."""
assert somar(-1, -5) == -6
assert somar(-5, 5) == 0
def test_soma_float_pytest():
"""Testa a soma com números de ponto flutuante."""
# pytest lida bem com comparação de floats (pode configurar precisão)
# Ou usar pytest.approx
assert somar(0.1, 0.2) == pytest.approx(0.3)
Page 75 of 85
def test_soma_tipo_invalido_pytest():
"""Testa se um TypeError é levantado com tipos inválidos."""
with pytest.raises(TypeError):
somar('a', 5)
# Verifica também a mensagem do erro
with pytest.raises(TypeError, match="numéricos"):
somar(10, 'b')
É uma filosofia de desenvolvimento onde você escreve os testes antes de escrever o código de produção.
Ciclo Red-Green-Refactor:
1. Red: Escreva um teste para uma pequena funcionalidade que ainda não existe. O teste deve falhar (ficar "vermelho").
2. Green: Escreva o código mínimo necessário para fazer o teste passar (ficar "verde"). Não se preocupe com a qualidade do código ainda.
3. Refactor: Melhore o código de produção (remova duplicação, melhore a clareza, performance) garantindo que todos os testes continuam
passando. Refatore também o código de teste se necessário.
Repita o ciclo para a próxima funcionalidade.
Benefícios: Garante cobertura de testes, foca no requisito atual, tende a gerar código mais testável e modular.
Usando print() :
Como: A forma mais simples e intuitiva. Inserir chamadas print() em pontos estratégicos do código para inspecionar o valor de variáveis ou
marcar o fluxo de execução.
Vantagens: Rápido, fácil, não requer ferramentas extras. Ótimo para problemas simples ou para ter uma ideia rápida do que está acontecendo.
Desvantagens:
Polui o código; é preciso lembrar de remover os print s depois.
Não é interativo; você não pode mudar o fluxo ou inspecionar outros estados enquanto o programa roda.
Pode ser ineficiente para bugs complexos ou que ocorrem dentro de loops longos.
Pode ser difícil em ambientes onde a saída padrão não é facilmente visível (ex: algumas aplicações web/servidor).
def funcao_com_bug(lista):
total = 0
print(f"DEBUG: Iniciando função com lista: {lista}") # Debug print
for i, item in enumerate(lista):
print(f"DEBUG: Loop {i}, item={item}, tipo={type(item)}") # Debug print
try:
# Suposto bug: esqueceu de tratar strings
if i % 2 == 0: # Só soma itens em índices pares
total += item
except TypeError:
print(f"DEBUG: TypeError ao tentar somar {item} no índice {i}") # Debug print
# Tentativa de correção (talvez?)
if isinstance(item, (int, float)):
total += item # Tenta somar de novo se for número? Lógica confusa.
O que é: Debugger embutido no Python, baseado em linha de comando. Permite pausar a execução, inspecionar variáveis, executar código linha
por linha, etc.
Como usar:
Page 76 of 85
1. Insira import pdb; pdb.set_trace() no ponto do código onde você quer iniciar a depuração (isso é um breakpoint).
2. Execute o script Python normalmente pelo terminal.
3. Quando a execução atingir pdb.set_trace() , o programa pausará e você verá o prompt (Pdb) .
Comandos Comuns do pdb :
n (next): Executa a próxima linha de código (sem entrar em funções chamadas).
s (step): Executa a próxima linha, entrando em funções chamadas.
c (continue): Continua a execução normal até o próximo breakpoint ou o fim do programa.
l (list): Mostra o código fonte ao redor da linha atual.
p <expressao> (print): Avalia e imprime o valor da expressão (ex: p minha_variavel , p len(lista) ).
pp <expressao> (pretty-print): Imprime de forma mais legível (útil para dicts/lists grandes).
w (where): Mostra a pilha de chamadas (stack trace), indicando onde você está na execução.
a (args): Mostra os argumentos da função atual.
b <linha> (breakpoint): Define um novo breakpoint em uma linha específica. b lista os breakpoints. cl <num> remove um breakpoint.
q (quit): Encerra a sessão de depuração e o script.
h (help): Mostra ajuda. h <comando> mostra ajuda para um comando específico.
Vantagens: Disponível em qualquer ambiente Python, poderoso, não requer IDE, bom para depuração remota ou em servidores.
Desvantagens: Interface de linha de comando pode ser menos intuitiva que debuggers gráficos, curva de aprendizado um pouco maior.
def funcao_com_pdb(lista):
total = 0
import pdb; pdb.set_trace() # <<-- Ponto de parada aqui
for i, item in enumerate(lista):
# ... resto da lógica com possível bug ...
# Você pode usar 'n' para passar por cada linha
# e 'p total' ou 'p item' para ver os valores
if i % 2 == 0:
total += item # Bug potencial aqui se item não for número
return total
O que são: Ferramentas de depuração gráfica integradas aos Ambientes de Desenvolvimento Integrado (IDEs).
Como usar:
Breakpoints Visuais: Clique na margem esquerda do editor de código para definir/remover breakpoints (pontos vermelhos).
Iniciar Depuração: Use uma opção de menu ou botão específico na IDE (geralmente "Run > Debug", um ícone de inseto, ou F5).
Controles Visuais: Botões para Step Over (equivalente a n ), Step Into (equivalente a s ), Step Out (continuar até sair da função
atual), Resume Program (equivalente a c ).
Inspeção de Variáveis: Painéis dedicados mostram automaticamente as variáveis locais e globais e seus valores atuais. Você pode
"observar" (watch) expressões específicas.
Pilha de Chamadas: Painel mostrando a sequência de chamadas de função que levaram ao ponto atual.
Console Interativo: Muitas vezes, um console permite digitar e executar comandos Python no contexto atual da execução pausada.
Vantagens: Interface gráfica muito mais intuitiva, visualização clara do estado do programa, fácil de navegar, recursos avançados como
breakpoints condicionais. Geralmente a forma mais produtiva de depurar.
Desvantagens: Requer o uso de uma IDE.
Dominar essas ferramentas ( datetime , serialização, testes e debugging) é essencial para ir além do básico em Python e construir aplicações mais
complexas, robustas e confiáveis.
Tipagem Estática Opcional (Type Hinting): Anotações de tipo para melhorar a legibilidade e verificação de código ( mypy ).
Concorrência e Paralelismo:
threading : Threads (para operações I/O bound).
multiprocessing : Processos (para operações CPU bound).
asyncio : Programação assíncrona (para muitas operações I/O simultâneas). Entender async / await .
Okay, vamos detalhar esses tópicos mais avançados: Tipagem Estática Opcional, Concorrência e Paralelismo em Python.
Page 77 of 85
O que é?
Python é tradicionalmente uma linguagem de tipagem dinâmica, o que significa que o tipo de uma variável é determinado em tempo de execução. Type
Hinting (introduzido principalmente na PEP 484) permite adicionar anotações de tipo opcionais ao código Python. Essas anotações sugerem os tipos
esperados para variáveis, parâmetros de função e valores de retorno.
É opcional porque o interpretador Python, por padrão, ignora essas anotações em tempo de execução. Elas não afetam o comportamento do programa
diretamente.
1. Melhora a Legibilidade e Compreensão: As anotações de tipo funcionam como documentação, tornando mais claro o que uma função espera
receber e o que ela retorna.
2. Detecção Antecipada de Erros: Ferramentas de análise estática, como o mypy , podem usar essas anotações para verificar a consistência dos
tipos no código antes da execução, pegando muitos erros comuns ( TypeError , chamar métodos inexistentes, etc.) durante o desenvolvimento.
3. Melhora o Suporte de Ferramentas (IDEs): IDEs (como PyCharm, VS Code com Pylance/Pyright) usam type hints para fornecer melhor
autocompletar, refatoração mais segura e análise de código em tempo real.
4. Manutenibilidade: Facilita a manutenção e a colaboração em bases de código maiores, pois a intenção do código fica mais explícita.
Exemplo:
# arquivo: exemplo_tipagem.py
from typing import List, Optional, Dict, Union
if not precos:
# Mypy pode avisar se não tratarmos o caso de lista vazia dependendo da config
return 0.0
Page 78 of 85
raise ValueError("Desconto deve estar entre 0 e 1")
preco_final: float = soma_precos * (1 - desconto)
return preco_final
else:
# Mypy sabe que 'desconto' é None aqui
return soma_precos
# Uso correto
precos_produtos: List[float] = [10.50, 25.00, 5.75]
total_sem_desconto: float = calcular_preco_total(precos_produtos)
total_com_desconto: float = calcular_preco_total(precos_produtos, desconto=0.1) # 10% de desconto
# Para verificar:
# 1. Salve o código como exemplo_tipagem.py
# 2. Instale mypy: pip install mypy
# 3. Rode no terminal: mypy exemplo_tipagem.py
# (Mypy deve passar sem erros se as linhas incorretas estiverem comentadas)
# (Descomente as linhas de erro para ver mypy reportá-los)
Estes conceitos permitem que um programa execute múltiplas tarefas "ao mesmo tempo", mas de maneiras diferentes, adequadas para problemas distintos.
Concorrência: Lidar com múltiplas tarefas, fazendo progresso em mais de uma delas em períodos de tempo sobrepostos. Não significa
necessariamente execução simultânea. Pense em um cozinheiro fazendo várias coisas na cozinha: corta legumes, depois mexe a panela, depois
verifica o forno - ele está gerenciando várias tarefas concorrentemente, mas só faz uma coisa por vez.
Paralelismo: Executar múltiplas tarefas literalmente ao mesmo tempo, geralmente usando múltiplos núcleos de CPU ou múltiplos processadores.
Pense em vários cozinheiros trabalhando na mesma cozinha, cada um em uma tarefa diferente, simultaneamente. Paralelismo é uma forma de
alcançar concorrência.
I/O Bound: A tarefa passa a maior parte do tempo esperando por operações de Entrada/Saída (Input/Output) - como esperar por dados da rede,
ler/escrever em disco, esperar por entrada do usuário. O processador fica ocioso durante essas esperas.
CPU Bound: A tarefa passa a maior parte do tempo realizando cálculos intensivos, usando ativamente a CPU. Não há muitas esperas por I/O.
O GIL é um mutex (mecanismo de bloqueio) no interpretador CPython (o mais comum) que protege o acesso à memória do Python, garantindo que
apenas uma thread execute bytecode Python por vez dentro de um único processo.
Impacto:
Para tarefas CPU-bound, usar múltiplas threads ( threading ) em CPython não resultará em paralelismo real e pode até deixar o programa
mais lento devido à sobrecarga de gerenciamento das threads e disputa pelo GIL.
Para tarefas I/O-bound, threading é eficaz porque a thread que está esperando por I/O (ex: download) libera o GIL, permitindo que outra
thread execute código Python enquanto a primeira espera.
Módulos Principais:
O que faz: Cria múltiplas threads dentro do mesmo processo. Threads compartilham o mesmo espaço de memória.
Ideal para: Tarefas I/O-bound (downloads de rede, queries a bancos de dados, leitura de múltiplos arquivos). Permite que o programa faça
outras coisas enquanto espera por I/O.
Como usar:
Cria-se um objeto threading.Thread , passando a função alvo ( target ) e seus argumentos ( args , kwargs ).
Page 79 of 85
Chama-se thread.start() para iniciar a thread.
Chama-se thread.join() para esperar que a thread termine (opcional, mas comum).
Cuidados: Como a memória é compartilhada, é preciso ter cuidado com race conditions (condições de corrida) ao acessar dados
compartilhados. Use mecanismos de sincronização como threading.Lock , Semaphore , Event , etc., para proteger o acesso a
recursos compartilhados.
Exemplo (Simulando Downloads):
import threading
import time
import random
start_time = time.perf_counter()
end_time = time.perf_counter()
print(f"\nTodos os downloads simulados concluídos em {end_time - start_time:.2f} segundos.")
# Note que o tempo total será próximo ao da thread mais longa, não a soma,
# mostrando a concorrência I/O.
O que faz: Cria múltiplos processos independentes, cada um com seu próprio interpretador Python e espaço de memória.
Ideal para: Tarefas CPU-bound (cálculos matemáticos pesados, processamento de imagens/vídeos, simulações) porque contorna o GIL,
permitindo paralelismo real em máquinas multi-core.
Como usar:
API similar a threading : cria-se multiprocessing.Process , start() , join() .
Como a memória não é compartilhada diretamente, a comunicação entre processos (IPC - Inter-Process Communication) requer
mecanismos explícitos como multiprocessing.Queue , Pipe , Value , Array .
Cuidados: Criar processos tem maior sobrecarga (memória e tempo) do que criar threads. A comunicação IPC adiciona complexidade. A
serialização de dados entre processos (geralmente pickle ) pode ser um gargalo.
Exemplo (Simulando Cálculo CPU Bound):
import multiprocessing
import time
import os
Page 80 of 85
time.sleep(0.1) # Pequena pausa para simular, mas idealmente seria cálculo
resultados_queue.put((os.getpid(), numero, resultado)) # Envia resultado via Queue
# print(f"[Processo {os.getpid()}] Calculou {numero}^2 = {resultado}") # Print pode ser confuso
start_time = time.perf_counter()
end_time = time.perf_counter()
print(f"\nTodos os cálculos concluídos em {end_time - start_time:.2f} segundos.")
# Em uma máquina multi-core, o tempo total deve ser significativamente menor
# que a soma dos tempos individuais, mostrando paralelismo CPU.
O que faz: Permite concorrência em uma única thread usando um event loop e coroutines (funções async def ). Baseado em multitarefa
cooperativa: as tarefas explicitamente cedem controle ( await ) quando estão esperando por I/O.
Ideal para: Aplicações com muitas (centenas, milhares) operações I/O-bound simultâneas, onde a sobrecarga de criar milhares de threads
seria proibitiva (ex: servidores web, crawlers, chatbots).
Como usar:
async def nome_funcao(...) : Define uma coroutine. Chamar essa função retorna um objeto coroutine, não a executa
imediatamente.
await expressao : Usado dentro de uma async def para chamar outra coroutine ou aguardar uma operação I/O assíncrona.
Pausa a coroutine atual, permitindo que o event loop execute outras tarefas.
Event Loop: O núcleo do asyncio . Gerencia e executa as coroutines e callbacks de I/O.
asyncio.run(coro) : Ponto de entrada principal para executar a coroutine de nível superior.
asyncio.gather(*coros) : Executa múltiplas coroutines concorrentemente e retorna uma lista de resultados quando todas
terminarem.
asyncio.sleep(secs) : Versão assíncrona de time.sleep() , não bloqueia o event loop.
Cuidados: Requer um paradigma de programação diferente (pensar em termos de não-bloqueio). Bibliotecas de I/O precisam ser
compatíveis com asyncio (ex: usar aiohttp em vez de requests , asyncpg em vez de psycopg2 ). Não acelera código CPU-
bound, pois roda em uma única thread.
Exemplo (Simulando Tarefas Async I/O):
import asyncio
import time
import random
Page 81 of 85
print(f"[{nome}] Concluída.")
return resultado
end_time = time.perf_counter()
print("\n--- Todas as tarefas concluídas ---")
print(f"Resultados: {resultados}")
print(f"Tempo total: {end_time - start_time:.2f} segundos.")
# O tempo total será próximo ao da tarefa mais longa.
Qual escolher?
Dominar Type Hinting melhora drasticamente a qualidade e manutenibilidade do código Python. Entender as diferenças e os casos de uso de threading ,
multiprocessing e asyncio é crucial para escrever aplicações performáticas e escaláveis que lidam eficientemente com operações I/O e de CPU.
6. Ecossistema e Ferramentas:
Linters e Formatadores: Flake8 , Pylint , Black , isort (para garantir a qualidade e o estilo do código - PEP 8).
Okay, vamos detalhar o papel e o uso de Linters e Formatadores no ecossistema Python.
Manter a qualidade, a consistência e a legibilidade do código é crucial, especialmente em projetos colaborativos ou de longa duração. Linters e Formatadores
são ferramentas essenciais que automatizam esse processo, ajudando a garantir que o código siga padrões estabelecidos, como o PEP 8, e esteja livre de
erros comuns.
PEP 8: É o guia de estilo oficial para código Python. Ele define convenções sobre como escrever código Python legível, incluindo regras sobre indentação
(4 espaços), comprimento máximo de linha (tradicionalmente 79 caracteres, mas frequentemente estendido para 88 ou mais em projetos modernos),
como nomear variáveis e funções (snake_case), espaçamento em torno de operadores, organização de imports, e muito mais. O objetivo principal do
PEP 8 é melhorar a legibilidade e a consistência do código Python.
Linter: Analisa o código fonte para encontrar erros programáticos, bugs potenciais, desvios de estilo e construções suspeitas. Ele reporta
problemas, mas geralmente não modifica o código automaticamente (embora alguns possam oferecer correções). Exemplos: Flake8 , Pylint .
Formatter: Reescreve automaticamente o código fonte para garantir que ele siga regras estritas de estilo. Ele se preocupa principalmente com a
aparência do código (indentação, espaçamento, quebras de linha, etc.), não necessariamente com a lógica ou erros potenciais (além dos que
impedem a formatação). Exemplos: Black , isort (focado em imports).
Linters
Page 82 of 85
Eles ajudam a pegar erros antes mesmo de executar o código e a manter um estilo consistente.
1. Flake8
O que é: Uma ferramenta popular e rápida que combina três outras ferramentas:
PyFlakes : Verifica erros lógicos (variáveis não usadas, nomes não definidos, etc.) sem se preocupar muito com estilo.
pycodestyle (anteriormente pep8 ): Verifica se o código está em conformidade com as convenções de estilo do PEP 8.
McCabe: Verifica a complexidade ciclomática do código (ajuda a identificar funções muito complexas que podem precisar ser refatoradas).
Vantagens: Rápido, fácil de configurar, bom equilíbrio entre verificação de erros e estilo, extensível com plugins (ex: flake8-bugbear para
mais verificações de bugs, flake8-docstrings para verificar docstrings).
Instalação: pip install flake8
Uso Básico: flake8 meu_arquivo.py ou flake8 meu_projeto/
Configuração: Geralmente feita em arquivos como setup.cfg , tox.ini ou .flake8 . Você pode especificar quais erros ignorar, definir o
comprimento máximo de linha, etc.
Exemplo de Saída:
meu_arquivo.py:10:1: F841 local variable 'x' is assigned to but never used
meu_arquivo.py:25:80: E501 line too long (85 > 79 characters)
2. Pylint
O que é: Um linter muito mais rigoroso e configurável que o Flake8 . Ele realiza uma análise mais profunda, verificando:
Erros de programação.
Conformidade com padrões de codificação (além do PEP 8, pode ser configurado para padrões mais específicos).
"Code smells" (padrões de código que indicam possíveis problemas de design ou manutenção).
Oferece sugestões de refatoração.
Calcula uma pontuação de qualidade do código.
Vantagens: Análise muito completa, altamente configurável, pode detectar problemas mais sutis.
Desvantagens: Pode ser mais lento que o Flake8 . Pode ser "barulhento" (reportar muitas coisas) se não for configurado corretamente para as
necessidades do projeto. Requer um arquivo de configuração ( .pylintrc ) para um uso mais eficaz.
Instalação: pip install pylint
Uso Básico: pylint meu_arquivo.py ou pylint meu_projeto/
Configuração: Use pylint --generate-rcfile > .pylintrc para criar um arquivo de configuração inicial e depois edite-o para ajustar
as regras, desabilitar mensagens específicas, etc.
Exemplo de Saída:
************* Module meu_arquivo
meu_arquivo.py:10:0: C0103 Invalid constant name "minha_var" (should be MINHA_VAR) (invalid-name)
meu_arquivo.py:15:4: W0612 Unused variable 'y' (unused-variable)
meu_arquivo.py:20:0: R1710 Either all return statements in a function should return an expression, or none o
------------------------------------------------------------------
Your code has been rated at 7.50/10 (previous run: 7.50/10, +0.00)
Formatadores
Eles garantem que todo o código no projeto tenha uma aparência uniforme, eliminando debates sobre estilo.
1. Black
O que é: "The uncompromising Python code formatter". É um formatador altamente opinionado com pouquíssimas opções de configuração. Seu
objetivo é simples: formatar o código Python de forma consistente, acabando com as discussões sobre estilo.
Filosofia: A consistência é mais importante que a flexibilidade. Ele aplica um subconjunto do PEP 8 com algumas modificações próprias (ex: usa
aspas duplas para strings, gerencia quebras de linha de forma específica). O comprimento padrão da linha é 88.
Page 83 of 85
Vantagens: Extremamente fácil de usar (pouca ou nenhuma configuração), garante um estilo 100% consistente em todo o projeto, amplamente
adotado pela comunidade.
Instalação: pip install black
Uso Básico:
Para verificar se os arquivos precisam de formatação (sem modificar): black --check . (o . indica o diretório atual)
Para formatar os arquivos diretamente: black . ou black meu_arquivo.py
Configuração: Geralmente feita no arquivo pyproject.toml . As opções são limitadas, sendo a mais comum o comprimento da linha.
# Exemplo em pyproject.toml
[tool.black]
line-length = 88
# target-version = ['py38', 'py39'] # Opcional: especificar versões Python alvo
# include = '\.pyi?$'
# exclude = '''
# /(
# \.git
# | \.hg
# | \.mypy_cache
# # ... outros padrões de exclusão ...
# )/
# '''
2. isort
O que é: Uma ferramenta focada especificamente em ordenar e formatar as declarações import no topo dos arquivos Python.
Funcionalidade:
Agrupa os imports em seções (ex: biblioteca padrão, pacotes de terceiros, código local).
Ordena os imports alfabeticamente dentro de cada seção.
Pode formatar imports longos em múltiplas linhas.
Vantagens: Mantém os blocos de import limpos e organizados, facilita a visualização das dependências do módulo, ajuda a evitar conflitos de
merge relacionados a imports.
Instalação: pip install isort
Uso Básico:
Para verificar: isort --check .
Para formatar: isort . ou isort meu_arquivo.py
Configuração: Também pode ser configurado via pyproject.toml , setup.cfg , .isort.cfg , etc. Uma configuração importante é o
profile , especialmente se você usa Black .
A melhor maneira de usar essas ferramentas é integrá-las ao seu fluxo de trabalho de desenvolvimento:
1. IDE Integration: Configure seu editor/IDE (VS Code, PyCharm, etc.) para rodar o formatador (ex: Black ) automaticamente ao salvar o arquivo e para
mostrar os avisos do linter (ex: Flake8 ) enquanto você digita.
2. Pre-commit Hooks: Use a ferramenta pre-commit ( pip install pre-commit ) para configurar "ganchos" que rodam automaticamente essas
ferramentas em arquivos modificados antes de você fazer um commit. Isso garante que apenas código formatado e (pelo menos parcialmente) lintado
seja enviado para o controle de versão.
# Exemplo de .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
Page 84 of 85
rev: v4.5.0 # Use a versão estável mais recente
hooks:
- id: trailing-whitespace # Remove espaços em branco no final da linha
- id: end-of-file-fixer # Garante uma linha em branco no final do arquivo
- id: check-yaml # Verifica sintaxe de arquivos YAML
- id: check-added-large-files # Previne commit de arquivos grandes
- repo: https://github.com/psf/black
rev: 24.4.2 # Use a versão estável mais recente
hooks:
- id: black
args: [--line-length=88] # Argumentos opcionais
- repo: https://github.com/pycqa/isort
rev: 5.13.2 # Use a versão estável mais recente
hooks:
- id: isort
name: isort (python)
args: ["--profile", "black", "--filter-files"]
- repo: https://github.com/pycqa/flake8
rev: 7.0.0 # Use a versão estável mais recente
hooks:
- id: flake8
args: ['--max-line-length=88', '--ignore=E203,W503'] # Argumentos opcionais
# additional_dependencies: [flake8-bugbear] # Exemplo de plugin
3. Continuous Integration (CI): Configure seu pipeline de CI (GitHub Actions, GitLab CI, Jenkins, etc.) para rodar os linters e formatadores no modo --
check . Isso garante que código que não segue os padrões seja detectado e reprove o build, impedindo que ele seja mergeado na branch principal.
Em resumo, usar linters como Flake8 ou Pylint ajuda a encontrar erros e manter a qualidade lógica e estilística, enquanto formatadores como Black e
isort impõem automaticamente um estilo de código consistente. Integrar essas ferramentas no fluxo de trabalho (IDE, pre-commit, CI) é uma prática
recomendada para qualquer projeto Python sério, economizando tempo, prevenindo bugs e melhorando a colaboração.
Page 85 of 85