Guia Definitivo de Programação Python
Jornada completa pela programação Python baseada na experiência pessoal que tive na UFRJ em 2021
Python para Marinheiros de Primeira Viagem
Documentação completa e definitiva baseada na experiência prática de laboratório e redação na UFRJ. Desde a instalação até conceitos de estruturas de dados, preservando o estilo didático e explicações claras que fizeram desta jornada uma experiência única de aprendizado.
Sumário
- Introdução
- Preparação do Ambiente
- Parte I: Fundamentos
- Parte II: Estruturas de Dados
- Parte III: Programação Intermediária
- Parte IV: Lógica e Problemas Clássicos
- Parte V: Projetos Práticos
- Parte VI: PyckageTools - Redes e Segurança
- Parte VII: Programação Avançada
- Parte VIII: Dicas e Armadilhas Comuns
- Conclusão
Introdução
Este guia apresenta uma jornada completa pela programação Python, desde conceitos fundamentais até estruturas de dados e projetos práticos como jogos e sistemas completos. O material foi desenvolvido através de 10 laboratórios práticos, um trabalho final e projetos extras que abordam progressivamente todos os aspectos essenciais da linguagem.
A abordagem aqui é diferente dos manuais tradicionais - usamos analogias do dia a dia, explicações que fazem sentido e exemplos práticos que tornam conceitos abstratos mais concretos. Como sempre dizíamos nas aulas: “É legal deixar claro” cada detalhe, e é exatamente isso que faremos.
Lembro que no começo eu anotava coisas como “todos os comandos são como funções em Python, e todas as funções precisam de parênteses” - parece bobo agora, mas essas primeiras impressões são importantes. A diferença entre print(7+4) dando 11 e print('7'+'4') dando '74' foi um dos primeiros “cliques” que tive.
Python é conhecida por sua sintaxe limpa e intuitiva - é quase como escrever em português com algumas palavras-chave em inglês. Diferentemente de C, que exige muita cerimônia (includes, tipos explícitos, compilação), Python é interpretada e permite que você se concentre na lógica em vez de detalhes técnicos. Como anotei na época: “print = escreva; input = leia” - simples assim.
Queria aproveitar a introdução desta documentação para contextualizar: este material foi desenvolvido durante as aulas de COMP1 na UFRJ entre 2020 e 2021, com os professores José Sapienza Ramos e Rodrigo Guerchon (se tiver mais algum não lembro, foi mal). Foram eles que estruturaram os laboratórios e trabalhos que compõem este guia. Juntei todos os scripts dos labs, trabalhos e projetos pessoais que fiz durante o curso e tentei compactar neste documento. A ideia é que sirva tanto como guia de aprendizado quanto como referência para consultas futuras.
O código do meu trabalho final (PyckageTools) está disponível no GitHub: PyckageTools-UFRJ.
Este guia serve tanto para iniciantes quanto para quem quer relembrar conceitos, funcionando como uma referência completa da linguagem Python com foco prático em problemas reais.
Para Marinheiros de Primeira Viagem
Se você está vendo Python pela primeira vez, aqui estão alguns conceitos fundamentais que tornarão sua jornada muito mais tranquila:
O Que É Python?
Python é uma linguagem de alto nível - isto é, bem distante do hardware. Enquanto C te mostra como as coisas funcionam “por baixo dos panos”, Python esconde esses detalhes para você se concentrar em resolver problemas. É como a diferença entre dirigir um carro e entender como funcionam todos os componentes do motor.
Por Que Aprender Python?
- Sintaxe limpa: Código Python é legível e parece quase português
- Comunidade enorme: Bibliotecas para praticamente tudo (análise de dados, web, IA, etc.)
- Versátil: Pode fazer desde scripts simples até aplicações complexas
- Sem compilação: Escreve e executa imediatamente
- Indentação obrigatória: Força código bem organizado
Conceitos Essenciais Antes de Começar
Comentários são anotações que o Python ignora - servem para você (ou outros) entenderem o código:
1
2
3
# Isso é um comentário - Python ignora essa linha
# Tudo depois do # até o final da linha é comentário
print("Olá") # Comentário no final de uma linha também funciona
Variáveis são como caixas rotuladas onde guardamos informações:
1
2
idade = 25 # Caixa chamada "idade" guardando o número 25
nome = "Matheus" # Caixa chamada "nome" guardando o texto "Matheus"
Importante: O = em programação significa “recebe” ou “guarda”, não “igual”. Lemos idade = 25 como “idade recebe 25”.
Funções são como receitas de cozinha - você escreve uma vez e pode usar várias vezes:
1
2
3
4
5
6
7
# Definindo a "receita" (função)
def saudar(nome): # "def" = definir. "nome" é o ingrediente (parâmetro)
print(f"Olá, {nome}!") # O que a receita faz
# Usando a receita (chamando a função)
saudar("Maria") # Resultado: "Olá, Maria!"
saudar("João") # Resultado: "Olá, João!"
Listas são como caixas com vários compartimentos numerados:
1
2
3
4
5
# [0] [1] [2] <- posições (começam do 0!)
frutas = ["maçã", "banana", "laranja"]
print(frutas[0]) # "maçã" (primeira posição)
print(frutas[1]) # "banana" (segunda posição)
Importante: Em programação, contamos a partir do ZERO, não do um!
Anatomia de um Programa Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1. Função auxiliar
def calcular_area(base, altura):
return (base * altura) / 2
# 2. Função principal
def main():
# Variáveis
b = 10
h = 5
# Chamando função
area = calcular_area(b, h)
# Exibindo resultado
print(f"A área é: {area}")
# 3. Executar o programa
if __name__ == "__main__":
main()
Linha por linha:
def- Define uma função. Tudo indentado abaixo pertence a ela.return- Devolve um valor da função. Semreturn, a função retornaNone(nada).
1
2
3
4
5
6
7
8
9
10
11
12
# Com return - devolve um valor
def somar(a, b):
return a + b
resultado = somar(2, 3) # resultado = 5
# Sem return - não devolve nada útil
def mostrar_mensagem(texto):
print(texto)
# sem return
resultado = mostrar_mensagem("Olá") # resultado = None
O que é None? É o “nada” do Python. Representa ausência de valor. É diferente de 0, "" ou False - é literalmente “nada aqui”.
print()- Escreve algo na tela. Em Python,print()é mais fácil que oprintf()de C.f"texto {variavel}"- F-strings permitem inserir variáveis dentro de strings facilmente. Chamamos de “template”. É a forma mais moderna (Python 3.6+):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
nome = "Maria"
idade = 25
# F-string (recomendado)
print(f"Olá, {nome}! Você tem {idade} anos.")
# Formatação de números
preco = 49.9
print(f"Preço: R$ {preco:.2f}") # R$ 49.90 (2 casas decimais)
# Alinhamento
print(f"{nome:>10}") # " Maria" (10 espaços, alinhado à direita)
print(f"{nome:<10}") # "Maria " (10 espaços, alinhado à esquerda)
print(f"{nome:^10}") # " Maria " (10 espaços, centralizado)
if __name__ == "__main__":- Truque para executar código apenas quando o arquivo é executado diretamente (não importado). Explicação mais detalhada na Parte VIII.
Python vs C: As Grandes Diferenças
| Aspecto | C | Python |
|---|---|---|
| Tipagem | Explícita (int, char, etc.) | Dinâmica (deduz sozinha) |
| Compilação | Precisa compilar | Interpretado (executa direto) |
| Sintaxe | Formal e rígida | Flexível e limpa |
| Memória | Manual (malloc/free) | Automática (garbage collection) |
| Estrutura | Orientado a funções | Multi-paradigma |
| Velocidade | Rápido | Mais lento, mas suficiente |
Mentalidade Certa
- Indentação é obrigatória - Python usa espaços para estrutura, não chaves
{}. Indentação são os espaços no início da linha que mostram o que está “dentro” de quê:
1
2
3
4
# Os espaços antes do print mostram que ele está DENTRO do if
if idade >= 18:
print("Maior de idade") # 4 espaços = está dentro do if
print("Fim") # Sem espaços = fora do if, sempre executa
Isso parece estranho no início, mas força código bem organizado.
Dinâmico não significa bagunçado - Python é fortemente tipada, só não exige que você declare o tipo explicitamente. Ela deduz sozinha.
Erros são professores - Mensagens de erro em Python são bem descritivas. Leia com calma, elas geralmente dizem exatamente o que está errado.
Pense em algoritmo primeiro - A lógica é mais importante que a sintaxe. Se você sabe o que quer fazer, Python geralmente tem uma forma simples de fazer.
Antes de Prosseguir
Este guia foi criado a partir de aulas reais da UFRJ, mantendo o estilo didático que ajudou muitos estudantes. As explicações usam analogias do dia a dia porque conceitos abstratos ficam mais fáceis quando comparamos com coisas familiares.
Não tenha pressa. Cada seção constrói sobre a anterior. Se algo não fizer sentido, volte e releia - é completamente normal precisar de várias leituras para absorver conceitos novos.
Dica importante: Não apenas leia os códigos - digite e execute! Programação se aprende fazendo, não apenas lendo.
Agora vamos começar nossa jornada! 🐍
Guia Rápido de Símbolos
Antes de mergulhar no código, aqui estão os símbolos que você vai ver muito:
| Símbolo | Nome | O que faz |
|---|---|---|
= | Atribuição | Guarda valor numa variável |
== | Comparação | Verifica se são iguais |
# | Comentário | Python ignora tudo depois |
: | Dois pontos | Indica início de bloco |
() | Parênteses | Chamada de função ou agrupamento |
[] | Colchetes | Listas ou acesso por índice |
{} | Chaves | Dicionários |
"" ou '' | Aspas | Definem texto (string) |
Preparação do Ambiente
Instalação do Python
Windows
Baixe de python.org.
IMPORTANTE: Marque “Add Python to PATH” durante instalação.
1
2
3
4
5
# Verificar instalação
python --version
# Ou pode ser:
python3 --version
Linux/Ubuntu
1
2
sudo apt update
sudo apt install python3 python3-pip
macOS
1
brew install python3
Seu Primeiro Programa
Vamos criar seu primeiro programa! Abra um editor de texto (VSCode, Notepad++, ou até o Bloco de Notas) e salve um arquivo chamado primeiro.py:
1
2
3
4
5
6
7
8
# Meu primeiro programa em Python!
print("Python está funcionando!")
# Criando uma variável
idade = 25
# Usando f-string para mostrar a variável
print(f"Eu tenho {idade} anos")
Agora abra o terminal (cmd no Windows, terminal no Linux/Mac), navegue até a pasta do arquivo e execute:
1
python primeiro.py
Se aparecer:
1
2
Python está funcionando!
Eu tenho 25 anos
Parabéns! Você acabou de rodar seu primeiro programa! 🎉
Entrada e Saída Básica
Nas minhas primeiras anotações, resumi assim: “print = escreva; input = leia”. Simples e direto! Com o input mostramos uma mensagem na tela e deixa o espaço para o usuário digitar.
print() - Mostrar informações na tela:
1
2
3
print("Olá, mundo!") # Texto simples
print(10 + 5) # Resultado de cálculo: 15
print("Resultado:", 10 + 5) # Múltiplos valores: "Resultado: 15"
F-strings - Inserindo Variáveis em Texto:
O f antes das aspas permite colocar variáveis dentro do texto usando {}:
1
2
3
4
nome = "Maria"
idade = 25
print(f"Olá, {nome}! Você tem {idade} anos.")
# Resultado: "Olá, Maria! Você tem 25 anos."
Sem f-string, seria mais trabalhoso:
1
print("Olá, " + nome + "! Você tem " + str(idade) + " anos.") # Chato!
input() - Receber dados do usuário:
1
2
3
4
5
6
7
8
9
nome = input("Digite seu nome: ") # Exibe mensagem e espera digitação
print(f"Olá, {nome}!")
# IMPORTANTE: input() SEMPRE retorna string!
idade_texto = input("Sua idade: ") # Se digitar 25, vem "25" (texto)
idade_numero = int(idade_texto) # Converter para número
# Forma resumida:
idade = int(input("Sua idade: ")) # Pede, converte e guarda
Conversão de tipos:
| Função | O que faz | Exemplo |
|---|---|---|
int(x) | Converte para inteiro | int("25") → 25 |
float(x) | Converte para decimal | float("3.14") → 3.14 |
str(x) | Converte para texto | str(25) → "25" |
Ambiente Recomendado
- Editor: VSCode, PyCharm Community ou Thonny (especial para iniciantes)
- Terminal: Use
cmd(Windows),bash(Linux) ouzsh(macOS) - Gerenciador de pacotes:
pip(já vem com Python)
Instalando Bibliotecas Externas
Python vem com muitas funcionalidades, mas às vezes precisamos de bibliotecas extras. O pip é o gerenciador de pacotes do Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Instalar um pacote
pip install nome_do_pacote
# Exemplos comuns:
pip install requests # Para fazer requisições HTTP
pip install numpy # Para cálculos matemáticos
pip install pandas # Para análise de dados
# Ver pacotes instalados
pip list
# Desinstalar
pip uninstall nome_do_pacote
Dica Windows: Se pip não funcionar, tente py -m pip install nome_do_pacote.
Problemas Comuns na Instalação
Se você está tendo problemas, aqui estão as soluções mais comuns:
| Problema | Solução |
|---|---|
python não é reconhecido | Reinstale e marque “Add to PATH” |
| Versão errada | Use python3 em vez de python |
| Erro de permissão no pip | Use pip install --user pacote |
| Acentos não funcionam | Salve o arquivo como UTF-8 |
Dica de ouro: Se algo não funcionar, copie a mensagem de erro e pesquise no Google. 99% das vezes alguém já teve o mesmo problema!
Parte I: Fundamentos
Lab 1: Funções e Cálculos Básicos
O Contexto Prático
No primeiro laboratório, aprendemos a pensar em termos de funções - receitas reutilizáveis. A ideia é que, em vez de fazer o mesmo cálculo várias vezes manualmente, criamos uma função que faz por nós.
Nas minhas anotações da época, escrevi: “Aqui apenas criamos a função, mas não executamos ela. Para executar é só escrever no shell f(6), por exemplo.” Parece bobo agora, mas essa distinção entre definir e chamar uma função é fundamental!
Geometria: Calculando Áreas e Volumes
Uma das primeiras tarefas é criar funções que calculem propriedades geométricas:
1
2
3
4
5
6
7
8
9
10
11
def area_retangulo(base, altura):
"""Calcula a área de um retângulo dado base e altura"""
return base * altura
def area_circulo_coroa(raio_externo, raio_interno):
"""Calcula a área de um anel (coroa circular)"""
return 3.14 * (raio_externo**2 - raio_interno**2)
def area_cubo_superficie(aresta):
"""Calcula a superfície de um cubo (6 faces quadradas)"""
return aresta**2 * 6
Conceitos importantes:
**é o operador de exponenciação (potência)- Docstrings (strings entre
""") documentam o que a função faz - Sem
returnexplícito, a função retornaNone
Álgebra: Operações Matemáticas
Desafio: Antes de ver o código, tente criar uma função que calcule a média entre dois números. Lembre-se: média = soma dos valores dividido pela quantidade.
💡 Ver solução
```python def media(x, y): """Calcula a média entre dois números""" return (x + y) / 2 # Testando: print(media(10, 20)) # 15.0 print(media(7, 8)) # 7.5 ```Agora funções mais complexas:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def media_ponderada(valor1, peso1, valor2, peso2):
"""
Calcula média ponderada.
Cada valor é multiplicado pelo seu peso.
"""
return (valor1*peso1 + valor2*peso2) / (peso1 + peso2)
def valor_polinomio(a, b, c, x):
"""Calcula y = ax² + bx + c para um dado x"""
return a*x**2 + b*x + c
# Testando:
print(media_ponderada(8, 2, 6, 3)) # 6.8 (prova com peso 2, trabalho com peso 3)
print(valor_polinomio(1, 2, 1, 3)) # 16 (1*9 + 2*3 + 1 = 16)
Aplicações Práticas do Dia a Dia
1
2
3
4
5
6
7
8
9
10
11
12
def calcular_gorjeta(valor_conta, percentual=15):
"""Calcula gorjeta. Default é 15% se não informado"""
return valor_conta * (percentual / 100)
def calcular_saldo_juros(saldo_inicial, taxa_mensal, meses):
"""Calcula saldo com juros simples"""
return saldo_inicial * (1 + taxa_mensal/100 * meses)
def barco_atravessando_rio(largura_rio, velocidade_barco, velocidade_corrente):
"""Calcula quanto a corrente arrasta o barco"""
tempo = largura_rio / velocidade_barco
return tempo * velocidade_corrente
Por que funções? Sem elas, você teria que reescrever o cálculo toda vez. Com funções, escreve uma vez, usa infinitas vezes. É como a diferença entre decorar uma receita de bolo vs. ler a receita cada vez que faz.
Operadores Aritméticos em Python
Antes de avançar, vamos garantir que conhecemos todos os operadores:
| Operador | Operação | Exemplo | Resultado |
|---|---|---|---|
+ | Adição | 5 + 3 | 8 |
- | Subtração | 5 - 3 | 2 |
* | Multiplicação | 5 * 3 | 15 |
/ | Divisão | 7 / 2 | 3.5 |
// | Divisão inteira | 7 // 2 | 3 |
% | Módulo (resto) | 7 % 2 | 1 |
** | Potência | 2 ** 3 | 8 |
Divisão / vs //:
/sempre retorna decimal:10 / 3=3.333...//arredonda pra baixo:10 / 3=3
Lembro de anotar: “Sempre que tiver a divisão / será em decimal, para inteiro fazer com // - sem ligar pro resto.” E sobre potência: “Dá pra tirar raiz, já que uma raíz é elevar a uma fração. Exemplo: 9 ** (1/2) → raiz quadrada. Mas precisa dos parênteses!”
Operador % (módulo):
Retorna o resto da divisão. Parece inútil, mas é super útil! Veja:
1
2
3
4
5
# 7 dividido por 2 = 3, sobra 1
print(7 % 2) # 1 (o resto)
# 10 dividido por 5 = 2, sobra 0 (divisão exata)
print(10 % 5) # 0
Usos práticos:
- Verificar se é par:
numero % 2 == 0(se resto é 0, é par) - Verificar se é divisível:
numero % 5 == 0(divisível por 5) - Ciclar valores:
indice % tamanho_lista(volta ao início)
Lab 2: Matemática e Manipulação de Números
Trabalhando com Números
Python tem funções embutidas para operações matemáticas comuns:
1
2
3
4
5
6
7
def maior_menor(a, b, c):
"""Retorna o maior e menor entre três números"""
return max(a, b, c), min(a, b, c)
# Note: retorna uma TUPLA (múltiplos valores)
maior, menor = maior_menor(10, 5, 8)
print(f"Maior: {maior}, Menor: {menor}")
Equações de Segundo Grau
Um exercício clássico é resolver equações do tipo $ax^2 + bx + c = 0$ usando a fórmula de Bhaskara:
\[\Delta = b^2 - 4ac\]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def calcular_delta(a, b, c):
"""Calcula discriminante (delta)"""
return b**2 - 4*a*c
def raizes(a, b, c):
"""Calcula as duas raízes da equação"""
delta = calcular_delta(a, b, c)
# Se delta for negativo, não existe raíz real
if delta < 0:
return None, None # Ou poderia usar números complexos
raiz1 = (-b + delta**0.5) / (2*a) # sqrt é raiz quadrada
raiz2 = (-b - delta**0.5) / (2*a)
return raiz1, raiz2
Nota: **0.5 é equivalente a raiz quadrada. Ou pode usar import math e math.sqrt().
Progressão Aritmética (PA)
\[S_n = \frac{n(a_1 + a_n)}{2}\]1
2
3
4
5
6
7
8
9
10
11
12
13
def numero_termos_pa(primeiro, ultimo, razao):
"""Calcula quantos termos tem uma PA"""
# Fórmula: n = (an - a1) / r + 1
return ((ultimo - primeiro) / razao) + 1
def soma_pa(primeiro, ultimo, razao):
"""Calcula a soma de todos os termos da PA"""
n = numero_termos_pa(primeiro, ultimo, razao)
return (n * (primeiro + ultimo)) / 2
# Exemplo: PA de 1 a 10 com razão 1
# numero_termos_pa(1, 10, 1) → 10 termos
# soma_pa(1, 10, 1) → 55
Geometria com Módulo Math
Python tem um módulo math para operações matemáticas avançadas. Para usá-lo, precisamos importar no início do programa:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import math
# Constantes úteis
print(math.pi) # 3.141592653589793
print(math.e) # 2.718281828459045 (número de Euler)
# Funções comuns
math.sqrt(16) # Raiz quadrada: 4.0
math.pow(2, 3) # Potência: 8.0 (igual a 2**3)
math.ceil(3.2) # Arredonda pra cima: 4
math.floor(3.8) # Arredonda pra baixo: 3
math.fabs(-5) # Valor absoluto: 5.0
# Trigonometria (em radianos!)
math.sin(math.pi/2) # Seno de 90°: 1.0
math.cos(0) # Cosseno de 0°: 1.0
math.radians(90) # Converte graus para radianos
math.degrees(math.pi) # Converte radianos para graus: 180.0
Agora alguns exemplos práticos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import math
def distancia_dois_pontos(x1, y1, x2, y2):
"""Distância euclidiana entre dois pontos"""
return math.sqrt((x2-x1)**2 + (y2-y1)**2)
def perimetro_triangulo_reto(cateto_a, cateto_b):
"""Perímetro de triângulo retângulo"""
hipotenusa = math.sqrt(cateto_a**2 + cateto_b**2)
return cateto_a + cateto_b + hipotenusa
def area_setor_circular(raio, angulo_graus=360):
"""Área de um setor circular"""
if angulo_graus == 360:
return math.pi * raio**2 # Círculo completo
return (angulo_graus / 360) * math.pi * raio**2
Lab 3: Condicionais - Tomando Decisões
O Poder de Tomar Decisões
Até agora, nossos programas eram como uma receita de bolo bem simples - faziam uma coisa após outra, sempre na mesma ordem. Mas e se precisarmos fazer coisas diferentes dependendo da situação?
Imagine uma máquina de refrigerantes: ela precisa verificar se você colocou dinheiro suficiente antes de liberar a bebida. Isso é uma condicional - o programa toma decisões baseado em condições.
Nas anotações de aula, tinhamos: “if → condicional. Dependendo do código, melhor um laço for ou while. elif → else if, sempre dependente do if; se não for, criar outro if normal.”
Entendendo if, elif, else
Vamos criar uma função simples que classifica um número:
1
2
3
4
5
6
7
8
9
10
11
12
13
def classificar_numero(n):
"""Classifica um número como positivo, negativo ou zero"""
if n > 0:
return "Positivo"
elif n < 0:
return "Negativo"
else:
return "Zero"
# Testando:
print(classificar_numero(10)) # "Positivo"
print(classificar_numero(-5)) # "Negativo"
print(classificar_numero(0)) # "Zero"
Traduzindo para português:
if= “SE” - Se essa condição for verdadeira, faça issoelif= “SENÃO SE” - Se a anterior for falsa, teste essa outra condiçãoelse= “SENÃO” - Se nenhuma condição anterior foi verdadeira, faça isso
Analogia da porta: Imagine que você está decidindo por qual porta entrar:
ifé a primeira porta - se ela abrir, você entraelifé a segunda porta - só tenta se a primeira não abriuelseé a saída de emergência - usa se nenhuma outra abriu
Operadores de Comparação
| Operador | Significado |
|---|---|
== | Igual a |
!= | Diferente de |
> | Maior que |
< | Menor que |
>= | Maior ou igual |
<= | Menor ou igual |
Combinando Condições
Às vezes precisamos verificar múltiplas condições ao mesmo tempo. Por exemplo, para votar no Brasil você precisa ter idade suficiente E ser brasileiro:
1
2
3
4
5
6
7
8
9
10
11
12
13
def pode_votar(idade, nacionalidade):
"""
Verifica se pode votar.
Precisa ter 18+ anos E ser brasileiro.
"""
if idade >= 18 and nacionalidade == "brasileira":
return True
return False
# Testando:
print(pode_votar(20, "brasileira")) # True
print(pode_votar(15, "brasileira")) # False (menor de idade)
print(pode_votar(25, "americana")) # False (estrangeiro)
Operadores Lógicos:
| Operador | Significado | Exemplo |
|---|---|---|
and | Ambas verdadeiras | True and False → False |
or | Pelo menos uma | True or False → True |
not | Inverte | not True → False |
O que são booleanos? São valores que só podem ser True (verdadeiro) ou False (falso). Toda comparação retorna um booleano:
1
2
3
4
print(5 > 3) # True
print(10 == 10) # True
print("a" == "b") # False
print(not False) # True
Valores “falsy” em Python: Além de False, alguns valores são considerados “falsos” em contextos booleanos:
None0(zero)""(string vazia)[](lista vazia){}(dicionário vazio)
1
2
if lista: # É equivalente a: if len(lista) > 0:
print("Lista tem elementos")
Exemplo Prático: Cálculo de Impostos
Agora vamos aplicar condicionais em algo mais realista:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def desconto_inss(salario_bruto):
"""
Calcula desconto de INSS baseado em faixas salariais.
Valores simplificados para fins didáticos.
"""
if salario_bruto <= 2000:
return salario_bruto * 0.06 # 6% para até R$2000
elif salario_bruto <= 3000:
return salario_bruto * 0.08 # 8% para R$2000-R$3000
else:
return salario_bruto * 0.10 # 10% acima de R$3000
def salario_liquido(salario_bruto):
"""Calcula salário após descontos"""
inss = desconto_inss(salario_bruto)
return salario_bruto - inss
# Testando:
print(f"Salário bruto: R$ 2500")
print(f"Desconto INSS: R$ {desconto_inss(2500)}") # R$ 200.0 (8%)
print(f"Salário líquido: R$ {salario_liquido(2500)}") # R$ 2300.0
Importante: Usar if para tomar decisões torna programas muito mais poderosos!
Lab 4: Tuplas e Strings
O Que É Uma Tupla?
Uma tupla é como uma lista, mas imutável - depois de criada, não pode ser alterada. Pense assim: uma lista é um caderno (pode apagar e reescrever), uma tupla é um documento oficial impresso (o que está ali, está).
Use parênteses para criar tuplas:
1
2
3
4
5
6
7
8
9
10
11
12
# Criando uma tupla
coordenadas = (10, 20)
resultado = (10, 20, 30)
# Desempacotamento - super útil!
x, y, z = resultado # x=10, y=20, z=30
# Função retornando múltiplos valores (na verdade retorna tupla)
def dividir_e_resto(a, b):
return a // b, a % b
quociente, resto = dividir_e_resto(17, 5) # quociente=3, resto=2
Por que usar tupla se lista faz a mesma coisa?
- Tuplas são mais rápidas (para listas grandes)
- Tuplas podem ser chaves de dicionário (listas não podem)
- Tuplas deixam claro que o dado não deve mudar
Trabalhando com Strings
Strings em Python são extremamente poderosas. Na verdade, uma string é uma sequência de caracteres - quase como uma lista de letras.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
nome = "Matheus"
# Concatenação (juntar strings)
saudacao = "Olá, " + nome # "Olá, Matheus"
# Repetição
barras = "=" * 10 # "=========="
# Acessar caractere por índice (começa em 0!)
primeira_letra = nome[0] # "M"
ultima_letra = nome[-1] # "s" (índices negativos contam de trás)
# Comprimento
tamanho = len(nome) # 7
Fatiamento (Slicing) - Super Importante!
Slicing é uma das features mais úteis de Python. A sintaxe é string[inicio:fim:passo]:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
texto = "PYTHON"
# 012345
# Básico: [inicio:fim] - pega do inicio até fim-1
print(texto[0:3]) # "PYT" (índices 0, 1, 2)
print(texto[2:5]) # "THO" (índices 2, 3, 4)
# Omitindo valores
print(texto[:3]) # "PYT" (do início até índice 2)
print(texto[3:]) # "HON" (do índice 3 até o final)
print(texto[:]) # "PYTHON" (cópia completa)
# Com passo
print(texto[::2]) # "PTO" (de 2 em 2)
print(texto[::-1]) # "NOHTYP" (invertido!)
Dica: [::-1] inverte qualquer sequência. Muito útil para verificar palíndromos!
Desafio: Como você verificaria se uma palavra é um palíndromo (igual de trás pra frente)? Pense antes de ver a solução!
💡 Ver solução
```python def eh_palindromo(palavra): """Verifica se palavra é igual ao contrário""" palavra = palavra.lower() # Ignorar maiúsculas/minúsculas return palavra == palavra[::-1] print(eh_palindromo("arara")) # True print(eh_palindromo("python")) # False print(eh_palindromo("Ovo")) # True ```Exemplo: Formatando Datas
1
2
3
4
5
6
def formatar_data(dia, mes, ano):
"""Retorna data formatada como DD/MM/AAAA"""
return f"{dia:02d}/{mes:02d}/{ano:04d}"
# :02d significa: inteiro com pelo menos 2 dígitos, preenchendo com 0
formatar_data(3, 8, 2020) # "03/08/2020"
Exemplo: Sistema de Notas (SIGA)
Vamos criar uma função que simula o sistema de notas da universidade. Ela recebe as três notas e retorna uma tupla com o resultado:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def avaliar_aluno(nome, p1, p2, p3):
"""
Retorna tupla com nome, média e situação.
Args:
nome: Nome do aluno
p1, p2, p3: Notas das três provas
Returns:
Tupla: (nome, media_arredondada, status)
"""
media = (p1 + p2 + p3) / 3
if media >= 7:
status = "Aprovado"
elif media >= 5:
status = "Recuperação"
else:
status = "Reprovado"
return (nome, round(media, 1), status)
# Testando - note o desempacotamento da tupla:
nome, media, status = avaliar_aluno("Maria", 8.5, 7.0, 6.5)
print(f"{nome}: {media} - {status}") # Maria: 7.3 - Aprovado
# Também podemos receber como tupla:
resultado = avaliar_aluno("João", 4.0, 5.0, 3.5)
print(resultado) # ('João', 4.2, 'Reprovado')
Perceba: A função retorna uma tupla porque o resultado não deve ser modificado depois.
Dicionários: Dados com Significado
Enquanto listas usam índice numérico (posição), dicionários usam “chaves” (nomes). É como a diferença entre:
- Lista: “Me dá o item na posição 3”
- Dicionário: “Me dá o item chamado ‘nome’”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Criar dicionário com chaves e valores
aluno = {
"nome": "Matheus",
"idade": 20,
"matricula": 12345
}
# Acessar valor pela chave
print(aluno["nome"]) # "Matheus"
# Adicionar novo par chave-valor
aluno["email"] = "matheus@ufrj.br"
# Verificar se chave existe (importante para evitar erros!)
if "telefone" in aluno:
print(aluno["telefone"])
else:
print("Telefone não registrado")
# Forma segura: get() retorna None se não existir
telefone = aluno.get("telefone") # None, sem erro
telefone = aluno.get("telefone", "Não informado") # valor padrão
Por que get() é melhor? Se você usar aluno["chave_inexistente"], Python levanta um KeyError e o programa para. Com get(), ele retorna None (ou um valor padrão) sem quebrar.
Sets (Conjuntos) - Prévia Rápida
Antes de avançar, vale conhecer os sets (conjuntos). São como listas, mas sem duplicatas e com operações matemáticas poderosas:
1
2
3
4
5
6
7
8
9
10
11
# Criar set
numeros = {1, 2, 3, 3, 3} # Duplicatas são ignoradas
print(numeros) # {1, 2, 3}
# Operações entre sets
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a & b) # {3, 4} - Interseção (elementos em ambos)
print(a | b) # {1, 2, 3, 4, 5, 6} - União (todos elementos)
print(a - b) # {1, 2} - Diferença (só em a)
Usaremos sets mais adiante em projetos. Por enquanto, saiba que existem!
Parte II: Estruturas de Dados
Lab 5: Listas - Coleções Dinâmicas
Entendendo Listas
Listas são provavelmente a estrutura de dados mais usada em Python. Pense nelas como uma prateleira organizável onde você pode:
- Adicionar itens no final
- Remover itens de qualquer lugar
- Reorganizar a ordem
- Acessar qualquer item pelo número da posição
Importante: Em programação, contamos a partir do ZERO. O primeiro elemento está na posição 0, o segundo na posição 1, e assim por diante.
1
2
3
4
5
frutas = ["maçã", "banana", "laranja"]
# [0] [1] [2]
print(frutas[0]) # "maçã"
print(frutas[-1]) # "laranja" (último elemento)
Criando e Manipulando Listas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Criar lista vazia e adicionar elementos
contatos = []
contatos.append({"nome": "João", "telefone": "999999999"})
contatos.append({"nome": "Maria", "telefone": "988888888"})
# Acessar elemento
primeiro = contatos[0]
# Verificar tamanho
quantidade = len(contatos)
# Remover
contatos.pop(0) # Remove primeiro elemento
# Verificar se existe
if {"nome": "João", "telefone": "999999999"} in contatos:
print("João está nos contatos")
Métodos Úteis de Listas
| Método | O que faz |
|---|---|
append(x) | Adiciona elemento no final |
pop(i) | Remove e retorna elemento no índice i |
remove(x) | Remove primeira ocorrência de x |
sort() | Ordena lista |
reverse() | Inverte ordem |
count(x) | Conta quantas vezes x aparece |
index(x) | Retorna índice de x |
Iterando com for
A forma mais Pythônica de percorrer uma lista:
1
2
3
4
5
6
7
8
9
10
11
12
13
frutas = ["maçã", "banana", "laranja"]
# Forma 1: sobre os elementos
for fruta in frutas:
print(fruta)
# Forma 2: com índice
for i in range(len(frutas)):
print(f"{i}: {frutas[i]}")
# Forma 3: enumerado (índice e elemento)
for i, fruta in enumerate(frutas):
print(f"{i}: {fruta}")
Entendendo range() - Gerador de Sequências
range() cria uma sequência de números. É fundamental para loops em Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# range(fim) - de 0 até fim-1
for i in range(5):
print(i) # 0, 1, 2, 3, 4
# range(inicio, fim) - de inicio até fim-1
for i in range(2, 6):
print(i) # 2, 3, 4, 5
# range(inicio, fim, passo) - com incremento personalizado
for i in range(0, 10, 2):
print(i) # 0, 2, 4, 6, 8 (de 2 em 2)
# Contagem regressiva
for i in range(5, 0, -1):
print(i) # 5, 4, 3, 2, 1
Dica: range() não cria uma lista na memória - ele gera os números conforme necessário. Por isso é eficiente mesmo para sequências enormes.
Lab 6: Dicionários - Estruturas Complexas
O Que São Dicionários?
Se listas são como prateleiras numeradas, dicionários são como… dicionários! Você busca por uma “palavra” (chave) e encontra uma “definição” (valor).
1
2
3
4
5
6
7
8
9
10
11
# Lista: acessa por posição numérica
alunos_lista = ["Maria", "João", "Ana"]
print(alunos_lista[0]) # "Maria"
# Dicionário: acessa por chave significativa
notas = {
"Maria": 9.5,
"João": 7.0,
"Ana": 8.5
}
print(notas["Maria"]) # 9.5
Quando usar cada um?
- Lista: Quando a ordem importa e os dados são homogêneos (todos do mesmo tipo)
- Dicionário: Quando você precisa buscar por nome/identificador, não por posição
Operações com Dicionários
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Criar
tabela_campeonato = {
"Flamengo": {"vitórias": 10, "empates": 2, "derrotas": 1},
"Botafogo": {"vitórias": 8, "empates": 3, "derrotas": 2},
"Vasco": {"vitórias": 7, "empates": 2, "derrotas": 4}
}
# Acessar valor
pontos_flamengo = tabela_campeonato["Flamengo"]["vitórias"] * 3 + tabela_campeonato["Flamengo"]["empates"]
# Iterar sobre dicionário
for time, dados in tabela_campeonato.items():
print(f"{time}: {dados['vitórias']} vitórias")
# Adicionar novo time
tabela_campeonato["Corinthians"] = {"vitórias": 9, "empates": 1, "derrotas": 3}
# Remover
del tabela_campeonato["Vasco"]
Calculando Estatísticas
No futebol, vitória vale 3 pontos e empate vale 1. Vamos criar funções para trabalhar com isso:
1
2
3
def calcular_pontos_time(vitorias, empates):
"""No futebol: vitória = 3 pontos, empate = 1 ponto"""
return vitorias * 3 + empates
Desafio: Usando a função acima e o dicionário tabela_campeonato, como você encontraria o time com mais pontos? Tente pensar na lógica antes de ver a solução.
💡 Ver solução
```python def melhor_time(tabela): """Retorna o time com mais pontos""" melhor = None max_pontos = -1 for time, dados in tabela.items(): pontos = calcular_pontos_time(dados["vitórias"], dados["empates"]) if pontos > max_pontos: max_pontos = pontos melhor = time return melhor, max_pontos # Testando: vencedor, pontos = melhor_time(tabela_campeonato) print(f"{vencedor} com {pontos} pontos") # Flamengo com 32 pontos ``` **A lógica:** Guardamos o melhor até agora. Para cada time, calculamos os pontos e comparamos. Se for maior, atualizamos o "melhor".Lab 7: Loops while - Repetição Controlada
Por Que Precisamos de Loops?
Imagine que você precisa imprimir “Olá” 1000 vezes. Escrever print("Olá") mil vezes seria insano! Loops resolvem isso - eles repetem um bloco de código várias vezes.
Python tem dois tipos principais de loops, e cada um tem seu uso ideal.
Quando Usar Cada Um?
Pense assim:
for= “Repita X vezes” (você sabe quantas)while= “Repita até que…” (não sabe quantas)
Diferença entre for e while
for - Use quando você sabe quantas vezes vai repetir:
- “Repita 10 vezes”
- “Para cada item da lista”
- “Para cada número de 1 a 100”
while - Use quando você não sabe quantas vezes vai repetir:
- “Enquanto o usuário não digitar ‘sair’”
- “Enquanto não encontrar o resultado”
- “Enquanto ainda tiver vida no jogo”
1
2
3
4
5
6
7
8
9
# for - sabe que vai repetir 5 vezes
for i in range(5):
print(i)
# while - repete enquanto a condição for verdadeira
contador = 0
while contador < 5:
print(contador)
contador += 1 # IMPORTANTE: sem isso, loop infinito!
O que é +=? É um atalho! contador += 1 é a mesma coisa que contador = contador + 1. Existem também -=, *=, /=.
Armadilha comum: Esquecer de atualizar a variável no while causa loop infinito (o programa trava). Se seu programa “congelar”, provavelmente é isso!
Exemplo Prático: Jogo de Dados
Antes de ver o exemplo, precisamos conhecer o módulo random:
Módulo random - Números Aleatórios:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import random
# Número inteiro aleatório entre a e b (inclusive)
numero = random.randint(1, 6) # Simula um dado: 1, 2, 3, 4, 5 ou 6
# Escolher elemento aleatório de uma lista
cores = ["vermelho", "azul", "verde"]
cor = random.choice(cores) # Uma das três
# Embaralhar uma lista
cartas = [1, 2, 3, 4, 5]
random.shuffle(cartas) # Modifica a lista original!
# Número decimal entre 0 e 1
chance = random.random() # Ex: 0.7342518...
Agora sim, o jogo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import random
def jogar_dados(quantidade_jogadas):
"""Simula jogadas de dado e retorna lista com os resultados"""
jogadas = []
for _ in range(quantidade_jogadas):
jogadas.append(random.randint(1, 6))
return jogadas
# Usando while para processar as jogadas
def processar_jogadas(jogadas):
"""
Conta quantas vezes um número repetiu em sequência.
Exemplo: [1, 2, 2, 3, 3, 3, 4] tem 2 sequências:
- Os dois 2's seguidos
- Os três 3's seguidos
Obs: Conta quando a sequência TERMINA, não quando começa.
"""
i = 0
sequencias = 0
while i < len(jogadas):
if i > 0 and jogadas[i] == jogadas[i-1]:
# Elemento atual é igual ao anterior (estamos numa sequência)
# Agora verificamos se é o FINAL da sequência
if i+1 >= len(jogadas) or jogadas[i] != jogadas[i+1]:
# Acabou a lista OU o próximo é diferente = sequência terminou
sequencias += 1
i += 1
return sequencias
# Testando:
teste = [1, 2, 2, 3, 3, 3, 4]
print(processar_jogadas(teste)) # 2 (duas sequências de repetição)
Busca Linear com while
Busca linear é o algoritmo mais simples de busca: olhamos elemento por elemento até encontrar o que queremos (ou acabar a lista). É lento para listas grandes, mas funciona em qualquer situação.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def buscar_contato(agenda, telefone_buscado):
"""
Busca contato por telefone na agenda.
Retorna o contato se encontrar, None se não existir.
"""
i = 0
while i < len(agenda):
contato = agenda[i]
if contato["telefone"] == telefone_buscado:
return contato # Encontrou! Sai da função
i += 1
return None # Percorreu tudo e não encontrou
# Exemplo de uso:
agenda = [
{"nome": "João", "telefone": "999999999"},
{"nome": "Maria", "telefone": "988888888"},
{"nome": "Pedro", "telefone": "977777777"}
]
resultado = buscar_contato(agenda, "988888888")
if resultado:
print(f"Encontrado: {resultado['nome']}") # Encontrado: Maria
else:
print("Não encontrado!")
resultado = buscar_contato(agenda, "000000000")
print(resultado) # None
Por que while e não for? Neste caso, ambos funcionariam. Mas while deixa explícito que estamos buscando até encontrar (ou acabar). É questão de estilo.
Lab 8: Loops for Avançados
List Comprehension - Pythônico
Python tem uma forma elegante e compacta de criar listas. É chamada de list comprehension (compreensão de lista). Compare:
1
2
3
4
5
6
7
8
9
10
11
# Forma tradicional - várias linhas
quadrados = []
for i in range(10):
quadrados.append(i**2)
print(quadrados) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# Forma Pythônica (list comprehension) - uma linha!
quadrados = [i**2 for i in range(10)]
print(quadrados) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
A estrutura é: [expressão for variável in iteração]
Com condição (filtro):
1
2
3
# Apenas números pares de 0 a 19
pares = [i for i in range(20) if i % 2 == 0]
print(pares) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Transformando elementos:
1
2
3
4
# Converter lista para maiúsculas
nomes = ["ana", "bruno", "carlos"]
nomes_upper = [nome.upper() for nome in nomes]
print(nomes_upper) # ['ANA', 'BRUNO', 'CARLOS']
Quando usar? List comprehension é ótima para transformações simples. Para lógica complexa, use o loop tradicional - clareza vem primeiro!
Iterando com Múltiplas Variáveis
Às vezes precisamos percorrer duas listas ao mesmo tempo. O zip() combina listas elemento por elemento:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
nomes = ["Ana", "Bruno", "Carlos"]
notas = [8.5, 7.0, 9.0]
# Sem zip - mais complicado:
for i in range(len(nomes)):
print(f"{nomes[i]} tirou {notas[i]}")
# Com zip - mais elegante:
for nome, nota in zip(nomes, notas):
print(f"{nome} tirou {nota}")
# Saída:
# Ana tirou 8.5
# Bruno tirou 7.0
# Carlos tirou 9.0
Como funciona? zip cria pares: ("Ana", 8.5), ("Bruno", 7.0), ("Carlos", 9.0). O for desempacota cada par em nome e nota.
Cuidado: Se as listas tiverem tamanhos diferentes, zip para no menor.
Exemplo: Frequência de Palavras
Um exercício clássico: contar quantas vezes cada palavra aparece num texto. Este exemplo usa dicionário para acumular contagens:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def contar_frequencia_palavras(texto):
"""
Conta quantas vezes cada palavra aparece.
Args:
texto: string com as palavras
Returns:
Dicionário {palavra: contagem}
"""
# Converter para minúsculas e separar por espaços
palavras = texto.lower().split()
# Dicionário para acumular contagens
frequencia = {}
for palavra in palavras:
if palavra in frequencia:
# Palavra já existe, incrementar
frequencia[palavra] += 1
else:
# Primeira ocorrência
frequencia[palavra] = 1
return frequencia
# Testando:
texto = "python python java python java c c c"
freq = contar_frequencia_palavras(texto)
print(freq) # {'python': 3, 'java': 2, 'c': 3}
# Qual palavra aparece mais?
for palavra, contagem in freq.items():
print(f"{palavra}: {contagem} vezes")
Métodos usados:
texto.lower(): converte para minúsculastexto.split(): separa string em lista (por espaços)dicionario.items(): retorna pares (chave, valor) para iteração
Parte III: Programação Intermediária
Lab 9: Matrizes - Arrays Bidimensionais
O Que São Matrizes?
Até agora trabalhamos com listas simples (uma dimensão). Mas e se precisarmos representar algo com linhas e colunas? É aí que entram as matrizes.
Uma matriz é basicamente uma “lista de listas” - imagine uma tabela com linhas e colunas, como uma planilha do Excel. Cada posição é identificada por dois números: linha e coluna.
Onde usamos matrizes?
- Tabuleiros de jogos (xadrez, jogo da velha, campo minado)
- Imagens (cada pixel é uma posição na matriz)
- Tabelas de dados
- Mapas de jogos
Visualizando uma Matriz
1
2
3
4
5
Coluna 0 Coluna 1 Coluna 2
↓ ↓ ↓
Linha 0 → [ 1 , 2 , 3 ]
Linha 1 → [ 4 , 5 , 6 ]
Linha 2 → [ 7 , 8 , 9 ]
Para acessar o 5, usamos matriz[1][1] - linha 1, coluna 1.
Criando Matrizes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Matriz 3x3 (3 linhas, 3 colunas)
matriz = [
[1, 2, 3], # Linha 0
[4, 5, 6], # Linha 1
[7, 8, 9] # Linha 2
]
# Acessar elemento: matriz[linha][coluna]
elemento = matriz[0][1] # Linha 0, Coluna 1 = 2
print(elemento) # 2
elemento = matriz[1][1] # Linha 1, Coluna 1 = 5
print(elemento) # 5
# Modificar um elemento
matriz[1][1] = 99
print(matriz[1]) # [4, 99, 6]
# Descobrir dimensões
linhas = len(matriz) # Quantas linhas = 3
colunas = len(matriz[0]) # Quantas colunas = 3 (olhamos a primeira linha)
print(f"Matriz {linhas}x{colunas}")
Atenção com índices! É fácil confundir matriz[linha][coluna] com matriz[coluna][linha]. Sempre pense: primeiro a linha (vertical), depois a coluna (horizontal).
Percorrendo Matrizes
Para operar em todos os elementos, usamos loops aninhados (um dentro do outro):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def multiplicar_matriz_por_escalar(matriz, escalar):
"""
Multiplica cada elemento da matriz por um número.
Retorna uma NOVA matriz (não modifica a original).
"""
resultado = []
for linha in matriz:
nova_linha = []
for elemento in linha:
nova_linha.append(elemento * escalar)
resultado.append(nova_linha)
return resultado
# Testando:
matriz = [[1, 2], [3, 4]]
dobrada = multiplicar_matriz_por_escalar(matriz, 2)
print(dobrada) # [[2, 4], [6, 8]]
Versão com list comprehension (mais avançada):
1
2
def multiplicar_matriz_v2(matriz, escalar):
return [[elem * escalar for elem in linha] for linha in matriz]
A versão com list comprehension faz a mesma coisa em uma linha, mas é menos legível para iniciantes. Use a que você entender melhor!
Encontrando Mínimo e Máximo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def melhor_volta_kart(tempos_pilotos):
"""
Encontra o melhor tempo (menor) em uma matriz de tempos.
tempos_pilotos é uma lista de listas.
Retorna (piloto, tempo, volta)
"""
# float('inf') é "infinito" - qualquer número real é menor
# Usamos como valor inicial para encontrar o mínimo
melhor_tempo = float('inf')
piloto_melhor = 0
volta_melhor = 0
for i, tempos in enumerate(tempos_pilotos):
for j, tempo in enumerate(tempos):
if tempo < melhor_tempo:
melhor_tempo = tempo
piloto_melhor = i + 1 # +1 porque pilotos começam em 1
volta_melhor = j + 1 # +1 porque voltas começam em 1
return piloto_melhor, melhor_tempo, volta_melhor
# Exemplo de uso:
tempos = [
[65.2, 64.8, 65.5], # Piloto 1: três voltas
[64.1, 64.5, 64.3], # Piloto 2
[65.0, 64.2, 64.9] # Piloto 3
]
piloto, tempo, volta = melhor_volta_kart(tempos)
print(f"Melhor volta: Piloto {piloto}, {tempo}s na volta {volta}")
# Resultado: Piloto 2, 64.1s na volta 1
Por que float('inf')? Quando buscamos o menor valor, precisamos de um ponto de partida. Se começarmos com 0, o primeiro tempo válido (ex: 64.5) seria maior, e não atualizaríamos. Com “infinito”, qualquer tempo real é menor.
Lab 10: Integração - Sistema com Menu
Criando um Menu Interativo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def menu_principal():
"""Exibe menu e retorna escolha do usuário"""
print("\n" + "="*40)
print("MENU PRINCIPAL")
print("="*40)
print("1 - Opção A")
print("2 - Opção B")
print("3 - Opção C")
print("0 - Sair")
print("="*40)
escolha = input("Digite sua escolha: ")
return escolha
def validar_entrada(entrada):
"""Verifica se entrada é um número válido"""
try:
numero = int(entrada)
return numero
except ValueError:
print("Erro: Digite um número válido!")
return None
def main():
"""Programa principal"""
while True:
escolha = menu_principal()
numero = validar_entrada(escolha)
if numero is None:
continue
if numero == 0:
print("Encerrando programa...")
break
elif numero == 1:
print("Executando Opção A...")
elif numero == 2:
print("Executando Opção B...")
elif numero == 3:
print("Executando Opção C...")
else:
print("Opção inválida!")
if __name__ == "__main__":
main()
Try-Except para Tratamento de Erros
Quando algo dá errado em Python, o programa “levanta uma exceção” e para. Por exemplo:
1
2
resultado = 10 / 0 # ZeroDivisionError - programa PARA aqui!
print("Isso nunca executa")
Mas às vezes queremos que o programa continue mesmo com erros - por exemplo, se o usuário digitar uma letra quando esperávamos um número. É aí que entra o try-except.
A estrutura básica:
1
2
3
4
5
6
try:
# Código que PODE dar erro
codigo_arriscado()
except TipoDoErro:
# O que fazer SE der erro
tratar_o_erro()
Analogia: try-except é como dirigir com cinto de segurança. Você não espera bater, mas se acontecer, está protegido.
Exemplo prático - divisão segura:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def dividir(a, b):
"""Tenta dividir, mas trata erro de divisão por zero"""
try:
resultado = a / b
return resultado
except ZeroDivisionError:
print("Erro: Não pode dividir por zero!")
return None
except TypeError:
print("Erro: Tipos inválidos para divisão!")
return None
# Testando:
print(dividir(10, 2)) # 5.0 - funcionou normal
print(dividir(10, 0)) # Erro: Não pode dividir por zero! / None
print(dividir("a", 2)) # Erro: Tipos inválidos! / None
Tipos de erro comuns:
| Erro | Quando acontece |
|---|---|
ZeroDivisionError | Divisão por zero |
ValueError | Valor inadequado (ex: int("abc")) |
TypeError | Tipo errado (ex: "a" + 1) |
IndexError | Índice fora da lista |
KeyError | Chave não existe no dicionário |
FileNotFoundError | Arquivo não encontrado |
Entrada do Usuário com Validação
Combinando while e try-except, podemos criar funções que insistem até o usuário digitar corretamente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def obter_numero(mensagem):
"""
Pede um número ao usuário.
Repete até digitar um número válido.
"""
while True:
try:
valor = int(input(mensagem))
return valor # Conseguiu! Sai da função
except ValueError:
print("Erro: Digite um número inteiro!")
# Loop continua, pede de novo
# Usar:
numero = obter_numero("Digite um número: ")
print(f"Você digitou: {numero}")
Por que funciona? O while True cria um loop “infinito”, mas o return dentro do try sai da função quando consegue converter. Se der ValueError, o loop continua e pede novamente.
Parte IV: Lógica e Problemas Clássicos
Antes de partirmos para projetos maiores, vale a pena ver alguns problemas clássicos que aparecem em praticamente todo curso de programação. Esses exercícios parecem simples, mas ensinam padrões de pensamento que você vai usar pra sempre.
O legal desses problemas é que eles começam com uma descrição em português e você precisa traduzir para código. Essa habilidade de “pensar como programador” é mais importante que decorar sintaxe!
Problemas de Simulação com While
Um tipo de problema muito comum é simular situações do mundo real. Veja este clássico:
Crescimento Populacional
O problema: País A tem população menor que B, mas cresce mais rápido. Em quantos anos A ultrapassa B?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def crescimento_populacional(pop_a, pop_b, taxa_a, taxa_b):
"""
Calcula quantos anos para população A ultrapassar B.
Args:
pop_a, pop_b: populações iniciais
taxa_a, taxa_b: taxas de crescimento em %
Returns:
Número de anos, ou -1 se nunca ultrapassar
"""
# Primeiro, verificar se faz sentido
if pop_a >= pop_b:
return 0 # Já ultrapassou
if taxa_a <= taxa_b:
return -1 # Nunca vai ultrapassar
anos = 0
while pop_a < pop_b:
# Aplicar crescimento
pop_a = pop_a + pop_a * (taxa_a / 100)
pop_b = pop_b + pop_b * (taxa_b / 100)
anos = anos + 1
return anos
# Exemplo: Albônia vs Betônia
# Albônia: 80.000 habitantes, cresce 3% ao ano
# Betônia: 200.000 habitantes, cresce 1.5% ao ano
anos = crescimento_populacional(80000, 200000, 3, 1.5)
print(f"A ultrapassa B em {anos} anos")
O truque aqui: Antes de entrar no loop, verificamos se o problema tem solução. Se A já é maior, retorna 0. Se A cresce mais devagar, retorna -1 (nunca vai ultrapassar). Isso evita loops infinitos!
Encontrando Valores em Sequências
Última Ocorrência de um Caractere
Problema comum: encontrar onde uma letra aparece pela última vez numa palavra.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def ultima_ocorrencia(frase, letra):
"""
Encontra o índice da última vez que a letra aparece.
Versão com while (mais didática):
"""
i = 0
pos = -1 # -1 significa "não encontrou"
while i < len(frase):
if frase[i] == letra:
pos = i # Atualiza toda vez que encontra
i = i + 1
return pos
# Versão com for (mais Pythônica):
def ultima_ocorrencia_v2(frase, letra):
"""Mesma coisa, mas com for"""
pos = -1
for i in range(len(frase)):
if frase[i] == letra:
pos = i
return pos
# Testando:
print(ultima_ocorrencia("banana", "a")) # 5 (última posição)
print(ultima_ocorrencia("banana", "z")) # -1 (não existe)
Perceba: Não usamos return dentro do loop! Se fizéssemos, sairíamos na primeira ocorrência. Queremos a última, então guardamos em pos e só retornamos no final.
Encontrar o Menor Divisor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def menor_divisor(num):
"""
Encontra o menor divisor de um número (maior que 1).
Útil para verificar se é primo!
"""
d = 2
while num % d != 0: # Enquanto não dividir exato
d = d + 1
return d
# Se menor_divisor(n) == n, então n é primo!
print(menor_divisor(15)) # 3
print(menor_divisor(17)) # 17 (é primo!)
print(menor_divisor(100)) # 2
Problemas com Condicionais Compostas
Meia-Entrada: Várias Formas de Resolver
Um problema que parece simples mas tem várias soluções elegantes:
O problema: Tem direito a meia-entrada quem:
- Tem carteira de estudante, OU
- Tem menos de 21 anos, OU
- Tem 65 anos ou mais
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Versão 1: Mais explícita (boa para iniciantes)
def meia_entrada_v1(carteira, idade):
"""carteira é bool, idade é int"""
if carteira == True:
return True
elif idade < 21:
return True
elif idade >= 65:
return True
else:
return False
# Versão 2: Simplificando os elifs
def meia_entrada_v2(carteira, idade):
if carteira: # "if carteira == True" é redundante
return True
if idade < 21 or idade >= 65:
return True
return False
# Versão 3: Uma linha só!
def meia_entrada_v3(carteira, idade):
"""A mais elegante: junta tudo com 'or'"""
return carteira or idade < 21 or idade >= 65
Lição: Em Python, condições que retornam True ou False podem ser simplificadas drasticamente. A versão 3 faz exatamente a mesma coisa que a versão 1, mas em uma linha!
Sistema de Pontuação de Campeonato
Este foi um exercício que fizemos sobre calcular pontos em um campeonato de futebol:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def pontos_por_time(jogos):
"""
Calcula pontos de dois times em uma fase de campeonato.
Args:
jogos: lista com dois jogos, cada um no formato:
[time_casa, time_fora, [gols_casa, gols_fora]]
Returns:
Dicionário {time: pontos}
Exemplo de entrada:
[['Cormengo','Flamínthians',[1,0]], ['Flamínthians','Cormengo',[2,2]]]
"""
time1 = jogos[0][0]
time2 = jogos[0][1]
pontos_t1 = 0
pontos_t2 = 0
# Processar primeiro jogo
gols_casa = jogos[0][2][0]
gols_fora = jogos[0][2][1]
if gols_casa > gols_fora:
pontos_t1 += 3 # Time 1 jogou em casa e ganhou
elif gols_casa < gols_fora:
pontos_t2 += 3 # Time 2 jogou fora e ganhou
else:
pontos_t1 += 1 # Empate
pontos_t2 += 1
# Processar segundo jogo (times invertidos!)
gols_casa = jogos[1][2][0]
gols_fora = jogos[1][2][1]
if gols_casa > gols_fora:
pontos_t2 += 3 # Time 2 jogou em casa
elif gols_casa < gols_fora:
pontos_t1 += 3 # Time 1 jogou fora
else:
pontos_t1 += 1
pontos_t2 += 1
return {time1: pontos_t1, time2: pontos_t2}
# Testando:
jogos = [
['Cormengo', 'Flamínthians', [1, 0]], # Cormengo 1x0 Flamínthians
['Flamínthians', 'Cormengo', [2, 2]] # Flamínthians 2x2 Cormengo
]
resultado = pontos_por_time(jogos)
print(resultado) # {'Cormengo': 4, 'Flamínthians': 1}
O pulo do gato: No segundo jogo, os times trocam de lado (casa/fora). Precisa prestar atenção nisso!
Filtragem com For e While
Filtrar elementos de uma lista é uma operação super comum:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def filtra_e_soma(valores, limite):
"""
Soma apenas os elementos maiores que o limite.
Versão com for:
"""
soma = 0
for valor in valores:
if valor > limite:
soma = soma + valor
return soma
def filtra_e_soma_while(valores, limite):
"""
Mesma coisa, mas com while.
Útil quando você precisa do índice:
"""
soma = 0
i = 0
while i < len(valores):
if valores[i] > limite:
soma = soma + valores[i]
i = i + 1
return soma
# Testando:
numeros = [5, 12, 3, 8, 15, 2, 9]
print(filtra_e_soma(numeros, 7)) # 12 + 8 + 15 + 9 = 44
Encontrando Elementos em Comum
Desafio: Dadas duas listas, como encontrar os elementos que aparecem em ambas? Tente resolver antes de ver a solução!
💡 Ver solução
```python def elementos_em_comum(lista1, lista2): """ Retorna lista com elementos que aparecem em ambas. """ comum = [] for elemento in lista1: if elemento in lista2: comum.append(elemento) return comum # Testando: a = [1, 2, 3, 4, 5] b = [4, 5, 6, 7, 8] print(elementos_em_comum(a, b)) # [4, 5] ``` **Versão com set (mais eficiente):** ```python def elementos_em_comum_v2(lista1, lista2): return list(set(lista1) & set(lista2)) ```Funções Matemáticas com Loops Aninhados
Às vezes você precisa de loop dentro de loop:
1
2
3
4
5
6
7
8
9
10
11
12
def multiplicar_com_soma(x, y):
"""
Implementa multiplicação usando apenas soma!
x * y = x + x + x + ... (y vezes)
"""
resultado = 0
for _ in range(y):
resultado = resultado + x
return resultado
# 3 * 5 = 3 + 3 + 3 + 3 + 3 = 15
print(multiplicar_com_soma(3, 5)) # 15
Por que _? Quando não vamos usar a variável do loop, convenção é usar _. É como dizer “não me importa o valor, só quero repetir”.
Parte V: Projetos Práticos
Agora vamos juntar tudo que aprendemos em projetos reais! A diferença entre “saber programação” e “programar de verdade” está em aplicar os conceitos juntos para resolver problemas completos.
Trabalho Final: Sistema de Receitas
O trabalho final do curso foi desenvolver um sistema completo que lê receitas de um arquivo de texto e sugere o que você pode fazer com os ingredientes que tem em casa.
Por que esse projeto é bom para aprender? Ele integra praticamente tudo que vimos:
- Funções (modularização)
- Dicionários e listas (estruturas de dados)
- Loops e condicionais (lógica)
- Manipulação de strings (processamento de texto)
- Leitura de arquivos (entrada de dados reais)
Leitura de Arquivos em Python
Uma das habilidades mais úteis é ler dados de arquivos externos. Python torna isso bem simples:
1
2
3
4
5
6
7
8
9
def ler_arquivo(nome_arquivo):
"""
Lê o conteúdo completo de um arquivo de texto.
Retorna uma string com todo o conteúdo.
"""
arquivo = open(nome_arquivo, 'r', encoding='utf8')
conteudo = arquivo.read()
arquivo.close()
return conteudo
Entendendo a função open():
- Primeiro argumento: nome/caminho do arquivo
'r': modo de leitura (read). Outros modos:'w'(escrita),'a'(append)encoding='utf8': garante que caracteres especiais (acentos) funcionem
Forma mais segura com with:
1
2
3
4
def ler_arquivo_seguro(nome_arquivo):
"""Forma mais segura - fecha automaticamente"""
with open(nome_arquivo, 'r', encoding='utf8') as arquivo:
return arquivo.read()
O with é como um “guarda-costas” - garante que o arquivo será fechado mesmo se der erro no meio.
Processando o Arquivo de Receitas
O arquivo receitas.txt tinha esse formato:
1
2
3
4
5
Receita: Bolo
Tipo: Doce
- Ovos: 3 unidades
- Farinha: 10 colheres de sopa
- Fermento: 1 colher de sopa
Para transformar isso em dados úteis, precisamos “parsear” (processar) o texto:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def processar_receita(texto_receita):
"""
Transforma texto de receita em dicionário estruturado.
"""
linhas = texto_receita.strip().split('\n')
# Extrair nome e tipo
nome = linhas[0].replace("Receita: ", "")
tipo = linhas[1].replace("Tipo: ", "")
# Extrair ingredientes
ingredientes = {}
for linha in linhas[2:]:
# Limpar e extrair dados
linha = linha.replace("- ", "")
partes = linha.split(": ")
if len(partes) == 2:
ingrediente = partes[0].lower()
quantidade = partes[1]
ingredientes[ingrediente] = quantidade
return {
"nome": nome,
"tipo": tipo,
"ingredientes": ingredientes
}
Métodos de string usados:
strip(): Remove espaços/quebras de linha do início e fimsplit('\n'): Divide string em lista, separando por quebra de linhareplace("antigo", "novo"): Substitui textolower(): Converte para minúsculas
Filtrando Receitas por Ingredientes
Agora a parte interessante: dado o que você tem em casa, quais receitas pode fazer? Aqui usamos sets (que vimos brevemente na Parte I) para comparar ingredientes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def filtrar_receitas(receitas, ingredientes_usuario, tipo_desejado):
"""
Encontra receitas que combinam com os ingredientes disponíveis.
Args:
receitas: lista de dicionários de receitas
ingredientes_usuario: dicionário com ingredientes disponíveis
tipo_desejado: "Doce", "Salgado", etc.
Returns:
Lista de receitas possíveis
"""
receitas_possiveis = []
for receita in receitas:
# Primeiro, filtrar por tipo
if tipo_desejado.lower() != receita["tipo"].lower():
continue # Pula para próxima receita
# Converter para sets para usar operação de interseção
ingredientes_receita = set(receita["ingredientes"].keys())
ingredientes_disponiveis = set(ingredientes_usuario.keys())
# Se tem pelo menos um ingrediente em comum
if ingredientes_receita & ingredientes_disponiveis: # Interseção
receitas_possiveis.append(receita)
return receitas_possiveis
Por que sets aqui? A operação & (interseção) é muito mais eficiente que comparar elemento por elemento com loops.
O Sistema Completo
Juntando tudo num programa interativo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import time
def main():
"""Sistema de busca de receitas"""
print("=" * 50)
print("SISTEMA DE RECEITAS - UFRJ COMP 2020")
print("=" * 50)
# Carregar receitas do arquivo
receitas = carregar_todas_receitas('receitas.txt')
# Coletar ingredientes do usuário
print("\nDigite os ingredientes que você tem.")
print("Digite 'sair' para finalizar.\n")
meus_ingredientes = {}
while True:
item = input("Ingrediente: ").lower()
if item == 'sair':
break
quantidade = input("Quantidade: ")
meus_ingredientes[item] = quantidade
# Perguntar tipo de receita
tipo = input("\nQue tipo de receita quer? (Doce/Salgado/Salada): ")
# Buscar receitas
print("\nBuscando receitas...")
time.sleep(1)
possiveis = filtrar_receitas(receitas, meus_ingredientes, tipo)
if possiveis:
print(f"\nEncontrei {len(possiveis)} receita(s):")
for r in possiveis:
print(f" - {r['nome']}")
else:
print("\nNenhuma receita encontrada com esses ingredientes.")
if __name__ == "__main__":
main()
Módulo time: Usamos time.sleep(1) para pausar 1 segundo - dá aquela sensação de “processando” pro usuário.
Projeto Extra: Campo Minado (Minesweeper)
Durante o curso também desenvolvi um jogo de Campo Minado para praticar matrizes e lógica de jogos. Era uma opção de trabalho final junto do projeto de receitas. Achei um ótimo exercício para consolidar conceitos!
A Estrutura do Jogo
O Campo Minado usa uma matriz 9x9. Cada célula pode ter:
- Uma bomba (
'*') - Um número (quantidade de bombas vizinhas)
- Vazio (zero bombas ao redor)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import random
def criar_tabuleiro_vazio():
"""Cria matriz 9x9 preenchida com traços"""
return [['-' for _ in range(9)] for _ in range(9)]
def criar_tabuleiro_com_bombas():
"""Cria matriz com 10 bombas posicionadas aleatoriamente"""
tabuleiro = [[0 for _ in range(9)] for _ in range(9)]
bombas_colocadas = 0
while bombas_colocadas < 10:
linha = random.randint(0, 8)
coluna = random.randint(0, 8)
# Não colocar bomba onde já tem
if tabuleiro[linha][coluna] != '*':
tabuleiro[linha][coluna] = '*'
bombas_colocadas += 1
return tabuleiro
Observe: Usamos while em vez de for porque precisamos garantir exatamente 10 bombas, mesmo se sortear uma posição repetida.
Calculando Números das Células
Cada célula sem bomba mostra quantas bombas tem ao redor (incluindo diagonais):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def calcular_vizinhos(tabuleiro, linha, coluna):
"""
Conta quantas bombas existem ao redor de uma célula.
Considera as 8 direções: cima, baixo, esquerda, direita e diagonais.
"""
if tabuleiro[linha][coluna] == '*':
return '*' # É bomba, não calcula
contador = 0
# Verificar todas as 8 direções
for delta_linha in [-1, 0, 1]:
for delta_coluna in [-1, 0, 1]:
if delta_linha == 0 and delta_coluna == 0:
continue # Pular a própria célula
nova_linha = linha + delta_linha
nova_coluna = coluna + delta_coluna
# Verificar se está dentro do tabuleiro
if 0 <= nova_linha < 9 and 0 <= nova_coluna < 9:
if tabuleiro[nova_linha][nova_coluna] == '*':
contador += 1
return contador
Conceito importante: Verificação de limites
A condição 0 <= nova_linha < 9 é uma forma elegante de Python para verificar se um valor está num intervalo. Evita erros de “index out of range” quando estamos nas bordas da matriz.
O Loop Principal do Jogo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def jogar():
"""Loop principal do jogo"""
# Tabuleiro que o jogador vê
visivel = criar_tabuleiro_vazio()
# Tabuleiro real com bombas e números
real = criar_tabuleiro_com_bombas()
real = preencher_numeros(real)
while True:
exibir_tabuleiro(visivel)
try:
linha = int(input("Linha (0-8): "))
coluna = int(input("Coluna (0-8): "))
except ValueError:
print("Digite números válidos!")
continue
# Verificar se é bomba
if real[linha][coluna] == '*':
print("\n💥 BOOM! Você perdeu!")
exibir_tabuleiro(real)
break
# Revelar célula
visivel[linha][coluna] = real[linha][coluna]
# Verificar vitória (implementação simplificada)
if verificar_vitoria(visivel, real):
print("\n🎉 Parabéns! Você venceu!")
break
def exibir_tabuleiro(tab):
"""Mostra o tabuleiro formatado"""
print("\n 0 1 2 3 4 5 6 7 8")
print(" " + "-" * 19)
for i, linha in enumerate(tab):
print(f" {i} | {' '.join(str(c) for c in linha)} |")
print(" " + "-" * 19)
enumerate(): Retorna índice e valor ao mesmo tempo - muito útil para saber “em que linha estou”. É melhor que usar range(len(lista)):
1
2
3
4
5
6
7
8
9
10
11
12
13
frutas = ["maçã", "banana", "laranja"]
# Forma chata:
for i in range(len(frutas)):
print(f"{i}: {frutas[i]}")
# Forma Pythônica:
for i, fruta in enumerate(frutas):
print(f"{i}: {fruta}")
# Començar de outro número:
for i, fruta in enumerate(frutas, start=1):
print(f"{i}: {fruta}") # 1, 2, 3 em vez de 0, 1, 2
Menu do Jogo
Todo jogo precisa de um menu:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def menu_principal():
"""Menu inicial do jogo"""
print("=" * 30)
print(" CAMPO MINADO")
print("=" * 30)
print("\n[0] Começar")
print("[1] Ajuda")
print("[2] Sair\n")
opcao = input("> ")
if opcao == '0':
jogar()
elif opcao == '1':
mostrar_ajuda()
elif opcao == '2':
print("Até mais!")
else:
print("Opção inválida!")
menu_principal()
Parte VI: PyckageTools - Redes e Segurança
Trabalho Final (Prova): PyckageTools
O verdadeiro trabalho final da disciplina - a prova em si - foi um projeto mais ambicioso que chamei de PyckageTools. Enquanto os labs focavam em conceitos isolados, aqui eu quis ir além do que foi ensinado em aula, explorando conceitos de redes e segurança da informação que estava aprendendo em cursos paralelos (como o da Desec).
Este projeto é importante não só pelo conteúdo técnico, mas por mostrar como Python permite que você combine conhecimentos de diferentes áreas rapidamente. Você não precisa entender 100% de redes para fazer coisas úteis!
O Conceito do PyckageTools
O PyckageTools é uma “caixa de ferramentas” de segurança que reúne várias funcionalidades:
- DNS Resolver: Descobrir o IP de um site
- Port Scanner: Verificar quais portas estão abertas
- Gerenciador de Senhas: Verificar força e gerar senhas seguras
- FTP Brute Force: Ataque de força bruta (para fins educacionais!)
Vou explicar cada conceito novo que aparece aqui.
Sockets: A Base da Comunicação em Rede
O que é um socket?
Até agora, nossos programas funcionavam sozinhos - recebiam dados do teclado e mostravam na tela. Mas e se quisermos que dois computadores “conversem”? É aí que entram os sockets.
Pense em um socket como uma “tomada de comunicação” entre computadores. Quando você acessa um site, seu navegador cria um socket para “conversar” com o servidor do site. Cada comunicação na internet usa sockets por baixo dos panos.
Analogia do telefone: Criar um socket é como pegar o telefone. Você ainda não ligou pra ninguém, mas está pronto pra fazer a ligação.
Criando um socket em Python:
1
2
3
4
import socket
# Criar um socket
conexao = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Entendendo os parâmetros:
socket.AF_INET: Indica que vamos usar endereços IPv4 (como192.168.1.1)socket.SOCK_STREAM: Indica protocolo TCP (conexão confiável, com confirmação)
Analogia expandida:
AF_INETé escolher usar telefone (e não carta ou pombo-correio)SOCK_STREAMé escolher ligação comum (onde você sabe que a pessoa recebeu) vs SMS (onde pode perder)
Descobrindo o IP de um Site (DNS Resolver)
DNS (Domain Name System) é como uma “lista telefônica” da internet. Você sabe o nome do site (google.com), mas o computador precisa do “número de telefone” (IP).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import socket
def descobrir_ip(site):
"""
Descobre o endereço IP de um site.
É como buscar um número na lista telefônica.
"""
try:
ip = socket.gethostbyname(site)
print(f'O IP de {site} é: {ip}')
return ip
except socket.gaierror:
print(f'Erro: não foi possível resolver {site}')
return None
# Testando:
descobrir_ip("google.com") # Algo como: 142.250.79.46
descobrir_ip("github.com") # Algo como: 140.82.121.3
descobrir_ip("site-inexistente-xyz.com") # Erro!
Conceito importante: try-except aqui é essencial! Se o site não existir ou houver problema de conexão, sem o tratamento de erro o programa simplesmente quebraria.
Scan de Portas: Verificando Serviços
O que são portas?
Se o IP é como o endereço de um prédio, a porta é o número do apartamento. Cada serviço na internet usa uma porta específica:
| Porta | Serviço | Para que serve |
|---|---|---|
| 80 | HTTP | Sites sem criptografia |
| 443 | HTTPS | Sites seguros (com cadeado) |
| 21 | FTP | Transferência de arquivos |
| 22 | SSH | Acesso remoto seguro |
| 53 | DNS | Resolução de nomes |
| 3306 | MySQL | Banco de dados |
| 3389 | RDP | Área de trabalho remota |
Por que verificar portas?
Em segurança da informação, saber quais portas estão abertas ajuda a entender quais serviços um servidor oferece - e potenciais vulnerabilidades.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import socket
def scan_portas(ip, portas=[80, 443, 21, 22, 53, 8080, 3306, 3389]):
"""
Verifica quais portas estão abertas em um IP.
Args:
ip: Endereço IP ou nome do site
portas: Lista de portas para verificar
"""
print(f"Escaneando {ip}...")
print("Isso pode levar alguns segundos...\n")
for porta in portas:
# Criar nova conexão para cada porta
conexao = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Timeout de 1 segundo para não travar
conexao.settimeout(1)
# connect_ex retorna 0 se conectou (porta aberta)
resultado = conexao.connect_ex((ip, porta))
if resultado == 0:
print(f"Porta {porta}: ** ABERTA **")
else:
print(f"Porta {porta}: fechada")
conexao.close()
# Testar:
# scan_portas("google.com") # Vai mostrar 80 e 443 abertas
Explicando connect_ex():
- Diferente de
connect(), não levanta exceção em caso de erro - Retorna 0 se conexão bem sucedida (porta aberta)
- Retorna código de erro se falhou (porta fechada ou filtrada)
settimeout(1) é importante: sem isso, o programa pode travar por muito tempo esperando uma porta que não responde.
Gerenciando Senhas de Forma Segura
O projeto também inclui funções para verificar e gerar senhas seguras:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import string
import random
def verificar_senha_forte(senha):
"""
Verifica se uma senha atende critérios de segurança.
Retorna True se forte, False se fraca.
"""
# Critério 1: Mínimo de 8 caracteres
if len(senha) < 8:
print("❌ Adicione mais caracteres (mínimo 8)")
return False
# Critério 2: Ter números
if not any(char.isdigit() for char in senha):
print("❌ Adicione números")
return False
# Critério 3: Ter letras
if not any(char.isalpha() for char in senha):
print("❌ Adicione letras")
return False
# Critério 4: Ter maiúsculas
if not any(char.isupper() for char in senha):
print("❌ Adicione letras maiúsculas")
return False
# Critério 5: Ter minúsculas
if not any(char.islower() for char in senha):
print("❌ Adicione letras minúsculas")
return False
# Critério 6: Ter caractere especial
if not any(char in string.punctuation for char in senha):
print("❌ Adicione caractere especial (!@#$%...)")
return False
print("✅ Senha forte!")
return True
Métodos úteis para strings:
char.isdigit(): É um número?char.isalpha(): É uma letra?char.isupper(): É maiúscula?char.islower(): É minúscula?string.punctuation: String com todos caracteres especiais
A função any() - super útil!
any(condição for item in lista) retorna True se pelo menos um item atender a condição. É como perguntar: “Algum desses atende?”
1
2
3
4
5
6
7
8
9
# Sem any():
tem_numero = False
for char in senha:
if char.isdigit():
tem_numero = True
break
# Com any() (mais Pythônico):
tem_numero = any(char.isdigit() for char in senha)
Gerando Senhas Seguras
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import random
import string
def gerar_senha(tamanho=16):
"""
Gera uma senha aleatória forte.
Args:
tamanho: Comprimento da senha (8-32 recomendado)
Returns:
String com a senha gerada
"""
if tamanho < 8:
print("Tamanho mínimo é 8 caracteres!")
return None
# Conjunto de caracteres possíveis
caracteres = (
string.ascii_lowercase + # a-z
string.ascii_uppercase + # A-Z
string.digits + # 0-9
string.punctuation # !@#$%...
)
# Gerar senha
senha = ''.join(random.choice(caracteres) for _ in range(tamanho))
return senha
# Testando:
print(gerar_senha(12)) # Algo como: kP9@mL#2nXq!
print(gerar_senha(16)) # Algo como: Hn5$vR&8mK2@pL9!
''.join(...) explicado:
join junta uma lista de strings em uma só:
1
2
3
4
letras = ['a', 'b', 'c']
resultado = ''.join(letras) # "abc"
resultado = '-'.join(letras) # "a-b-c"
resultado = ' '.join(letras) # "a b c"
O Menu Principal do PyckageTools
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import socket
import random
import string
import time
def menu_principal():
"""Menu do programa PyckageTools"""
print("=" * 30)
#.....................................#
print("\n MENU\n")
print(" (a) DNS Resolver")
print(" (b) Port Scanner")
print(" (c) Password Manager")
print(" (d) FTP Brute Force")
print(" (0) Sair\n")
return input("> ").lower()
def main():
"""Loop principal"""
while True:
opcao = menu_principal()
if opcao == '0':
print("Encerrando...")
break
elif opcao == 'a':
site = input("Site para resolver: ")
descobrir_ip(site)
elif opcao == 'b':
ip = input("IP para escanear: ")
scan_portas(ip)
elif opcao == 'c':
submenu_senha()
elif opcao == 'd':
print("⚠️ Use apenas em sistemas autorizados!")
# bruteforce_ftp() - para fins educacionais
else:
print("Opção inválida!")
input("\nPressione Enter para continuar...")
if __name__ == "__main__":
main()
Conceitos Importantes do Pyckage
1. Módulo re para Expressões Regulares
Expressões regulares (regex) são padrões para buscar texto:
1
2
3
4
5
6
7
import re
texto = "Status: 230 Login successful"
# Buscar se "230" aparece no texto
if re.search("230", texto):
print("Login foi bem sucedido!")
2. Encoding em Sockets
Ao enviar dados por socket, precisamos converter para bytes:
1
2
3
4
5
6
7
# Enviar string por socket
mensagem = "Hello"
socket.send(mensagem.encode('utf-8'))
# Receber dados (vem em bytes)
dados = socket.recv(1024)
texto = dados.decode('utf-8')
3. Constantes de string
O módulo string tem constantes úteis:
1
2
3
4
5
6
import string
print(string.ascii_lowercase) # abcdefghijklmnopqrstuvwxyz
print(string.ascii_uppercase) # ABCDEFGHIJKLMNOPQRSTUVWXYZ
print(string.digits) # 0123456789
print(string.punctuation) # !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
Aviso Importante: Ética em Segurança
O PyckageTools foi criado para fins educacionais. Técnicas como scan de portas e brute force:
- São legais em sistemas que você tem autorização
- São ILEGAIS em sistemas de terceiros sem permissão
Na área de segurança, usamos essas técnicas para:
- Testar nossos próprios sistemas
- Participar de CTFs (Capture The Flag)
- Trabalhar como pentester (com contrato!)
Sempre aja de forma ética e dentro da lei! 🔐
Conceitos Avançados dos Projetos
Cópia Profunda vs Cópia Rasa
Este é um erro clássico ao trabalhar com listas de listas (matrizes). Veja o problema:
1
2
3
4
5
6
# PROBLEMA - cópia rasa
matriz_original = [[1, 2], [3, 4]]
copia = matriz_original.copy() # Parece que copiou...
copia[0][0] = 999 # Modificando a cópia
print(matriz_original) # [[999, 2], [3, 4]] - ORIGINAL MUDOU!
Por que isso acontece? O .copy() copia a lista externa, mas as listas internas continuam sendo as mesmas. É como fazer cópia de uma lista de endereços - você tem duas listas de papel, mas ambas apontam para as mesmas casas.
Visualizando:
1
2
3
4
5
original → [ ponteiro1, ponteiro2 ]
↓ ↓
copia → [ ponteiro1, ponteiro2 ] ← MESMOS ponteiros!
↓ ↓
[1, 2] [3, 4]
A solução - cópia profunda:
1
2
3
4
5
6
7
import copy
matriz_original = [[1, 2], [3, 4]]
copia = copy.deepcopy(matriz_original) # Cópia REAL
copia[0][0] = 999
print(matriz_original) # [[1, 2], [3, 4]] - original intacto!
O deepcopy cria cópias de tudo, incluindo as listas internas. É como construir casas novas idênticas em vez de copiar só os endereços.
Módulo time para Controle de Fluxo
1
2
3
4
5
6
7
8
9
import time
def loading_animado():
"""Mostra animação de carregamento"""
print("Carregando", end="")
for _ in range(5):
print(".", end="", flush=True)
time.sleep(0.5)
print(" Pronto!")
flush=True: Força o Python a mostrar o texto imediatamente em vez de esperar a linha terminar.
Parte VII: Programação Avançada
Até agora cobrimos o que considero o “essencial” de Python - o que você precisa saber pra resolver a maioria dos problemas. Mas Python tem muito mais a oferecer! Nesta parte vou apresentar conceitos mais avançados que, confesso, não vi muito nas aulas da UFRJ, mas fui aprendendo depois em projetos pessoais e cursos online.
Esses conceitos não são obrigatórios pra começar a programar, mas quando você pega o jeito, eles tornam seu código muito mais elegante e poderoso. É tipo a diferença entre saber dirigir e saber fazer manobras mais avançadas - você não precisa pra ir ao mercado, mas faz diferença em situações mais complexas.
Orientação a Objetos (POO)
O Que É POO e Por Que Importa?
Até agora, programamos de forma “procedural” - escrevemos funções que recebem dados, processam e devolvem resultados. Funciona bem pra muita coisa! Mas conforme os projetos crescem, fica difícil organizar tudo.
Orientação a Objetos é outra forma de organizar código. A ideia é agrupar dados e comportamentos relacionados em uma única estrutura chamada classe.
Analogia do carro: Imagine que você está descrevendo um carro:
- Dados (atributos): cor, modelo, ano, velocidade atual
- Comportamentos (métodos): acelerar, frear, buzinar
Em POO, criamos um “molde” de carro (classe) que define essas características. Depois, criamos carros específicos (objetos) a partir desse molde.
Classes e Objetos
Classe é o molde, a “receita”. Objeto é a coisa criada a partir do molde.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# Definindo uma classe (o molde)
class Carro:
"""Representa um carro com suas características"""
def __init__(self, modelo, cor, ano):
"""
Método especial que roda quando criamos um objeto.
'self' é uma referência ao próprio objeto.
"""
self.modelo = modelo # Atributo
self.cor = cor # Atributo
self.ano = ano # Atributo
self.velocidade = 0 # Começa parado
def acelerar(self, quantidade):
"""Método que aumenta a velocidade"""
self.velocidade += quantidade
print(f"{self.modelo} acelerou para {self.velocidade} km/h")
def frear(self):
"""Método que zera a velocidade"""
self.velocidade = 0
print(f"{self.modelo} parou")
def buzinar(self):
"""Método simples"""
print("BIIIII!")
# Criando objetos (carros específicos)
meu_carro = Carro("Fusca", "azul", 1970)
carro_do_vizinho = Carro("Civic", "preto", 2020)
# Usando os objetos
print(meu_carro.modelo) # "Fusca"
print(meu_carro.cor) # "azul"
meu_carro.acelerar(50) # "Fusca acelerou para 50 km/h"
meu_carro.acelerar(30) # "Fusca acelerou para 80 km/h"
meu_carro.buzinar() # "BIIIII!"
meu_carro.frear() # "Fusca parou"
Entendendo o self:
Essa parte pode confundir muita gente, mas basta pensar no sentido do inglês. O self é como o objeto se refere a si mesmo. Quando você chama meu_carro.acelerar(50), por baixo dos panos o Python faz Carro.acelerar(meu_carro, 50). O self é automaticamente preenchido com o objeto que chamou o método.
Lembro de anotar: “self = ‘eu mesmo’. Quando o Fusca acelera, ele muda a velocidade DELE, não de todos os carros.”
O Método __init__
O __init__ é o construtor - roda automaticamente quando você cria um objeto. É onde você define os valores iniciais dos atributos.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Aluno:
def __init__(self, nome, matricula):
self.nome = nome
self.matricula = matricula
self.notas = [] # Lista vazia pra guardar notas
self.faltas = 0
def adicionar_nota(self, nota):
self.notas.append(nota)
def calcular_media(self):
if not self.notas:
return 0
return sum(self.notas) / len(self.notas)
def situacao(self):
media = self.calcular_media()
if media >= 7 and self.faltas <= 10:
return "Aprovado"
elif media >= 5:
return "Recuperação"
else:
return "Reprovado"
# Usando:
joao = Aluno("João Silva", "2021001")
joao.adicionar_nota(8.5)
joao.adicionar_nota(7.0)
joao.adicionar_nota(9.0)
joao.faltas = 3
print(f"{joao.nome}: {joao.calcular_media():.1f} - {joao.situacao()}")
# João Silva: 8.2 - Aprovado
Perceba: Agora os dados do aluno (nome, notas, faltas) e as operações sobre ele (calcular média, ver situação) estão todos juntos. Isso facilita muito a organização quando você tem dezenas de alunos.
Herança: Reaproveitando Código
Herança é quando uma classe “herda” características de outra. É como dizer “um Cachorro É UM Animal, então tem tudo que animal tem, mais coisas específicas de cachorro”.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# Classe "pai" (ou base)
class Animal:
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
def fazer_som(self):
print("(som genérico)")
def apresentar(self):
print(f"Sou {self.nome}, tenho {self.idade} anos")
# Classe "filha" - herda de Animal
class Cachorro(Animal):
def __init__(self, nome, idade, raca):
# Chama o __init__ da classe pai
super().__init__(nome, idade)
self.raca = raca # Atributo específico de cachorro
def fazer_som(self):
# Sobrescreve o método do pai
print("Au au!")
def abanar_rabo(self):
# Método específico de cachorro
print(f"{self.nome} está abanando o rabo!")
class Gato(Animal):
def fazer_som(self):
print("Miau!")
def arranhar_sofa(self):
print(f"{self.nome} está destruindo o sofá...")
# Usando:
rex = Cachorro("Rex", 3, "Labrador")
mimi = Gato("Mimi", 5)
rex.apresentar() # "Sou Rex, tenho 3 anos" (herdou de Animal)
rex.fazer_som() # "Au au!" (sobrescreveu)
rex.abanar_rabo() # "Rex está abanando o rabo!" (específico)
mimi.apresentar() # "Sou Mimi, tenho 5 anos"
mimi.fazer_som() # "Miau!"
mimi.arranhar_sofa() # "Mimi está destruindo o sofá..."
O super(): Chama o método da classe pai. No __init__, usamos super().__init__(...) pra não precisar reescrever a inicialização que já existe na classe pai.
Encapsulamento: Escondendo Detalhes
Em Python, por convenção, atributos que começam com _ são “privados” - não deveriam ser acessados diretamente de fora da classe.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class ContaBancaria:
def __init__(self, titular, saldo_inicial):
self.titular = titular
self._saldo = saldo_inicial # _ indica "privado"
def depositar(self, valor):
if valor > 0:
self._saldo += valor
print(f"Depósito de R${valor}. Novo saldo: R${self._saldo}")
else:
print("Valor inválido!")
def sacar(self, valor):
if valor > self._saldo:
print("Saldo insuficiente!")
elif valor <= 0:
print("Valor inválido!")
else:
self._saldo -= valor
print(f"Saque de R${valor}. Novo saldo: R${self._saldo}")
def ver_saldo(self):
return self._saldo
# Usando:
conta = ContaBancaria("Matheus", 1000)
conta.depositar(500) # Depósito de R$500. Novo saldo: R$1500
conta.sacar(200) # Saque de R$200. Novo saldo: R$1300
print(conta.ver_saldo()) # 1300
# Tecnicamente dá pra acessar direto, mas não é recomendado:
# conta._saldo = 999999 # Funciona, mas quebra a "confiança" da classe
Por que isso importa? Se todo mundo acessa _saldo diretamente, você não consegue colocar validações. Usando métodos como depositar() e sacar(), você garante que as regras de negócio são respeitadas.
Métodos Especiais (Dunder Methods)
Python tem métodos “mágicos” que começam e terminam com __ (double underscore, ou “dunder”). Eles permitem que seus objetos se comportem como tipos nativos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Vetor:
"""Representa um vetor 2D"""
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
"""Chamado quando você faz print() ou str()"""
return f"Vetor({self.x}, {self.y})"
def __repr__(self):
"""Representação "oficial" do objeto"""
return f"Vetor({self.x}, {self.y})"
def __add__(self, outro):
"""Permite usar + entre vetores"""
return Vetor(self.x + outro.x, self.y + outro.y)
def __eq__(self, outro):
"""Permite usar == entre vetores"""
return self.x == outro.x and self.y == outro.y
def __len__(self):
"""Permite usar len() - retorna magnitude como int"""
return int((self.x**2 + self.y**2)**0.5)
# Usando:
v1 = Vetor(3, 4)
v2 = Vetor(1, 2)
print(v1) # Vetor(3, 4) - usa __str__
print(v1 + v2) # Vetor(4, 6) - usa __add__
print(v1 == v2) # False - usa __eq__
print(len(v1)) # 5 - usa __len__ (magnitude do vetor 3,4)
Alguns dunder methods úteis:
__str__: representação legível (pro usuário)__repr__: representação técnica (pro programador)__add__,__sub__,__mul__: operadores +, -, *__eq__,__lt__,__gt__: comparações ==, <, >__len__: retorna tamanho__getitem__: permite acessar com []
Decoradores
O Que São Decoradores?
Decoradores são uma forma de “embrulhar” funções com funcionalidades extras. Parece complicado, mas é super útil.
Analogia: Imagine que você tem um presente (a função). Um decorador é como colocar um papel de embrulho em volta - o presente continua sendo o mesmo, mas agora tem algo extra (o embrulho bonito).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Exemplo mais simples possível
def meu_decorador(funcao):
def funcao_embrulhada():
print("Antes de executar...")
funcao()
print("Depois de executar...")
return funcao_embrulhada
@meu_decorador
def dizer_ola():
print("Olá!")
# Quando chamamos dizer_ola(), na verdade executamos funcao_embrulhada()
dizer_ola()
# Saída:
# Antes de executar...
# Olá!
# Depois de executar...
O que aconteceu? O @meu_decorador é “açúcar sintático” para dizer_ola = meu_decorador(dizer_ola). A função original foi substituída pela versão “embrulhada”.
Decorador Prático: Medir Tempo de Execução
Um uso muito comum é medir quanto tempo uma função leva:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import time
def medir_tempo(funcao):
"""Decorador que mede tempo de execução"""
def wrapper(*args, **kwargs):
inicio = time.time()
resultado = funcao(*args, **kwargs)
fim = time.time()
print(f"{funcao.__name__} levou {fim - inicio:.4f} segundos")
return resultado
return wrapper
@medir_tempo
def funcao_lenta():
"""Simula uma função que demora"""
time.sleep(1)
return "Pronto!"
@medir_tempo
def soma_grande(n):
"""Soma números de 0 a n"""
return sum(range(n))
# Testando:
funcao_lenta() # funcao_lenta levou 1.0012 segundos
soma_grande(10000000) # soma_grande levou 0.2341 segundos
Entendendo *args e **kwargs:
Esses “nomes mágicos” permitem que uma função aceite qualquer quantidade de argumentos:
*args- captura argumentos posicionais extras como uma tupla**kwargs- captura argumentos nomeados extras como um dicionário
1
2
3
4
5
6
7
def funcao_flexivel(*args, **kwargs):
print(f"Args: {args}") # Tupla com argumentos posicionais
print(f"Kwargs: {kwargs}") # Dict com argumentos nomeados
funcao_flexivel(1, 2, 3, nome="Ana", idade=25)
# Args: (1, 2, 3)
# Kwargs: {'nome': 'Ana', 'idade': 25}
No wrapper do decorador, usamos *args, **kwargs pra repassar qualquer argumento que a função original receba, independente de quantos sejam.
Decoradores com Parâmetros
Às vezes você quer passar parâmetros pro decorador:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def repetir(vezes):
"""Decorador que repete a função N vezes"""
def decorador(funcao):
def wrapper(*args, **kwargs):
for _ in range(vezes):
resultado = funcao(*args, **kwargs)
return resultado
return wrapper
return decorador
@repetir(3)
def gritar(mensagem):
print(mensagem.upper())
gritar("socorro")
# SOCORRO
# SOCORRO
# SOCORRO
Confuso? É uma função que retorna um decorador que retorna uma função. Leva um tempo pra pegar, mas quando pega é muito útil!
Decoradores Embutidos
Python já vem com alguns decoradores úteis:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class MinhaClasse:
contador = 0 # Atributo de classe (compartilhado)
def __init__(self, valor):
self.valor = valor
MinhaClasse.contador += 1
@staticmethod
def metodo_estatico():
"""Não precisa de self nem cls - é só uma função dentro da classe"""
print("Sou um método estático!")
@classmethod
def metodo_de_classe(cls):
"""Recebe a classe (não a instância) como primeiro argumento"""
print(f"Total de instâncias: {cls.contador}")
@property
def valor_dobrado(self):
"""Permite acessar como atributo, mas é calculado"""
return self.valor * 2
# Usando:
obj = MinhaClasse(10)
obj2 = MinhaClasse(20)
MinhaClasse.metodo_estatico() # Sou um método estático!
MinhaClasse.metodo_de_classe() # Total de instâncias: 2
print(obj.valor_dobrado) # 20 (acessa como atributo, não como método)
Generators
O Problema com Listas Grandes
Imagine que você quer processar um arquivo com 10 milhões de linhas. Se você carregar tudo numa lista, sua memória vai explodir:
1
2
3
4
5
6
7
8
9
# PROBLEMA: carrega TUDO na memória
def ler_arquivo_errado(nome):
linhas = []
with open(nome) as f:
for linha in f:
linhas.append(linha)
return linhas # Lista gigante na memória!
# Se o arquivo tem 10GB... RIP sua RAM
Generators resolvem isso gerando um valor por vez, sem guardar tudo na memória.
Criando um Generator
Use yield em vez de return:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def contar_ate(n):
"""Generator que conta de 1 até n"""
i = 1
while i <= n:
yield i # "Pausa" aqui e retorna i
i += 1 # Continua quando pedir o próximo
# Usando:
contador = contar_ate(5)
print(next(contador)) # 1
print(next(contador)) # 2
print(next(contador)) # 3
# Ou num for (mais comum):
for num in contar_ate(5):
print(num) # 1, 2, 3, 4, 5
A diferença crucial: Uma função normal com return executa tudo de uma vez. Um generator com yield executa até o yield, “pausa”, e continua quando você pede o próximo valor.
Generator para Arquivos Grandes
1
2
3
4
5
6
7
8
9
10
11
def ler_arquivo_linha_a_linha(nome):
"""Generator que lê uma linha por vez"""
with open(nome) as f:
for linha in f:
yield linha.strip()
# Agora podemos processar arquivos ENORMES:
for linha in ler_arquivo_linha_a_linha("arquivo_gigante.txt"):
# Processa uma linha por vez
# A memória nunca tem mais que uma linha!
processar(linha)
Generator Expressions
Assim como temos list comprehensions, temos generator expressions:
1
2
3
4
5
6
7
8
9
10
# List comprehension - cria lista na memória
quadrados_lista = [x**2 for x in range(1000000)]
# Generator expression - gera sob demanda
quadrados_gen = (x**2 for x in range(1000000))
# A diferença:
import sys
print(sys.getsizeof(quadrados_lista)) # ~8 MB
print(sys.getsizeof(quadrados_gen)) # ~112 bytes (!!)
Quando usar?
- Lista: quando você precisa acessar elementos múltiplas vezes ou precisa do tamanho
- Generator: quando vai iterar uma vez só, especialmente com dados grandes
Async/Await: Programação Assíncrona
O Problema da Espera
Imagine um programa que faz várias requisições HTTP. Com código normal (síncrono):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Código SÍNCRONO - espera cada um terminar
import time
def buscar_dados(url):
print(f"Buscando {url}...")
time.sleep(2) # Simula espera de rede
print(f"Recebi dados de {url}")
return f"dados de {url}"
# Se chamarmos 3 vezes:
inicio = time.time()
buscar_dados("site1.com")
buscar_dados("site2.com")
buscar_dados("site3.com")
print(f"Total: {time.time() - inicio:.1f}s") # ~6 segundos!
O programa fica parado esperando cada requisição terminar. Mas enquanto espera a resposta de site1, poderia estar pedindo pro site2…
Código Assíncrono com asyncio
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import asyncio
import time
async def buscar_dados_async(url):
"""Função assíncrona - usa async def"""
print(f"Buscando {url}...")
await asyncio.sleep(2) # "Espera" sem bloquear
print(f"Recebi dados de {url}")
return f"dados de {url}"
async def main():
inicio = time.time()
# Executa as 3 requisições "ao mesmo tempo"
resultados = await asyncio.gather(
buscar_dados_async("site1.com"),
buscar_dados_async("site2.com"),
buscar_dados_async("site3.com")
)
fim = time.time()
print(f"Total: {fim - inicio:.1f}s") # ~2 segundos!
return resultados
# Executar:
asyncio.run(main())
Saída:
1
2
3
4
5
6
7
Buscando site1.com...
Buscando site2.com...
Buscando site3.com...
Recebi dados de site1.com
Recebi dados de site2.com
Recebi dados de site3.com
Total: 2.0s
O que aconteceu? As três requisições começaram quase juntas. Enquanto uma esperava, as outras também estavam esperando. Quando todas terminaram (~2s), continuou.
Entendendo async/await
async def: Define uma função assíncrona (coroutine)await: “Espera” algo assíncrono terminar, mas permite que outras coisas rodem enquanto issoasyncio.gather(): Executa várias coroutines “em paralelo”asyncio.run(): Inicia o loop de eventos e roda a função principal
Quando usar?
- Requisições HTTP (web scraping, APIs)
- Operações de I/O (arquivos, banco de dados)
- Qualquer coisa que envolva “esperar” respostas externas
Quando NÃO usar?
- Cálculos pesados (CPU-bound) - async não ajuda
- Scripts simples que não precisam de paralelismo
Exemplo Prático: Baixar Múltiplas Páginas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import asyncio
import aiohttp # pip install aiohttp
async def baixar_pagina(session, url):
"""Baixa uma página de forma assíncrona"""
try:
async with session.get(url) as response:
html = await response.text()
print(f"Baixei {url}: {len(html)} caracteres")
return html
except Exception as e:
print(f"Erro em {url}: {e}")
return None
async def baixar_varias(urls):
"""Baixa várias páginas ao mesmo tempo"""
async with aiohttp.ClientSession() as session:
tarefas = [baixar_pagina(session, url) for url in urls]
resultados = await asyncio.gather(*tarefas)
return resultados
# Usar:
urls = [
"https://python.org",
"https://github.com",
"https://stackoverflow.com"
]
# asyncio.run(baixar_varias(urls))
Testes Automatizados
Por Que Testar?
Confesso que durante o curso eu não dava muita bola pra testes. “Funciona no meu computador” era o suficiente. Mas depois de quebrar a cara em projetos maiores, entendi a importância.
Analogia: Testar é como verificar se as portas e janelas estão trancadas antes de dormir. Você pode não fazer, mas uma hora vai se arrepender.
Testes com assert (Básico)
A forma mais simples de testar:
1
2
3
4
5
6
7
8
9
10
11
12
def soma(a, b):
return a + b
def subtrair(a, b):
return a - b
# Testes simples com assert
assert soma(2, 3) == 5, "Erro na soma!"
assert soma(-1, 1) == 0, "Erro com números negativos!"
assert subtrair(10, 4) == 6, "Erro na subtração!"
print("Todos os testes passaram!")
Como funciona: assert condição, mensagem - se a condição for False, levanta um AssertionError com a mensagem.
unittest: Framework de Testes
Python vem com o módulo unittest pra testes mais organizados:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import unittest
# Código a ser testado
def calcular_media(notas):
if not notas:
return 0
return sum(notas) / len(notas)
def eh_aprovado(media):
return media >= 7
# Classe de testes
class TestCalculos(unittest.TestCase):
"""Testes para funções de cálculo"""
def test_media_normal(self):
"""Testa média com valores normais"""
self.assertEqual(calcular_media([7, 8, 9]), 8.0)
def test_media_lista_vazia(self):
"""Testa média com lista vazia"""
self.assertEqual(calcular_media([]), 0)
def test_media_um_elemento(self):
"""Testa média com um elemento"""
self.assertEqual(calcular_media([10]), 10)
def test_aprovado_verdadeiro(self):
"""Testa se 7+ é aprovado"""
self.assertTrue(eh_aprovado(7))
self.assertTrue(eh_aprovado(10))
def test_aprovado_falso(self):
"""Testa se <7 é reprovado"""
self.assertFalse(eh_aprovado(6.9))
self.assertFalse(eh_aprovado(0))
# Executar testes
if __name__ == "__main__":
unittest.main()
Métodos úteis do unittest:
assertEqual(a, b): a == bassertNotEqual(a, b): a != bassertTrue(x): x é TrueassertFalse(x): x é FalseassertIn(a, b): a está em bassertRaises(Erro): verifica se levanta exceção
pytest: Testes Mais Simples
O pytest é uma alternativa mais moderna e com menos “cerimônia”:
1
pip install pytest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# test_calculadora.py
def soma(a, b):
return a + b
def dividir(a, b):
if b == 0:
raise ValueError("Não pode dividir por zero!")
return a / b
# Testes - funções que começam com test_
def test_soma_positivos():
assert soma(2, 3) == 5
def test_soma_negativos():
assert soma(-1, -1) == -2
def test_soma_zero():
assert soma(0, 0) == 0
def test_dividir_normal():
assert dividir(10, 2) == 5
def test_dividir_por_zero():
import pytest
with pytest.raises(ValueError):
dividir(10, 0)
Pra rodar, basta executar pytest no terminal:
1
pytest test_calculadora.py -v
Saída:
1
2
3
4
5
6
7
test_calculadora.py::test_soma_positivos PASSED
test_calculadora.py::test_soma_negativos PASSED
test_calculadora.py::test_soma_zero PASSED
test_calculadora.py::test_dividir_normal PASSED
test_calculadora.py::test_dividir_por_zero PASSED
5 passed in 0.02s
Fixtures: Preparando o Ambiente de Teste
Às vezes você precisa preparar dados antes dos testes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pytest
@pytest.fixture
def lista_alunos():
"""Fixture que cria dados de teste"""
return [
{"nome": "Ana", "nota": 8.5},
{"nome": "Bruno", "nota": 6.0},
{"nome": "Carla", "nota": 9.0}
]
@pytest.fixture
def aluno_vazio():
return {"nome": "", "nota": 0}
def test_quantidade_alunos(lista_alunos):
"""Usa a fixture lista_alunos"""
assert len(lista_alunos) == 3
def test_melhor_nota(lista_alunos):
notas = [a["nota"] for a in lista_alunos]
assert max(notas) == 9.0
def test_aluno_invalido(aluno_vazio):
assert aluno_vazio["nome"] == ""
Por que fixtures? Evita repetir código de preparação em cada teste. E se precisar mudar os dados de teste, muda só na fixture.
TDD: Test-Driven Development
Uma prática interessante é escrever os testes ANTES do código:
- Escreva um teste que falha
- Escreva o código mínimo pra passar
- Refatore se necessário
- Repita
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. Primeiro, o teste (vai falhar porque a função não existe)
def test_validar_cpf():
assert validar_cpf("123.456.789-09") == True
assert validar_cpf("111.111.111-11") == False # CPFs repetidos são inválidos
assert validar_cpf("abc") == False
# 2. Depois, implementa a função pra passar nos testes
def validar_cpf(cpf):
# Remove pontuação
cpf = cpf.replace(".", "").replace("-", "")
# Verifica tamanho
if len(cpf) != 11:
return False
# Verifica se todos são iguais
if cpf == cpf[0] * 11:
return False
# (implementação completa teria mais validações...)
return True
Vantagem do TDD: Você pensa no que a função DEVE fazer antes de como ela faz. Isso evita implementar coisas desnecessárias.
Parte VIII: Dicas e Armadilhas Comuns
Depois de passar por todos os labs e projetos, coletei os erros mais comuns que eu (e outros estudantes) cometemos. Aprender a identificar esses problemas economiza horas de debug!
A maioria desses erros não é “burrice” - são armadilhas da linguagem que pegam até programadores experientes. O importante é reconhecer rápido quando você caiu em uma.
Como Ler Mensagens de Erro
Quando Python dá erro, ele mostra uma mensagem. Não entre em pânico! Veja como ler:
1
2
3
4
Traceback (most recent call last):
File "meu_programa.py", line 5, in <module>
print(idade + 1)
TypeError: can only concatenate str (not "int") to str
Traduzindo:
File "meu_programa.py", line 5= O erro está na linha 5 do arquivoprint(idade + 1)= Esta é a linha com problemaTypeError= Tipo de erro (problema com tipos de dados)can only concatenate str...= Explicção: tentou somar string com número
Dica: A última linha sempre explica o que deu errado. Leia ela primeiro!
A Armadilha do Input: Sempre String!
Um dos erros mais frequentes de iniciantes:
1
2
3
4
5
6
7
# ERRO CLÁSSICO:
idade = input("Sua idade: ") # usuário digita 25
proxima_idade = idade + 1 # ERRO! Não pode somar string com int
# CORRETO:
idade = int(input("Sua idade: ")) # Converte para int
proxima_idade = idade + 1 # Agora funciona!
Lembro que nas minhas primeiras anotações eu escrevi: “em input o valor, mesmo como número, vira uma string. Então temos que usar o int”. Essa pegadinha é universal!
Concatenação vs Soma
1
2
3
4
5
6
7
8
# Soma de números
print(7 + 4) # 11
# "Soma" de strings (concatenação)
print('7' + '4') # '74' - não é onze!
# Misturar dá erro:
print('7' + 4) # TypeError!
Cuidado com Índices!
Python começa a contar do ZERO:
1
2
3
4
5
6
lista = ['a', 'b', 'c', 'd']
# 0 1 2 3
print(lista[1]) # 'b' (não 'a'!)
print(lista[4]) # ERRO! IndexError
print(lista[-1]) # 'd' (último elemento)
Modificando Lista Dentro de Loop
Este é um erro traiçoeiro que causa bugs difíceis de encontrar:
1
2
3
4
5
6
7
8
# PROBLEMA - remover elementos enquanto itera
numeros = [1, 2, 3, 4, 5, 6]
for n in numeros:
if n % 2 == 0: # Se for par, remove
numeros.remove(n)
print(numeros) # Esperado: [1, 3, 5]
# Resultado: [1, 3, 5] ou [1, 3, 4, 5]?!
Por que dá errado? Quando você remove um elemento, os índices mudam! O loop pode pular elementos.
Soluções corretas:
1
2
3
4
5
6
7
8
9
10
11
# Solução 1: Criar nova lista (mais seguro)
numeros = [1, 2, 3, 4, 5, 6]
impares = [n for n in numeros if n % 2 != 0]
print(impares) # [1, 3, 5]
# Solução 2: Iterar sobre cópia
numeros = [1, 2, 3, 4, 5, 6]
for n in numeros.copy(): # .copy() cria cópia
if n % 2 == 0:
numeros.remove(n)
print(numeros) # [1, 3, 5]
Regra de ouro: Nunca modifique uma lista enquanto itera sobre ela. Crie uma nova ou itere sobre cópia.
Comparação vs Atribuição
1
2
3
4
5
6
x = 5 # Atribuição: x RECEBE 5
x == 5 # Comparação: x é IGUAL a 5?
# Erro comum em condicionais:
if x = 5: # ERRO de sintaxe! Use ==
if x == 5: # Correto
Escopo de Variáveis
Escopo é “onde a variável existe”. Variáveis criadas dentro de funções só existem lá dentro:
1
2
3
4
5
6
def calcular():
resultado = 42 # Variável criada DENTRO da função
return resultado
calcular()
print(resultado) # ERRO! 'resultado' não existe aqui fora
Solução: Guarde o retorno da função:
1
2
meu_resultado = calcular() # Guarda o 42 retornado
print(meu_resultado) # Funciona! Imprime 42
Indentação é Sintaxe!
Em Python, espaços errados quebram o código:
1
2
3
4
5
6
7
# ERRADO
if True:
print("Olá") # IndentationError!
# CORRETO
if True:
print("Olá")
Dica: Configure seu editor para usar 4 espaços por nível de indentação. Nunca misture tabs e espaços!
Return Interrompe a Função
1
2
3
4
5
6
def exemplo():
print("Antes")
return 10
print("Depois") # Nunca executa!
resultado = exemplo() # Imprime "Antes", resultado = 10
Cuidado com Mutabilidade
Listas são mutáveis - cuidado ao passar para funções:
1
2
3
4
5
6
def adicionar_item(lista):
lista.append("novo") # Modifica a lista ORIGINAL!
minha_lista = [1, 2, 3]
adicionar_item(minha_lista)
print(minha_lista) # [1, 2, 3, "novo"] - foi modificada!
O Problema do else no Loop
else em loops roda quando o loop termina SEM break:
1
2
3
4
5
6
7
8
9
10
11
for i in range(5):
if i == 3:
break
else:
print("Completou!") # Não imprime (teve break)
for i in range(5):
if i == 10: # Nunca vai ser verdade
break
else:
print("Completou!") # Imprime (não teve break)
O Famoso if __name__ == "__main__"
Esse é um dos trechos mais misteriosos pra quem tá começando. Você vê em quase todo código Python, mas o que diabos isso faz?
O Problema:
Imagine que você criou um arquivo utilidades.py com funções úteis:
1
2
3
4
5
6
7
8
9
10
# utilidades.py
def somar(a, b):
return a + b
def multiplicar(a, b):
return a * b
# Testando as funções:
print(somar(2, 3)) # 5
print(multiplicar(4, 5)) # 20
Agora você quer usar essas funções em outro arquivo:
1
2
3
4
5
# meu_programa.py
from utilidades import somar
resultado = somar(10, 20)
print(resultado)
O que acontece? Quando você roda meu_programa.py, os print() de teste do utilidades.py também executam! Você só queria importar as funções, não rodar os testes.
A Solução:
1
2
3
4
5
6
7
8
9
10
11
12
13
# utilidades.py (versão corrigida)
def somar(a, b):
return a + b
def multiplicar(a, b):
return a * b
if __name__ == "__main__":
# Isso SÓ roda quando você executa utilidades.py diretamente
# Não roda quando alguém importa o arquivo
print("Testando as funções:")
print(somar(2, 3)) # 5
print(multiplicar(4, 5)) # 20
Como funciona? Python define uma variável especial __name__ em todo arquivo:
- Se você executa o arquivo diretamente →
__name__vale"__main__" - Se você importa o arquivo →
__name__vale o nome do arquivo (ex:"utilidades")
Padrão comum em projetos:
1
2
3
4
5
6
7
8
def main():
"""Função principal do programa"""
# Todo o código principal aqui
print("Programa iniciado!")
# ...
if __name__ == "__main__":
main()
Isso deixa o código organizado: uma função main() com a lógica principal, e ela só é chamada se o arquivo for executado diretamente.
Por que isso importa?
- Permite criar módulos reutilizáveis que também funcionam como scripts
- Evita que código de teste rode quando o arquivo é importado
- É o padrão da comunidade Python - outros programadores esperam esse comportamento
Funções Anônimas com Lambda
Às vezes você precisa de uma função bem simples, só pra usar uma vez. Criar um def completo parece exagero. É aí que entra o lambda:
1
2
3
4
5
6
7
8
9
# Função normal:
def dobrar(x):
return x * 2
# Mesma coisa com lambda:
dobrar = lambda x: x * 2
# Ambas funcionam igual:
print(dobrar(5)) # 10
Sintaxe: lambda argumentos: expressão
O lambda é uma função “de uma linha só”. Não tem nome próprio (por isso “anônima”), não tem return (o resultado da expressão é retornado automaticamente), e só pode ter uma expressão.
Onde lambda brilha? Em funções que recebem outras funções:
1
2
3
4
5
6
7
8
9
10
11
# Ordenar lista de tuplas pelo segundo elemento
alunos = [("Ana", 8.5), ("Bruno", 7.0), ("Carla", 9.0)]
# Sem lambda (precisa criar função):
def pegar_nota(aluno):
return aluno[1]
alunos_ordenados = sorted(alunos, key=pegar_nota)
# Com lambda (mais direto):
alunos_ordenados = sorted(alunos, key=lambda x: x[1])
print(alunos_ordenados) # [('Bruno', 7.0), ('Ana', 8.5), ('Carla', 9.0)]
Outro exemplo - filtrar números:
1
2
3
4
5
6
7
8
9
numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Filtrar só os pares:
pares = list(filter(lambda x: x % 2 == 0, numeros))
print(pares) # [2, 4, 6, 8, 10]
# Dobrar todos:
dobrados = list(map(lambda x: x * 2, numeros))
print(dobrados) # [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
map() e filter():
map(função, lista)- aplica a função em cada elementofilter(função, lista)- mantém só os elementos onde a função retorna True
Dica: Em Python moderno, muita gente prefere list comprehensions ao invés de map/filter:
1
2
3
4
5
6
# Equivalentes:
pares = list(filter(lambda x: x % 2 == 0, numeros))
pares = [x for x in numeros if x % 2 == 0] # Mais Pythônico
dobrados = list(map(lambda x: x * 2, numeros))
dobrados = [x * 2 for x in numeros] # Mais Pythônico
Mas lambda ainda é útil em sorted(), min(), max() com key, e em callbacks de bibliotecas.
Dicas das Minhas Anotações Antigas
Algumas pérolas que anotei quando estava aprendendo:
“Toda variável é um objeto. Um objeto é mais que uma variável.”
“O
=significa ‘recebe’, não ‘igual’. Para comparar, use==.”
“Sempre que tiver a divisão
/será em decimal. Para inteiro fazer com//.”
“Para funcionar bem e não ter que ficar repetindo (inviável) no modo interativo, é ideal criar isso em script. Salvar e rodar tudo junto.”
Conceitos Importantes Recapitulados
Tipos de Dados em Python
| Tipo | Exemplo | Descrição |
|---|---|---|
int | 42 | Número inteiro |
float | 3.14 | Número com decimal |
str | "texto" | Texto |
bool | True | Verdadeiro ou Falso |
list | [1, 2, 3] | Coleção ordenada |
tuple | (1, 2, 3) | Coleção imutável |
dict | {"a": 1} | Pares chave-valor |
Funções Embutidas Úteis
1
2
3
4
5
6
7
8
9
10
11
len(x) # Comprimento
max(x) # Máximo
min(x) # Mínimo
sum(x) # Soma
sorted(x) # Ordena
reversed(x) # Inverte
range(n) # Sequência de 0 a n-1
enumerate(x) # Índice + elemento
zip(a, b) # Combina duas listas
type(x) # Tipo de x
isinstance(x, int) # É x um int?
Importando Módulos
1
2
3
4
5
6
7
8
import math # Importa tudo
from math import pi # Importa só pi
import random as rnd # Apelido curto
# Usar:
print(math.pi)
print(pi)
print(rnd.randint(1, 10))
Módulos Importantes
math: Operações matemáticas avançadasrandom: Números aleatóriosdatetime: Datas e horasos: Sistema operacionalsys: Sistema Python
Diferenças com C
Por Que Python É “Mais Fácil”
| Problema em C | Solução em Python |
|---|---|
| Precisa compilar | Executa direto |
| Gerenciar memória manualmente | Automático |
printf() complexo | print() simples |
| Arrays de tamanho fixo | Listas dinâmicas |
| Tipos explícitos obrigatórios | Tipos deduzidos |
| Ponteiros confusos | Sem ponteiros |
O Que Python Não Tem (Comparado com C)
- Ponteiros: Python gerencia memória para você
- Compilação: Interpretado significa mais lento em performance pura
- Controle baixo nível: Não acessa memória diretamente
- Type casting explícito: Às vezes Python é “muito flexível”
Quando Usar Cada Uma
Python:
- Scripts simples
- Análise de dados
- Web (Django, Flask)
- Machine Learning
- Prototipagem rápida
C:
- Sistemas embarcados
- Kernels e sistemas operacionais
- Performance crítica
- Drivers
- Código que roda em qualquer lugar
Práticas Recomendadas
Escrevendo Código Limpo
- Use nomes significativos: ```python
Ruim
a = 10 x = 5 def f(p): return p * 3
Bom
preco_unitario = 10 quantidade = 5 def calcular_total(valor): return valor * 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2. **Documente com docstrings:**
```python
def calcular_media(notas):
"""
Calcula a média aritmética de um conjunto de notas.
Args:
notas: lista de números
Returns:
float: a média das notas
"""
return sum(notas) / len(notas)
- Divida em funções pequenas: ```python
Bom: cada função faz uma coisa
def validar_idade(idade): return idade >= 18
def validar_email(email): return “@” in email
def criar_usuario(nome, idade, email): if validar_idade(idade) and validar_email(email): return {“nome”: nome, “idade”: idade, “email”: email} return None
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
### Debug e Testes
```python
# Print simples para debug
print(f"Debug: variável x = {x}")
# Asserções para testes
assert 2 + 2 == 4, "Matemática está quebrada!"
assert len([1, 2, 3]) == 3, "Lista tem tamanho errado!"
# Try-except para robustez
try:
resultado = arriscado()
except Exception as e:
print(f"Erro capturado: {e}")
Conclusão
Esta jornada pela programação Python cobriu desde conceitos fundamentais até projetos práticos completos. Começamos com simples funções e chegamos a desenvolver um sistema de receitas, um jogo de Campo Minado e o PyckageTools, passando por:
- Fundamentos: Variáveis, tipos, funções, condicionais, loops, indentação
- Estruturas de Dados: Listas, dicionários, tuplas, sets, matrizes
- Programação Intermediária:
try-except, menus, validação, list comprehensions - Lógica e Algoritmos: Simulações, busca, filtragem, problemas clássicos
- Projetos Práticos: Leitura de arquivos, jogos, manipulação de texto
- Redes e Segurança: Sockets, DNS, port scanning, gerenciamento de senhas
Checklist do Iniciante
Se você consegue fazer tudo isso, pode se considerar com uma base sólida em Python:
- Criar variáveis e fazer cálculos
- Usar
input()eprint()para interagir com usuário - Criar funções com
defe usarreturn - Tomar decisões com
if,elif,else - Repetir código com
forewhile - Trabalhar com listas (criar, adicionar, acessar)
- Usar dicionários para dados estruturados
- Tratar erros com
try-except - Ler e entender mensagens de erro
Próximos Passos
Alguns recursos que me ajudaram bastante:
- Python Docs Oficial - A documentação oficial é surpreendentemente boa
- PEP 8 - Style Guide - Pra quando quiser escrever código “bonito”
- Real Python - Tutoriais muito bem explicados
Recados Finais
Lembro de ter anotado algumas coisas durante o curso que hoje fazem muito sentido:
“Programe todo dia, nem que seja 10 minutos. É melhor pouco todo dia do que muito de vez em quando.”
“Quando o código não funcionar, leia o erro. Sério. O Python quase sempre diz exatamente o que deu errado.”
“Copiar código do Stack Overflow não é vergonha, mas tenta entender o que tá copiando. Se não entender agora, uma hora vai precisar.”
Acho que a maior lição que tirei desse curso foi que programação não é sobre decorar comandos - é sobre aprender a pensar de forma lógica. A sintaxe você esquece e pesquisa, a lógica fica pra sempre.
Como sempre dizíamos nas aulas: “tamo junto” nessa jornada. Python é uma linguagem que cresce com você - os mesmos conceitos básicos que aprendemos aqui são usados em análise de dados, IA, desenvolvimento web e muito mais.
Bons códigos e continue sempre aprendendo! 🐍🚀
Agradecimentos:
Aos professores José Sapienza Ramos e Rodrigo Guerchon pela estruturação do curso na UFRJ. Ao pessoal da Desec Security pelos conceitos de segurança que usei no PyckageTools. E a todos que contribuem com a comunidade Python Brasil.
Repositório do Trabalho Final: PyckageTools-UFRJ
Última modificação: 17 de dezembro de 2025