Curso de Python: Básico ao Avançado
Módulo 1: Fundamentos do Python (Básico)
1.1 Variáveis, Tipos de Dados e Operadores
Python é uma linguagem de programação de alto nível, interpretada, orientada a
objetos e com tipagem dinâmica. Isso significa que você não precisa declarar o tipo de
uma variável antes de usá-la.
Variáveis
Variáveis são usadas para armazenar informações que podem ser referenciadas e
manipuladas em um programa. Em Python, você atribui um valor a uma variável
usando o operador de atribuição ( = ).
nome = "Alice"
idade = 30
preco = 25.50
is_ativo = True
print(nome) # Saída: Alice
print(idade) # Saída: 30
print(preco) # Saída: 25.5
print(is_ativo) # Saída: True
Tipos de Dados
Python possui vários tipos de dados embutidos:
Números:
int (inteiros): 10 , -5 , 0
float (ponto flutuante): 3.14 , -0.5 , 2.0
complex (números complexos): 1 + 2j
Sequências:
str (strings): "Olá, Mundo!" , 'Python'
list (listas): [1, 2, 3] , ['a', 'b', 'c']
tuple (tuplas): (1, 2, 3) , ('x', 'y')
Mapeamento:
dict (dicionários): {'chave': 'valor', 'nome': 'João'}
Conjuntos:
set (conjuntos): {1, 2, 3} , {'maçã', 'banana'}
Booleanos:
bool (booleanos): True , False
Você pode verificar o tipo de uma variável usando a função type() :
x = 10
y = 3.14
z = "Olá"
print(type(x)) # Saída: <class 'int'>
print(type(y)) # Saída: <class 'float'>
print(type(z)) # Saída: <class 'str'>
Operadores
Operadores são símbolos que realizam operações em valores e variáveis.
Operadores Aritméticos:
+ (Adição): 5 + 3 = 8
- (Subtração): 5 - 3 = 2
* (Multiplicação): 5 * 3 = 15
/ (Divisão): 5 / 3 = 1.666... (sempre retorna float)
// (Divisão inteira): 5 // 3 = 1
% (Módulo - resto da divisão): 5 % 3 = 2
** (Exponenciação): 5 ** 3 = 125
Operadores de Comparação:
== (Igual a): 5 == 3 = False
!= (Diferente de): 5 != 3 = True
> (Maior que): 5 > 3 = True
< (Menor que): 5 < 3 = False
>= (Maior ou igual a): 5 >= 3 = True
<= (Menor ou igual a): 5 <= 3 = False
Operadores Lógicos:
and (E lógico): True and False = False
or (Ou lógico): True or False = True
not (Negação lógica): not True = False
Operadores de Atribuição:
= (Atribuição simples): x = 10
+= (Adição e atribuição): x += 5 (equivalente a x = x + 5 )
-= (Subtração e atribuição): x -= 5
*= (Multiplicação e atribuição): x *= 5
/= (Divisão e atribuição): x /= 5
e assim por diante para outros operadores aritméticos.
a = 10
b = 3
print(f"Adição: {a + b}")
print(f"Subtração: {a - b}")
print(f"Multiplicação: {a * b}")
print(f"Divisão: {a / b}")
print(f"Divisão Inteira: {a // b}")
print(f"Módulo: {a % b}")
print(f"Exponenciação: {a ** b}")
print(f"Igual a: {a == b}")
print(f"Diferente de: {a != b}")
print(f"And lógico: {True and False}")
print(f"Or lógico: {True or False}")
print(f"Not lógico: {not True}")
c = 10
c += 5
print(f"Atribuição com adição: {c}") # Saída: 15
Exercícios
1. Crie duas variáveis, num1 e num2 , com valores inteiros. Realize e imprima o
resultado de todas as operações aritméticas básicas entre elas.
2. Crie uma variável frase com uma string. Verifique o tipo da variável e imprima-
o.
3. Crie duas variáveis booleanas, condicao1 e condicao2 . Use operadores lógicos
para imprimir o resultado de condicao1 and condicao2 , condicao1 or
condicao2 e not condicao1 .
1.2 Estruturas de Controle de Fluxo
As estruturas de controle de fluxo permitem que você controle a ordem em que as
instruções são executadas em um programa.
Condicionais ( if , elif , else )
Usadas para executar blocos de código diferentes com base em certas condições.
idade = 18
if idade >= 18:
print("Você é maior de idade.")
else:
print("Você é menor de idade.")
nota = 75
if nota >= 90:
print("Conceito A")
elif nota >= 70:
print("Conceito B")
elif nota >= 50:
print("Conceito C")
else:
print("Conceito D")
Laços de Repetição ( for , while )
Usados para executar um bloco de código repetidamente.
for loop
Itera sobre uma sequência (como uma lista, tupla, string ou range).
# Iterando sobre uma lista
frutas = ["maçã", "banana", "cereja"]
for fruta in frutas:
print(fruta)
# Iterando sobre uma string
for letra in "Python":
print(letra)
# Usando range()
for i in range(5): # Gera números de 0 a 4
print(i)
for i in range(2, 8): # Gera números de 2 a 7
print(i)
for i in range(1, 10, 2): # Gera números de 1 a 9, pulando de 2 em 2
print(i)
while loop
Executa um bloco de código enquanto uma condição for verdadeira.
contador = 0
while contador < 5:
print(contador)
contador += 1
# break e continue
for i in range(10):
if i == 3:
continue # Pula para a próxima iteração
if i == 7:
break # Sai do loop
print(i)
1.3 Funções e Módulos Básicos
Funções
Funções são blocos de código reutilizáveis que realizam uma tarefa específica. Elas
ajudam a organizar o código e torná-lo mais legível e modular.
def saudar(nome):
"""Esta função saúda a pessoa com o nome passado como argumento."""
print(f"Olá, {nome}!")
saudar("Maria")
saudar("Pedro")
def somar(a, b):
"""Esta função retorna a soma de dois números."""
return a + b
resultado = somar(10, 5)
print(f"A soma é: {resultado}")
# Argumentos padrão
def potencia(base, expoente=2):
return base ** expoente
print(potencia(3)) # Saída: 9 (3^2)
print(potencia(3, 3)) # Saída: 27 (3^3)
Módulos
Um módulo é um arquivo Python ( .py ) que contém definições e declarações Python.
Módulos permitem organizar o código em arquivos separados, tornando-o mais
gerenciável e reutilizável.
# Exemplo de uso do módulo math
import math
print([Link](25)) # Saída: 5.0
print([Link]) # Saída: 3.141592653589793
# Importando funções específicas de um módulo
from math import sqrt, pi
print(sqrt(16)) # Saída: 4.0
print(pi) # Saída: 3.141592653589793
# Renomeando um módulo ou função importada
import math as matematica
print([Link](5)) # Saída: 120
from math import sqrt as raiz_quadrada
print(raiz_quadrada(49)) # Saída: 7.0
Exercícios
1. Escreva um programa que peça ao usuário para digitar um número e determine
se ele é par ou ímpar.
2. Crie um loop for que imprima os números de 1 a 10. Em seguida, crie um loop
while que faça o mesmo.
3. Crie uma função chamada calcular_media que receba três notas como
argumentos e retorne a média. Teste a função com diferentes valores.
4. Importe o módulo random e use a função [Link]() para gerar um
número inteiro aleatório entre 1 e 100. Imprima o número gerado.
Módulo 2: Python Intermediário
2.1 Estruturas de Dados
Python oferece várias estruturas de dados embutidas que são muito eficientes e
flexíveis para organizar e armazenar dados.
Listas ( list )
Listas são coleções ordenadas e mutáveis de itens. Podem conter itens de diferentes
tipos de dados.
# Criando uma lista
minha_lista = [1, 2, 3, "quatro", 5.0]
print(minha_lista) # Saída: [1, 2, 3, 'quatro', 5.0]
# Acessando elementos (indexação)
print(minha_lista[0]) # Saída: 1
print(minha_lista[-1]) # Saída: 5.0 (último elemento)
# Fatiamento (slicing)
print(minha_lista[1:4]) # Saída: [2, 3, 'quatro']
# Modificando elementos
minha_lista[0] = 10
print(minha_lista) # Saída: [10, 2, 3, 'quatro', 5.0]
# Adicionando elementos
minha_lista.append("novo") # Adiciona ao final
print(minha_lista) # Saída: [10, 2, 3, 'quatro', 5.0, 'novo']
minha_lista.insert(1, "inserido") # Adiciona em uma posição específica
print(minha_lista) # Saída: [10, 'inserido', 2, 3, 'quatro', 5.0, 'novo']
# Removendo elementos
minha_lista.remove("quatro") # Remove o primeiro item com o valor especificado
print(minha_lista) # Saída: [10, 'inserido', 2, 3, 5.0, 'novo']
minha_lista.pop() # Remove e retorna o último elemento
print(minha_lista) # Saída: [10, 'inserido', 2, 3, 5.0]
minha_lista.pop(0) # Remove e retorna o elemento no índice especificado
print(minha_lista) # Saída: ['inserido', 2, 3, 5.0]
# Outros métodos úteis
lista_numeros = [1, 5, 2, 8, 3]
lista_numeros.sort() # Ordena a lista
print(lista_numeros) # Saída: [1, 2, 3, 5, 8]
lista_numeros.reverse() # Inverte a ordem da lista
print(lista_numeros) # Saída: [8, 5, 3, 2, 1]
print(len(lista_numeros)) # Saída: 5 (tamanho da lista)
Tuplas ( tuple )
Tuplas são coleções ordenadas e imutáveis de itens. Uma vez criadas, seus elementos
não podem ser alterados.
# Criando uma tupla
minha_tupla = (1, 2, "três", 4.0)
print(minha_tupla) # Saída: (1, 2, 'três', 4.0)
# Acessando elementos (indexação e fatiamento funcionam como em listas)
print(minha_tupla[0]) # Saída: 1
print(minha_tupla[1:3]) # Saída: (2, 'três')
# Tuplas são imutáveis (a linha abaixo geraria um erro)
# minha_tupla[0] = 10
# Desempacotamento de tuplas
a, b, c = (1, 2, 3)
print(a, b, c) # Saída: 1 2 3
Dicionários ( dict )
Dicionários são coleções não ordenadas de pares chave-valor. As chaves devem ser
únicas e imutáveis (strings, números, tuplas).
# Criando um dicionário
meu_dicionario = {"nome": "João", "idade": 30, "cidade": "São Paulo"}
print(meu_dicionario) # Saída: {'nome': 'João', 'idade': 30, 'cidade': 'São
Paulo'}
# Acessando valores
print(meu_dicionario["nome"]) # Saída: João
# Adicionando/Modificando pares chave-valor
meu_dicionario["profissao"] = "Engenheiro"
print(meu_dicionario) # Saída: {'nome': 'João', 'idade': 30, 'cidade': 'São
Paulo', 'profissao': 'Engenheiro'}
meu_dicionario["idade"] = 31
print(meu_dicionario) # Saída: {'nome': 'João', 'idade': 31, 'cidade': 'São
Paulo', 'profissao': 'Engenheiro'}
# Removendo pares chave-valor
meu_dicionario.pop("cidade")
print(meu_dicionario) # Saída: {'nome': 'João', 'idade': 31, 'profissao':
'Engenheiro'}
# Métodos úteis
print(meu_dicionario.keys()) # Saída: dict_keys(['nome', 'idade', 'profissao'])
print(meu_dicionario.values()) # Saída: dict_values(['João', 31, 'Engenheiro'])
print(meu_dicionario.items()) # Saída: dict_items([('nome', 'João'), ('idade',
31), ('profissao', 'Engenheiro')])
# Iterando sobre dicionários
for chave, valor in meu_dicionario.items():
print(f"{chave}: {valor}")
Conjuntos ( set )
Conjuntos são coleções não ordenadas de itens únicos. São úteis para remover
duplicatas e realizar operações de conjunto (união, interseção, diferença).
# Criando um conjunto
meu_conjunto = {1, 2, 3, 2, 1}
print(meu_conjunto) # Saída: {1, 2, 3} (duplicatas são removidas
automaticamente)
# Adicionando elementos
meu_conjunto.add(4)
print(meu_conjunto) # Saída: {1, 2, 3, 4}
# Removendo elementos
meu_conjunto.remove(2)
print(meu_conjunto) # Saída: {1, 3, 4}
# Operações de conjunto
conjunto_a = {1, 2, 3, 4}
conjunto_b = {3, 4, 5, 6}
print(conjunto_a.union(conjunto_b)) # União: {1, 2, 3, 4, 5, 6}
print(conjunto_a.intersection(conjunto_b)) # Interseção: {3, 4}
print(conjunto_a.difference(conjunto_b)) # Diferença: {1, 2}
Exercícios
1. Crie uma lista de 5 nomes de frutas. Adicione uma nova fruta, remova uma
existente e imprima a lista final.
2. Crie uma tupla com as coordenadas (latitude, longitude) de um local. Tente
modificar um dos valores e observe o erro.
3. Crie um dicionário para representar um livro, com chaves como "titulo", "autor" e
"ano". Adicione uma nova chave "genero" e imprima o dicionário completo.
4. Crie dois conjuntos de números. Encontre a união, interseção e diferença entre
eles e imprima os resultados.
2.2 Programação Orientada a Objetos (POO)
POO é um paradigma de programação que organiza o design do software em torno de
objetos, em vez de funções e lógica. Um objeto pode ser definido como uma instância
de uma classe.
Classes e Objetos
Classe: Um blueprint ou modelo para criar objetos. Define um conjunto de
atributos (variáveis) e métodos (funções) que os objetos criados a partir dela
terão.
Objeto: Uma instância de uma classe. É uma entidade real baseada no modelo
da classe.
class Carro:
# Atributo de classe (compartilhado por todas as instâncias)
rodas = 4
def __init__(self, marca, modelo, ano):
# Atributos de instância
[Link] = marca
[Link] = modelo
[Link] = ano
[Link] = 0
def acelerar(self, incremento):
[Link] += incremento
print(f"O {[Link]} está a {[Link]} km/h.")
def frear(self, decremento):
[Link] -= decremento
if [Link] < 0:
[Link] = 0
print(f"O {[Link]} está a {[Link]} km/h.")
# Criando objetos (instâncias da classe Carro)
carro1 = Carro("Toyota", "Corolla", 2020)
carro2 = Carro("Honda", "Civic", 2022)
print(f"Carro 1: {[Link]} {[Link]} ({[Link]})")
print(f"Carro 2: {[Link]} {[Link]} ({[Link]})")
[Link](50)
[Link](70)
[Link](20)
print(f"Rodas do carro1: {[Link]}")
print(f"Rodas do carro2: {[Link]}")
Herança
Herança permite que uma classe (subclasse/classe filha) herde atributos e métodos de
outra classe (superclasse/classe pai). Isso promove a reutilização de código.
class Veiculo:
def __init__(self, marca, modelo):
[Link] = marca
[Link] = modelo
def exibir_info(self):
print(f"Marca: {[Link]}, Modelo: {[Link]}")
class Carro(Veiculo):
def __init__(self, marca, modelo, portas):
super().__init__(marca, modelo) # Chama o construtor da classe pai
[Link] = portas
def exibir_info(self):
super().exibir_info()
print(f"Portas: {[Link]}")
class Moto(Veiculo):
def __init__(self, marca, modelo, cilindradas):
super().__init__(marca, modelo)
[Link] = cilindradas
def exibir_info(self):
super().exibir_info()
print(f"Cilindradas: {[Link]}")
carro = Carro("Ford", "Focus", 4)
moto = Moto("Yamaha", "Fazer", 250)
carro.exibir_info()
moto.exibir_info()
Polimorfismo
Polimorfismo significa "muitas formas". Em POO, refere-se à capacidade de objetos de
diferentes classes responderem ao mesmo método de maneiras diferentes.
class Cachorro:
def fazer_som(self):
print("Au Au")
class Gato:
def fazer_som(self):
print("Miau")
class Pato:
def fazer_som(self):
print("Quack")
def emitir_som(animal):
animal.fazer_som()
cachorro = Cachorro()
gato = Gato()
pato = Pato()
emitir_som(cachorro)
emitir_som(gato)
emitir_som(pato)
Encapsulamento
Encapsulamento é o agrupamento de dados (atributos) e métodos que operam sobre
esses dados em uma única unidade (classe), e a restrição do acesso direto a alguns
dos componentes do objeto. Em Python, usa-se convenções de nomenclatura para
indicar se um atributo ou método é privado (prefixo _ ou __ ).
class ContaBancaria:
def __init__(self, saldo_inicial):
self.__saldo = saldo_inicial # Atributo privado (convenção)
def depositar(self, valor):
if valor > 0:
self.__saldo += valor
print(f"Depósito de {valor} realizado. Novo saldo: {self.__saldo}")
else:
print("Valor de depósito inválido.")
def sacar(self, valor):
if valor > 0 and valor <= self.__saldo:
self.__saldo -= valor
print(f"Saque de {valor} realizado. Novo saldo: {self.__saldo}")
else:
print("Saldo insuficiente ou valor de saque inválido.")
def get_saldo(self):
return self.__saldo
conta = ContaBancaria(1000)
[Link](500)
[Link](200)
print(f"Saldo atual: {conta.get_saldo()}")
# print(conta.__saldo) # Isso geraria um AttributeError, pois __saldo é
'privado'
Exercícios
1. Crie uma classe Pessoa com atributos nome e idade e um método apresentar
que imprima uma mensagem de apresentação.
2. Crie uma classe Estudante que herde de Pessoa e adicione um atributo curso
e um método estudar .
3. Demonstre polimorfismo criando diferentes classes de formas geométricas (e.g.,
Circulo , Retangulo ) com um método calcular_area e chame esse método
para instâncias de cada classe em uma função genérica.
4. Modifique a classe ContaBancaria para incluir um atributo __numero_conta e
um método get_numero_conta .
2.3 Manipulação de Arquivos e Exceções
Manipulação de Arquivos
Python facilita a leitura e escrita de arquivos. O processo geralmente envolve abrir o
arquivo, realizar operações e fechar o arquivo.
# Escrevendo em um arquivo (modo 'w' - write, sobrescreve se existir)
with open("meu_arquivo.txt", "w") as arquivo:
[Link]("Olá, mundo!\n")
[Link]("Este é um exemplo de escrita em arquivo.\n")
# Lendo de um arquivo (modo 'r' - read)
with open("meu_arquivo.txt", "r") as arquivo:
conteudo = [Link]()
print("Conteúdo do arquivo:\n", conteudo)
# Lendo linha por linha
with open("meu_arquivo.txt", "r") as arquivo:
print("Lendo linha por linha:")
for linha in arquivo:
print([Link]()) # .strip() remove quebras de linha extras
# Adicionando conteúdo a um arquivo (modo 'a' - append)
with open("meu_arquivo.txt", "a") as arquivo:
[Link]("Adicionando uma nova linha.\n")
with open("meu_arquivo.txt", "r") as arquivo:
conteudo_atualizado = [Link]()
print("\nConteúdo atualizado:\n", conteudo_atualizado)
# Lidando com arquivos que não existem
try:
with open("arquivo_inexistente.txt", "r") as arquivo:
print([Link]())
except FileNotFoundError:
print("Erro: O arquivo não foi encontrado.")
Tratamento de Exceções ( try , except , finally )
Exceções são erros que ocorrem durante a execução de um programa. O tratamento
de exceções permite que você lide com esses erros de forma elegante, evitando que o
programa trave.
# Exemplo básico de try-except
try:
resultado = 10 / 0
print(resultado)
except ZeroDivisionError:
print("Erro: Divisão por zero não é permitida.")
# Tratando múltiplas exceções
try:
numero = int(input("Digite um número: "))
resultado = 10 / numero
print(f"O resultado é: {resultado}")
except ValueError:
print("Erro: Entrada inválida. Por favor, digite um número inteiro.")
except ZeroDivisionError:
print("Erro: Não é possível dividir por zero.")
except Exception as e: # Captura qualquer outra exceção
print(f"Ocorreu um erro inesperado: {e}")
finally:
print("Esta parte sempre será executada, independentemente de erro.")
# Usando else com try-except (executa se não houver exceção)
try:
num1 = int(input("Digite o primeiro número: "))
num2 = int(input("Digite o segundo número: "))
divisao = num1 / num2
except ValueError:
print("Erro: Certifique-se de digitar números válidos.")
except ZeroDivisionError:
print("Erro: Divisão por zero.")
else:
print(f"A divisão é: {divisao}")
finally:
print("Fim da operação.")
Exercícios
1. Crie um programa que escreva seu nome e sua idade em um arquivo chamado
dados_pessoais.txt . Em seguida, leia o conteúdo desse arquivo e imprima-o.
2. Escreva um programa que tente abrir um arquivo chamado lista_compras.txt
para leitura. Se o arquivo não existir, ele deve criar o arquivo e escrever alguns
itens de compra nele. Se existir, deve ler e imprimir o conteúdo.
3. Crie uma função que receba dois números e realize a divisão. Use tratamento de
exceções para lidar com ValueError (se a entrada não for um número) e
ZeroDivisionError (se o segundo número for zero).
2.4 Módulos e Pacotes Avançados
Módulos
Como vimos, módulos são arquivos .py que contêm código Python. Eles permitem
organizar o código em unidades lógicas e reutilizáveis. Além dos módulos embutidos,
você pode criar seus próprios módulos.
Criando e Usando um Módulo Personalizado:
Vamos criar um arquivo minhas_funcoes.py :
# minhas_funcoes.py
def saudacao(nome):
return f"Olá, {nome}! Bem-vindo(a) ao meu módulo."
def dobrar(numero):
return numero * 2
PI = 3.14159
Agora, você pode usar este módulo em outro arquivo Python (no mesmo diretório ou
em um diretório que esteja no PYTHONPATH ):
# meu_programa.py
import minhas_funcoes
print(minhas_funcoes.saudacao("Ana"))
print(minhas_funcoes.dobrar(10))
print(minhas_funcoes.PI)
from minhas_funcoes import saudacao, PI
print(saudacao("Bruno"))
print(PI)
Pacotes
Pacotes são uma forma de organizar módulos relacionados em um diretório
hierárquico. Um pacote é simplesmente um diretório que contém um arquivo especial
chamado __init__.py (que pode estar vazio).
Estrutura de um Pacote:
meu_projeto/
├── [Link]
└── meu_pacote/
├── __init__.py
├── [Link]
└── sub_pacote/
├── __init__.py
└── [Link]
Exemplo de Conteúdo:
meu_pacote/[Link] :
# meu_pacote/[Link]
def funcao_modulo1():
return "Função do Módulo 1"
meu_pacote/sub_pacote/[Link] :
# meu_pacote/sub_pacote/[Link]
def funcao_modulo2():
return "Função do Módulo 2 no Sub-pacote"
Usando o Pacote em [Link] :
# [Link]
from meu_pacote import modulo1
from meu_pacote.sub_pacote import modulo2
print(modulo1.funcao_modulo1())
print(modulo2.funcao_modulo2())
# Você também pode importar diretamente as funções
from meu_pacote.modulo1 import funcao_modulo1
print(funcao_modulo1())
__init__.py
O arquivo __init__.py é o que faz com que o Python trate os diretórios como
pacotes. Ele pode estar vazio, mas também pode ser usado para:
Inicialização do Pacote: Executar código de inicialização quando o pacote é
importado.
Definir __all__ : Controlar o que é importado quando from pacote import *
é usado.
Importações Simplificadas: Fazer importações de sub-módulos para o nível
superior do pacote.
Exemplo de meu_pacote/__init__.py para importações simplificadas:
# meu_pacote/__init__.py
from . import modulo1
from .sub_pacote import modulo2
# Opcional: definir o que é importado com 'from meu_pacote import *'
__all__ = ["modulo1", "modulo2"]
Com isso, em [Link] você poderia fazer:
# [Link]
import meu_pacote
print(meu_pacote.modulo1.funcao_modulo1())
print(meu_pacote.modulo2.funcao_modulo2())
Boas Práticas
Organização: Mantenha seus módulos e pacotes bem organizados e com nomes
descritivos.
Reutilização: Crie módulos para funcionalidades que você planeja reutilizar em
diferentes partes do seu projeto ou em outros projetos.
Evite from ... import * : Embora conveniente, from ... import * pode
poluir o namespace e dificultar a identificação da origem das funções e variáveis.
Prefira importações explícitas ( import modulo ou from modulo import
funcao ).
Exercícios
1. Crie um pacote chamado calculadora com dois módulos: [Link] (com uma
função somar ) e [Link] (com uma função subtrair ). Crie um arquivo
[Link] fora do pacote e importe e use as funções de soma e subtracao .
2. Adicione um arquivo __init__.py ao pacote calculadora para que você possa
importar as funções somar e subtrair diretamente do pacote (ex: from
calculadora import somar ).
3. Crie um módulo [Link] com algumas constantes (e.g., GRAVIDADE ,
VELOCIDADE_LUZ ). Importe e use essas constantes em outro script.
Módulo 3: Python Avançado
3.1 Decoradores, Geradores e Iteradores
Decoradores
Decoradores são funções que modificam o comportamento de outras funções ou
métodos. Eles permitem adicionar funcionalidades a uma função existente sem
modificar sua estrutura.
def meu_decorador(funcao):
def wrapper():
print("Antes da função ser chamada.")
funcao()
print("Depois da função ser chamada.")
return wrapper
@meu_decorador
def minha_funcao():
print("Minha função foi chamada.")
minha_funcao()
# Exemplo de decorador com argumentos
def log_execucao(funcao):
def wrapper(*args, **kwargs):
print(f"Executando a função {funcao.__name__} com argumentos: {args},
{kwargs}")
resultado = funcao(*args, **kwargs)
print(f"Função {funcao.__name__} finalizada. Resultado: {resultado}")
return resultado
return wrapper
@log_execucao
def somar(a, b):
return a + b
@log_execucao
def multiplicar(a, b):
return a * b
somar(10, 5)
multiplicar(4, 6)
Geradores
Geradores são funções que retornam um iterador. Eles geram valores "on the fly" (sob
demanda) usando a palavra-chave yield , o que os torna muito eficientes em termos
de memória, especialmente para sequências grandes.
def gerador_numeros(n):
i = 0
while i < n:
yield i
i += 1
# Usando o gerador
for numero in gerador_numeros(5):
print(numero)
# Geradores são iteradores, então podem ser convertidos em listas (mas perdem a
eficiência de memória)
lista_gerada = list(gerador_numeros(3))
print(lista_gerada)
# Exemplo: Gerador de Fibonacci
def fibonacci_generator(limite):
a, b = 0, 1
while a < limite:
yield a
a, b = b, a + b
for num in fibonacci_generator(10):
print(num)
Iteradores
Um iterador é um objeto que implementa o protocolo iterador, que consiste nos
métodos __iter__() e __next__() . O método __iter__() retorna o próprio objeto
iterador, e __next__() retorna o próximo item da sequência. Se não houver mais
itens, ele levanta a exceção StopIteration .
# Exemplo de iterador personalizado
class Contador:
def __init__(self, maximo):
[Link] = maximo
[Link] = 0
def __iter__(self):
return self
def __next__(self):
if [Link] < [Link]:
[Link] += 1
return [Link] - 1
else:
raise StopIteration
# Usando o iterador
meu_contador = Contador(3)
for num in meu_contador:
print(num)
# Ou manualmente
it = iter(Contador(2))
print(next(it))
print(next(it))
try:
print(next(it))
except StopIteration:
print("Fim da iteração.")
Exercícios
1. Crie um decorador que meça o tempo de execução de uma função.
2. Crie um gerador que produza números pares até um determinado limite.
3. Implemente um iterador para uma sequência de números de 1 a N, onde N é
definido na inicialização da classe.
3.2 Programação Assíncrona (Async/Await)
Programação assíncrona permite que um programa execute tarefas sem bloquear a
execução de outras tarefas, melhorando a responsividade e a eficiência,
especialmente em operações de I/O (entrada/saída) como requisições de rede ou
acesso a banco de dados.
Conceitos Chave
Corrotinas: Funções especiais definidas com async def . Elas podem ser
pausadas e retomadas.
await : Usado dentro de corrotinas para pausar a execução da corrotina atual
até que uma operação assíncrona seja concluída.
asyncio : Módulo da biblioteca padrão do Python para escrever código
concorrente usando a sintaxe async/await .
Loop de Eventos: O coração do asyncio , responsável por gerenciar e executar
as corrotinas.
import asyncio
import time
async def tarefa_assincrona(nome, tempo):
print(f"Iniciando {nome}...")
await [Link](tempo) # Simula uma operação de I/O demorada
print(f"{nome} concluída após {tempo} segundos.")
return f"Resultado de {nome}"
async def main():
print("Iniciando o programa principal...")
# Executando tarefas em paralelo
tarefa1 = asyncio.create_task(tarefa_assincrona("Tarefa 1", 3))
tarefa2 = asyncio.create_task(tarefa_assincrona("Tarefa 2", 2))
# Aguardando a conclusão das tarefas
resultado1 = await tarefa1
resultado2 = await tarefa2
print(f"Resultados: {resultado1}, {resultado2}")
print("Programa principal concluído.")
# Para executar o loop de eventos
if __name__ == "__main__":
start_time = [Link]()
[Link](main())
end_time = [Link]()
print(f"Tempo total de execução: {end_time - start_time:.2f} segundos")
No exemplo acima, tarefa_assincrona simula uma operação que leva tempo. Ao
usar await [Link](tempo) , a execução da tarefa_assincrona é pausada,
mas o loop de eventos pode alternar para outras corrotinas (como tarefa2 ) em vez
de ficar ocioso. Isso permite que as tarefas sejam executadas de forma concorrente,
reduzindo o tempo total de execução.
Requisições HTTP Assíncronas (com aiohttp )
Para operações de rede, bibliotecas como requests são síncronas. Para requisições
assíncronas, você pode usar aiohttp (precisa ser instalado: pip install aiohttp ).
import asyncio
import aiohttp
async def fetch_url(session, url):
async with [Link](url) as response:
return await [Link]()
async def main_http():
urls = [
"[Link]
"[Link]
"[Link]
]
async with [Link]() as session:
tasks = []
for url in urls:
[Link](fetch_url(session, url))
# Executa todas as tarefas de forma concorrente
htmls = await [Link](*tasks)
for i, html in enumerate(htmls):
print(f"Conteúdo da URL {urls[i]} (primeiros 100
caracteres):\n{html[:100]}\n...")
if __name__ == "__main__":
print("\n--- Exemplo de Requisições HTTP Assíncronas ---")
[Link](main_http())
Exercícios
1. Crie três corrotinas que simulem o download de arquivos de tamanhos
diferentes (usando [Link] com tempos variados). Execute-as
concorrentemente e imprima a ordem em que elas terminam.
2. Pesquise sobre [Link] e [Link] e explique a diferença entre
eles em um pequeno texto.
3. (Opcional, requer aiohttp ) Modifique o exemplo de main_http para buscar
dados de uma API pública (ex: JSONPlaceholder) e imprima o JSON retornado.
3.3 Frameworks Web (Flask/Django) e Manipulação de APIs
Python é amplamente utilizado no desenvolvimento web, graças a frameworks
poderosos como Flask e Django.
Flask (Microframework)
Flask é um microframework web leve e flexível. É ideal para projetos menores, APIs
RESTful e para quem prefere ter mais controle sobre os componentes.
Exemplo Básico de Flask:
Para rodar este exemplo, você precisará instalar Flask: pip install Flask
# [Link]
from flask import Flask, jsonify, request
app = Flask(__name__)
# Rota Home
@[Link]('/')
def home():
return "Olá, mundo! Bem-vindo à minha API Flask!"
# Rota com parâmetro
@[Link]('/saudar/<nome>')
def saudar(nome):
return f"Olá, {nome}!"
# Rota para API RESTful (GET)
@[Link]('/api/dados', methods=['GET'])
def get_dados():
dados = {
"id": 1,
"nome": "Exemplo",
"valor": 123.45
}
return jsonify(dados)
# Rota para API RESTful (POST)
@[Link]('/api/adicionar', methods=['POST'])
def adicionar_dados():
novo_dado = [Link] # Espera um JSON no corpo da requisição
print(f"Dado recebido: {novo_dado}")
# Aqui você normalmente salvaria o dado em um banco de dados
return jsonify({"mensagem": "Dado adicionado com sucesso!", "dado":
novo_dado}), 201 # 201 Created
if __name__ == '__main__':
[Link](debug=True) # debug=True para desenvolvimento (recarrega
automaticamente)
Para executar, salve como [Link] e rode python [Link] . Acesse
[Link] no seu navegador. Para testar a rota POST, você pode usar
ferramentas como Postman, Insomnia ou curl :
curl -X POST -H "Content-Type: application/json" -d '{"item": "caneta",
"quantidade": 10}' [Link]
Django (Full-stack Framework)
Django é um framework web de alto nível que incentiva o desenvolvimento rápido e
design limpo e pragmático. Ele vem com muitas funcionalidades "baterias incluídas",
como ORM (Object-Relational Mapper), sistema de administração, autenticação, etc. É
ideal para aplicações complexas e escaláveis.
Conceitos Chave do Django: * MTV (Model-Template-View): Similar ao MVC. Model
(dados), Template (interface), View (lógica de negócio). * ORM: Mapeia objetos Python
para tabelas de banco de dados, eliminando a necessidade de escrever SQL
diretamente. * Admin Site: Um painel de administração gerado automaticamente
para gerenciar dados do modelo.
Exemplo Básico de Django (Estrutura):
Para iniciar um projeto Django:
pip install Django
django-admin startproject meuprojeto
cd meuprojeto
python [Link] startapp minhaapp
meuprojeto/meuprojeto/[Link] : Adicione 'minhaapp' em INSTALLED_APPS .
meuprojeto/minhaapp/[Link] :
from [Link] import models
class Livro([Link]):
titulo = [Link](max_length=200)
autor = [Link](max_length=100)
ano_publicacao = [Link]()
def __str__(self):
return [Link]
Após definir o modelo, execute as migrações:
python [Link] makemigrations
python [Link] migrate
meuprojeto/minhaapp/[Link] :
from [Link] import render
from [Link] import HttpResponse
from .models import Livro
def lista_livros(request):
livros = [Link]()
return render(request, 'minhaapp/lista_livros.html', {'livros': livros})
def saudacao_django(request):
return HttpResponse("Olá do Django!")
meuprojeto/minhaapp/[Link] (crie este arquivo):
from [Link] import path
from . import views
urlpatterns = [
path('livros/', views.lista_livros, name='lista_livros'),
path('saudacao/', views.saudacao_django, name='saudacao_django'),
]
meuprojeto/meuprojeto/[Link] : Inclua as URLs da sua app.
from [Link] import admin
from [Link] import path, include
urlpatterns = [
path('admin/', [Link]),
path('minhaapp/', include('[Link]')), # Inclui as URLs da sua app
]
Crie o diretório meuprojeto/minhaapp/templates/minhaapp/ e o arquivo
lista_livros.html :
<!-- meuprojeto/minhaapp/templates/minhaapp/lista_livros.html -->
<!DOCTYPE html>
<html>
<head>
<title>Lista de Livros</title>
</head>
<body>
<h1>Livros Disponíveis</h1>
<ul>
{% for livro in livros %}
<li>{{ [Link] }} - {{ [Link] }} ({{ livro.ano_publicacao
}})</li>
{% endfor %}
</ul>
</body>
</html>
Para rodar o servidor de desenvolvimento:
python [Link] runserver
Acesse [Link] ou
[Link] .
Manipulação de APIs (Consumindo APIs)
Além de criar APIs, Python é excelente para consumir APIs existentes. A biblioteca
requests é a mais popular para isso.
Para instalar: pip install requests
import requests
def buscar_posts_jsonplaceholder():
url = "[Link]
try:
response = [Link](url)
response.raise_for_status() # Levanta um erro para códigos de status
HTTP ruins (4xx ou 5xx)
posts = [Link]() # Converte a resposta JSON em um objeto Python
for post in posts[:5]: # Imprime os primeiros 5 posts
print(f"ID: {post['id']}\nTítulo: {post['title']}\nCorpo:
{post['body'][:50]}...\n---\n")
except [Link] as errh:
print(f"Erro HTTP: {errh}")
except [Link] as errc:
print(f"Erro de Conexão: {errc}")
except [Link] as errt:
print(f"Timeout: {errt}")
except [Link] as err:
print(f"Ocorreu um erro: {err}")
def criar_novo_post():
url = "[Link]
novo_post = {
"title": "Meu Novo Post",
"body": "Este é o corpo do meu post de teste.",
"userId": 1
}
headers = {'Content-type': 'application/json; charset=UTF-8'}
try:
response = [Link](url, json=novo_post, headers=headers)
response.raise_for_status()
print("\nNovo post criado com sucesso:")
print([Link]())
except [Link] as err:
print(f"Erro ao criar post: {err}")
if __name__ == "__main__":
print("Buscando posts de exemplo...")
buscar_posts_jsonplaceholder()
print("\nCriando um novo post de exemplo...")
criar_novo_post()
Exercícios
1. (Flask) Modifique o aplicativo Flask para ter uma rota
/calculadora/<operacao>/<num1>/<num2> que realize operações básicas
(soma, subtracao, multiplicacao, divisao) e retorne o resultado em JSON.
2. (Django) Crie um novo modelo Django chamado Produto com campos nome ,
preco e estoque . Configure o admin para gerenciar Produto e crie algumas
instâncias via painel de administração.
3. (APIs) Use a biblioteca requests para consumir uma API pública diferente (ex:
Star Wars API - SWAPI, OpenWeatherMap API - requer chave, ou outra de sua
escolha) e imprima alguns dados relevantes.
3.4 Testes Unitários e Boas Práticas de Código
Testes Unitários com unittest
Testes unitários são uma parte crucial do desenvolvimento de software moderno. Eles
garantem que pequenas unidades de código (funções, métodos) funcionem como
esperado, facilitando a detecção de bugs e a manutenção do código.
Python possui um módulo embutido para testes unitários chamado unittest .
# [Link] (o código que vamos testar)
def somar(a, b):
return a + b
def subtrair(a, b):
return a - b
def multiplicar(a, b):
return a * b
def dividir(a, b):
if b == 0:
raise ValueError("Não é possível dividir por zero!")
return a / b
# test_calculadora.py (o arquivo de testes)
import unittest
from calculadora import somar, subtrair, multiplicar, dividir
class TestCalculadora([Link]):
def test_somar(self):
[Link](somar(2, 3), 5)
[Link](somar(-1, 1), 0)
[Link](somar(-1, -1), -2)
def test_subtrair(self):
[Link](subtrair(5, 2), 3)
[Link](subtrair(2, 5), -3)
[Link](subtrair(10, 0), 10)
def test_multiplicar(self):
[Link](multiplicar(2, 3), 6)
[Link](multiplicar(-1, 5), -5)
[Link](multiplicar(0, 10), 0)
def test_dividir(self):
[Link](dividir(10, 2), 5)
[Link](dividir(10, 3), 3.33333, places=5)
with [Link](ValueError):
dividir(10, 0)
if __name__ == '__main__':
[Link]()
Para executar os testes, salve os dois arquivos no mesmo diretório e execute python
test_calculadora.py no terminal.
Boas Práticas de Código Python (PEP 8)
Seguir boas práticas de codificação torna seu código mais legível, mantenível e
colaborativo. A PEP 8 é o guia de estilo oficial do Python.
Principais Pontos da PEP 8:
Nomenclatura:
Variáveis e funções: snake_case (ex: minha_variavel , calcular_total ).
Classes: CamelCase (ex: MinhaClasse , ContaBancaria ).
Constantes: ALL_CAPS (ex: MAX_SIZE , PI ).
Módulos: lowercase_with_underscores .
Espaçamento:
Indentação: 4 espaços (NUNCA tabs).
Linhas em branco: Duas linhas em branco entre funções e classes de nível
superior; uma linha em branco entre métodos dentro de uma classe.
Espaços em branco: Evite espaços em branco desnecessários (ex: x = 1 e
não x = 1 ).
Comprimento da Linha: Limite as linhas a 79 caracteres (ou 99 para comentários
e docstrings). Use parênteses para quebrar linhas longas.
```python
Ruim
resultado_muito_longo = funcao_com_muitos_parametros(parametro1,
parametro2, parametro3, parametro4, parametro5, parametro6)
Bom
resultado_muito_longo = funcao_com_muitos_parametros( parametro1,
parametro2, parametro3, parametro4, parametro5, parametro6 ) ```
Comentários e Docstrings:
Comentários ( # ): Use para explicar o "porquê" do código, não o "o quê".
Docstrings ( """Docstring""" ): Use para documentar módulos, classes e
funções. Explique o propósito, argumentos, o que retorna e possíveis
exceções.
Importações:
Importe módulos no início do arquivo.
Organize as importações em grupos: biblioteca padrão, bibliotecas de
terceiros, módulos locais. Separe os grupos com uma linha em branco.
Importe um módulo por linha.
```python import os import sys
import requests from flask import Flask
from meu_modulo import minha_funcao ```
Tratamento de Exceções: Seja específico ao capturar exceções. Evite except
Exception as e: genéricos, a menos que você realmente precise capturar tudo
e saiba como lidar com isso.
List Comprehensions: Use para criar listas de forma concisa e legível.
```python
Ruim
quadrados = [] for i in range(10): [Link](i**2)
Bom
quadrados = [i**2 for i in range(10)] ```
Uso de if __name__ == "__main__": : Garante que o código dentro deste bloco
só seja executado quando o script é rodado diretamente, e não quando é
importado como um módulo.
Ferramentas como flake8 , black e isort podem ajudar a automatizar a verificação
e formatação do código de acordo com a PEP 8.
Exercícios
1. Crie um arquivo minha_string_utils.py com uma função
inverter_string(texto) e outra contar_vogais(texto) . Escreva um arquivo
de teste test_string_utils.py usando unittest para testar essas funções.
2. Pegue um dos seus códigos anteriores e refatore-o para seguir as boas práticas
da PEP 8, prestando atenção à nomenclatura, espaçamento e comprimento das
linhas.
3. Pesquise sobre a ferramenta Black (formatador de código Python) e explique
em um parágrafo como ela pode ajudar a manter a conformidade com a PEP 8.
Módulo 4: Projetos Práticos
Para solidificar o conhecimento adquirido, é fundamental aplicar os conceitos em
projetos práticos. Abaixo, sugerimos alguns projetos, divididos por nível de
dificuldade, que você pode desenvolver.
4.1 Projetos Nível Básico
Estes projetos são ideais para praticar os fundamentos de variáveis, estruturas de
controle, funções e manipulação básica de strings e listas.
Projeto 1: Jogo de Adivinhação de Números
Descrição: O computador escolhe um número aleatório entre 1 e 100, e o jogador
tenta adivinhar. O programa deve dar dicas se o palpite é muito alto ou muito baixo, e
contar quantas tentativas o jogador levou.
Conceitos a praticar: * Geração de números aleatórios ( [Link] ). * Laços de
repetição ( while ). * Condicionais ( if / elif / else ). * Entrada e saída de dados
( input() , print() ). * Conversão de tipos ( int() ).
Exemplo de Estrutura:
import random
def jogar_adivinhacao():
numero_secreto = [Link](1, 100)
tentativas = 0
print("Bem-vindo ao Jogo de Adivinhação!")
print("Estou pensando em um número entre 1 e 100.")
while True:
try:
palpite = int(input("Qual é o seu palpite? "))
tentativas += 1
if palpite < numero_secreto:
print("Muito baixo! Tente novamente.")
elif palpite > numero_secreto:
print("Muito alto! Tente novamente.")
else:
print(f"Parabéns! Você adivinhou o número em {tentativas}
tentativas!")
break
except ValueError:
print("Entrada inválida. Por favor, digite um número inteiro.")
if __name__ == "__main__":
jogar_adivinhacao()
Projeto 2: Calculadora Simples
Descrição: Crie uma calculadora que realize as quatro operações básicas (adição,
subtração, multiplicação, divisão) com dois números fornecidos pelo usuário.
Conceitos a praticar: * Funções. * Condicionais. * Entrada de dados. * Tratamento de
exceções ( try / except para divisão por zero e entrada inválida).
4.2 Projetos Nível Intermediário
Estes projetos envolvem estruturas de dados mais complexas, POO, manipulação de
arquivos e módulos/pacotes.
Projeto 1: Sistema de Gerenciamento de Tarefas (To-Do List)
Descrição: Desenvolva um programa que permita ao usuário adicionar, remover,
marcar como concluída e listar tarefas. As tarefas devem ser persistidas em um
arquivo (e.g., CSV ou JSON).
Conceitos a praticar: * Listas e Dicionários. * Programação Orientada a Objetos
(classe Tarefa ). * Manipulação de arquivos (leitura/escrita). * Funções para cada
operação (adicionar, remover, etc.). * Uso de módulos como json ou csv .
Exemplo de Estrutura:
import json
class Tarefa:
def __init__(self, descricao, concluida=False):
[Link] = descricao
[Link] = concluida
def __str__(self):
status = "[X]" if [Link] else "[ ]"
return f"{status} {[Link]}"
def to_dict(self):
return {"descricao": [Link], "concluida": [Link]}
class GerenciadorTarefas:
def __init__(self, arquivo_dados="[Link]"):
self.arquivo_dados = arquivo_dados
[Link] = self._carregar_tarefas()
def _carregar_tarefas(self):
try:
with open(self.arquivo_dados, "r") as f:
dados = [Link](f)
return [Tarefa(t["descricao"], t["concluida"]) for t in dados]
except FileNotFoundError:
return []
def _salvar_tarefas(self):
with open(self.arquivo_dados, "w") as f:
[Link]([t.to_dict() for t in [Link]], f, indent=4)
def adicionar_tarefa(self, descricao):
[Link](Tarefa(descricao))
self._salvar_tarefas()
print(f"Tarefa \"{descricao}\" adicionada.")
def listar_tarefas(self):
if not [Link]:
print("Nenhuma tarefa na lista.")
return
print("\n--- Suas Tarefas ---")
for i, tarefa in enumerate([Link]):
print(f"{i + 1}. {tarefa}")
print("-------------------")
def marcar_concluida(self, indice):
if 0 <= indice < len([Link]):
[Link][indice].concluida = True
self._salvar_tarefas()
print(f"Tarefa \"{[Link][indice].descricao}\" marcada como
concluída.")
else:
print("Índice de tarefa inválido.")
def remover_tarefa(self, indice):
if 0 <= indice < len([Link]):
removida = [Link](indice)
self._salvar_tarefas()
print(f"Tarefa \"{[Link]}\" removida.")
else:
print("Índice de tarefa inválido.")
def menu():
gerenciador = GerenciadorTarefas()
while True:
print("\nMenu:")
print("1. Adicionar tarefa")
print("2. Listar tarefas")
print("3. Marcar tarefa como concluída")
print("4. Remover tarefa")
print("5. Sair")
escolha = input("Escolha uma opção: ")
if escolha == "1":
desc = input("Descrição da tarefa: ")
gerenciador.adicionar_tarefa(desc)
elif escolha == "2":
gerenciador.listar_tarefas()
elif escolha == "3":
gerenciador.listar_tarefas()
try:
idx = int(input("Número da tarefa para marcar como concluída:
")) - 1
gerenciador.marcar_concluida(idx)
except ValueError:
print("Entrada inválida. Digite um número.")
elif escolha == "4":
gerenciador.listar_tarefas()
try:
idx = int(input("Número da tarefa para remover: ")) - 1
gerenciador.remover_tarefa(idx)
except ValueError:
print("Entrada inválida. Digite um número.")
elif escolha == "5":
print("Saindo do programa.")
break
else:
print("Opção inválida. Tente novamente.")
if __name__ == "__main__":
menu()
Projeto 2: Consumidor de API de Clima
Descrição: Crie um programa que use uma API de clima (ex: OpenWeatherMap,
[Link]) para obter e exibir informações sobre o clima de uma cidade
fornecida pelo usuário.
Conceitos a praticar: * Requisições HTTP ( requests ). * Manipulação de JSON. *
Tratamento de erros de rede e API. * Uso de chaves de API.
4.3 Projetos Nível Avançado
Estes projetos exploram tópicos como programação assíncrona, frameworks web e
testes mais complexos.
Projeto 1: Aplicação Web Simples com Flask/Django
Descrição: Desenvolva uma pequena aplicação web (e.g., um blog simples, um
encurtador de URL, um gerenciador de contatos) usando Flask ou Django. A aplicação
deve ter pelo menos duas rotas e interagir com um banco de dados (pode ser SQLite
para simplicidade).
Conceitos a praticar: * Frameworks web (Flask ou Django). * Rotas e views. *
Templates (Jinja2 para Flask, Django Templates). * Interação com banco de dados
(ORM). * Formulários (WTForms para Flask, Django Forms).
Projeto 2: Raspador de Dados Assíncrono (Web Scraper)
Descrição: Crie um programa que raspe dados de um site (e.g., títulos de notícias,
preços de produtos) de forma assíncrona para melhorar a performance. Salve os
dados em um arquivo ou banco de dados.
Conceitos a praticar: * Programação assíncrona ( asyncio , aiohttp ). * Parsing de
HTML ( BeautifulSoup4 ). * Tratamento de exceções. * Manipulação de arquivos/banco
de dados.
Dicas para os Projetos:
Comece Pequeno: Não tente construir tudo de uma vez. Divida o projeto em
pequenas funcionalidades e implemente uma por uma.
Pesquise: Use a documentação oficial, Stack Overflow e outros recursos online
quando tiver dúvidas.
Teste: Escreva testes para suas funções e classes para garantir que tudo funcione
como esperado.
Controle de Versão: Use Git e GitHub para gerenciar seu código. Isso é uma
prática essencial no desenvolvimento de software.
Refatore: À medida que você aprende mais, revise seu código antigo e veja como
pode melhorá-lo (torná-lo mais limpo, eficiente, legível).
Divirta-se! A melhor forma de aprender é fazendo e se divertindo com o
processo.