Alura > Cursos de Programação > Cursos de Python > Conteúdos de Python > Primeiras aulas do curso Python: aplicando boas práticas com PEP 8

Python: aplicando boas práticas com PEP 8

Primeiros passos em Boas Práticas - Apresentação

Olá, meu nome é Laís Urano, sou instrutora na Alura e irei acompanhar vocês neste curso de Boas Práticas com Python!

Audiodescrição: Laís se declara uma mulher parda, com olhos e cabelos castanhos, sendo os cabelos cacheados volumosos. Veste uma camisa marrom. Ao fundo, há uma parede branca iluminada com as cores verde e azul.

Para quem é esse curso?

Este conteúdo é destinado a quem deseja aprender a aplicar boas práticas em seus projetos Python.

Pré-requisitos

Para isso, é necessário ter um conhecimento intermediário na linguagem Python. Também é importante estar familiarizado com os conceitos básicos de criação de API utilizando o framework FastAPI.

O que vamos aprender?

Neste curso, aprenderemos a aplicar os princípios de boas práticas da PEP 8, além de desenvolver conceitos e convenções de organização, nomenclatura, tipagem explícita, documentação, compreensão de lista, tratamento de erros e testes automatizados. Aprenderemos tudo isso em um projeto de uma API de recomendação de produtos, de acordo com as preferências e o histórico de uma pessoa usuária.

Conclusão

Aproveitem os recursos da plataforma! Além dos vídeos, temos as atividades, e vocês podem contar com o apoio do fórum e da comunidade da Alura. Vamos lá?

Primeiros passos em Boas Práticas - Ambiente virtualizado

Nós temos o projeto de uma API feita em FastAPI para recomendação de produtos de acordo com as preferências e o histórico de compras de uma determinada pessoa usuária. Esse projeto é de código aberto, ou seja, estará disponível para visualização, modificação e distribuição. Antes de verificar como esse projeto funciona, precisamos executá-lo em nossa máquina.

Estamos utilizando o VSCode, e dentro dele temos quatro arquivos:

Esses arquivos estão disponíveis para download na atividade "Preparando o ambiente".

No nosso caso, temos um arquivo app.py, que contém todo o código de funcionamento do projeto. Também temos o requirements.txt, que contém todas as dependências necessárias para o projeto funcionar. O gitignore é o arquivo que lista todos os arquivos e pastas que não devem ser enviados para o GitHub, como dados sensíveis e informações não pertinentes. Por fim, temos o README.md, que contém a documentação de funcionamento da aplicação.

É importante acompanhar o README de acordo com as aulas, pois ele está um pouco mais adiantado em relação ao projeto atual. Vamos verificar apenas pontos específicos conforme a evolução das aulas. O ponto principal a observar é que ele lista as tecnologias utilizadas, principalmente o Python 3.9, necessário para o projeto. Também há algumas dependências importantes mencionadas.

README.md

## código omitido

## Tecnologias Utilizadas

- Python 3.9+
- FastAPI
- Pydantic
- Uvicorn (para rodar o servidor)
- Pytest (para testes automatizados)

## código omitido

Instalação e configuração

Nosso foco agora é a instalação e configuração. O primeiro passo é clonar o repositório ou fazer o download, o que já fizemos. O segundo e terceiro passos tratam de ambientes virtuais: criar e ativar um ambiente.

## código omitido

1. Clone o repositório:

git clone https://github.com/uranolais/boas-praticas-python-curso01.git
cd recomendacao-produtos

2. Crie um ambiente virtual:
python -m venv venv

3. Ative o ambiente virtual:

- No Windows:
venv\Scripts\activate

- No macOS/Linux:
source venv/bin/activate

## código omitido

Vamos abrir o terminal com "Ctrl + J" e executar o seguinte comando:

python -m venv venv

Esse comando cria um ambiente virtual, e podemos perceber que ele cria uma pasta do lado esquerdo chamada venv. No canto inferior direito, uma mensagem indica que o ambiente foi criado. Vamos fechar essa mensagem por enquanto. Precisamos ativar o ambiente. No Windows, utilizamos:

venv\Scripts\activate

No Linux ou macOS, utilizamos:

source venv/bin/activate

Utilize o comando de acordo com seu sistema operacional.

No nosso caso, estamos utilizando o Windows, então ativamos com venv\Scripts\activate. Para confirmar a ativação, visualizamos a informação (venv) entre parênteses antes da linha de diretório no terminal.

Instalando as dependências

Após a ativação, precisamos instalar as dependências necessárias para o funcionamento do projeto com o comando:

A instrutora está seguindo os passos indicados no arquivo README.md

pip install -r requirements.txt

Teclamos "Enter".

Esse processo pode demorar um pouco, dependendo das dependências a serem instaladas. O pip install faz a instalação, o -r lê um arquivo, e o requirements.txt é o arquivo que contém as dependências a serem lidas. Após a instalação, todas as dependências necessárias para o projeto estão instaladas no ambiente virtual.

Se observarmos o app.py, ele pode não mostrar que o FastAPI está instalado, mesmo após a instalação. Observe que está com um sublinhado na importação do fastapi. Para resolver isso, basta reiniciar o VSCode. Ao reiniciar, o VSCode identifica as importações corretamente. No terminal, é exibida a mensagem "Histórico restaurado". Caso isso aconteça, apenas reinicie o VSCode

Compreendendo sobre ambiente virtual

Criamos e ativamos o ambiente virtual, mas o que é um ambiente virtual e por que utilizamos o venv especificamente? Existem outros ambientes virtuais que podemos utilizar. Vamos explorar isso.

Abriremos um slide no navegador que mostra um computador com o Python 3.10.11 instalado. Dentro desse computador, podemos criar pequenos computadores.

Ilustração estilizada de um monitor de computador com temas de programação e ambientes virtuais. Há dois monitores que exibem dois ambientes virtuais rotulados como "AMBIENTE VIRTUAL 01" com Python 3.9.8 e Flask 2.3.3, e "AMBIENTE VIRTUAL 02" com Python 3.11.6 e Django 4.2. Há um terceiro espaço indicando a possibilidade de outros ambientes, sugerido por pontos de suspensão. Na parte inferior da tela, centralizado, há uma indicação de "Python 3.10.11". A palavra "COMPUTADOR" está escrita verticalmente em uma aba à esquerda e possui uma linha que se conecta ao "Python 3.10.11".

A função principal de um ambiente virtual é isolar certas dependências para evitar conflitos entre versões de tecnologias. Por exemplo, podemos ter uma máquina com o Python 3.10.11, mas um projeto que requer o Python 3.9 e o Flask 2.3.3. Para evitar conflitos, criamos um ambiente virtual e instalamos as versões necessárias, isolando-as do ambiente global.

Podemos criar diversos ambientes virtualizados, como um com Python e Flask, outro com Python e Django, ou Python com FastAPI, para evitar conflitos entre si.

A função principal do ambiente virtual é isolar dependências para evitar conflitos, uma prática recomendada para pessoas desenvolvedoras de Python.

Utilizamos o venv porque ele é o ambiente virtual mais utilizado na linguagem Python, com 55% de uso em 2023, segundo o site da JetBrains ao pesquisarmos por "PyCharm Python Survey 2023" no Google. Nesse site, verificamos quais tecnologias são utilizadas no mercado e qual sua frequência de uso.

Ao clicarmos em "Python Packaging" na aba "Contents" verificamos a pesquisa que mostra que o venv é o mais utilizado na linguagem Python.

Outras opções, como Virtualenv e Conda, tiveram uma queda de uso. O Poetry teve um aumento no uso, no entanto, não é um crescimento tão significativo quanto do venv. Descendo um pouco a página, visualizamos as ferramentas de uso de dependências. No caso a Pip está no topo com 77%. Quanto ao formato de armazenamento de dependências, visualizamos que o requirements.txt é o mais comum, com 63% de uso.

A utilização dessas ferramentas deve considerar a preferência pessoal e da empresa.

Verificando as dependências

Voltando ao VSCode, em um ambiente virtual, podemos verificar as dependências necessárias para o projeto com o comando pip freeze.

pip freeze 

annotated-types==0.7.0

anyio==4.6.2.post1

click==8.1.7

colorama==0.4.6

fastapi==0.115.3

h11==0.14.0

idna==3.10

pydantic==2.9.2

pydantic_core==2.23.4

sniffio==1.3.1

starlette==0.41.0

typing_extensions==4.12.2

uvicorn==0.32.0

Esse comando mostra o conteúdo do arquivo do pip requirements.txt. Podemos atualizar esse arquivo com:

pip freeze > requirements.txt

Assim, mantemos o arquivo atualizado com as dependências atuais do projeto. Se quisermos desinstalar ou instalar uma tecnologia, podemos atualizar o requirements.txt com pip freeze para garantir que o projeto funcione corretamente.

Próximo passo

Agora que instalamos todas as dependências e o app.py as reconhece, podemos verificar como o projeto funciona e quais pontos precisamos trabalhar. É isso que faremos a seguir!

Primeiros passos em Boas Práticas - Conhecendo o projeto

Instalamos as dependências necessárias para o projeto rodar e configuramos um ambiente virtual para sua execução. Agora, é importante compreender como ele está estruturado e como podemos interagir com ele.

Analisando o projeto

O primeiro ponto a ser analisado no arquivo app.py são os imports:

from pydantic import BaseModel
from typing import List
from fastapi import FastAPI
from fastapi import APIRouter, HTTPException

# código omitido

Os imports são responsáveis por trazer os módulos e bibliotecas essenciais para o funcionamento da nossa aplicação. Incluímos o import de modelo, o import de typing para tipos, como List, e também os imports do FastAPI, framework utilizado para a construção da nossa API.

A partir da linha 9, temos a criação de modelos.

Modelos são representações de dados que serão utilizados dentro da nossa aplicação.

# código omitido

# Modelo base para produto
class ProdutoBase(BaseModel):
    nome: str
    categoria: str
    tags: List[str]


# Modelo para criar um produto
class CriarProduto(ProdutoBase):
    pass

# Modelo de produto com ID
class Produto(ProdutoBase):
    id: int


# Modelo para histórico de compras do usuário
class HistoricoCompras(BaseModel):
    produtos_ids: List[int]


# Modelo para preferências do usuário
class Preferencias(BaseModel):
    categorias: List[str] | None = None
    tags: List[str] | None = None

# código omitido

No momento, estamos criando um tipo de dado, por exemplo, o ProdutoBase(), que terá nome, categoria e tags, todos eles do tipo string. O ProdutoBase() herda do BaseModel, que é do próprio pydantic.

Em seguida, temos o CriarProduto(), que é o modelo para a criação de um produto, herdando do ProdutoBase, que é o primeiro modelo que verificamos. Logo após, temos o modelo de Produto(), que também herda do ProdutoBase e cria um id do tipo inteiro para o produto.

Logo após, temos um modelo para o histórico de compras da pessoa usuária. O HistoricoCompras() herda do BaseModel, com produtos_ids sendo uma lista de inteiros que representam os IDs dos produtos. Por último, temos a classe Preferencias(), que é o modelo de preferências, herdando do BaseModel, com categorias e tags, que são listas de strings ou vazias (None).

Para representar a pessoa usuária, definimos a classe Usuario(), que serve como modelo de dados. Esta classe herda de BaseModel e define dois atributos: um id do tipo inteiro e um nome do tipo string:

# código omitido

# Modelo base para um usuário
class Usuario(BaseModel):
    id: int
    nome: str

# código omitido

Estamos abordando de forma superficial o funcionamento deste projeto, pois já vamos trabalhar com ele em funcionamento.

Caso prefira, há uma atividade explicando detalhadamente como o projeto foi criado e como funciona.

Em seguida, definimos algumas variáveis essenciais para o funcionamento do nosso projeto: Produtos, que é uma lista; Usuarios, igualmente uma lista; ContadorProduto e ContadorUsuario, ambos iniciados como inteiros com valor 1.

Também temos a constante ConstanteMensagemHome, que armazena uma mensagem de boas-vindas à API de recomendação de produtos, e o HistoricoDeCompras, um dicionário em memória. Por fim, criamos uma instância de app, que é inicializada na linha 57 com app = FastAPI():

# código omitido

Produtos                 =[]
ContadorProduto          =1


Usuarios                 =[]

ContadorUsuario          =1


ConstanteMensagemHome    ="Bem-vindo à API de Recomendação de Produtos"

# Histórico de compras em memória
HistoricoDeCompras         ={}

# Criando o App
app = FastAPI()

# código omitido

Estamos criando uma instância desse app FastAPI.

Para iniciar o servidor e definir as rotas da API, utilizamos o decorador @app.get("/") (sendo get o recurso HTTP), passando como argumento a rota ou endpoint que desejamos acessar. Neste caso, estamos criando o endpoint /, que é o principal da nossa aplicação. Em seguida, definimos uma função que será executada quando a rota for acessada:

# código omitido

@app.get("/")
def home():
    global ConstanteMensagemHome
    return {"mensagem": ConstanteMensagemHome}

# código omitido

A função home(), por exemplo, retorna a constante ConstanteMensagemHome, que contém a mensagem de boas-vindas à API de recomendação de produtos. É fundamental lembrar que toda rota deve ter um endpoint associado e deve sempre retornar uma resposta.

Depois, temos a rota de POST de produtos, que cria um produto do modelo Produto:

# código omitido

# Rota para cadastrar produtos

@app.post("/produtos/", response_model=Produto)
def criarproduto(produto: CriarProduto):
    global ContadorProduto
    NovoProduto = Produto(id=ContadorProduto, **produto.model_dump())
    Produtos.append(NovoProduto)
    ContadorProduto += 1
    return NovoProduto
        
# Rota para listar todos os produtos

@app.get("/produtos/", response_model=List[Produto])
def listarprodutos():
    return Produtos

# código omitido

A função recebe o produto criado e retorna exatamente o valor que foi fornecido. Além disso, é possível acessar esses produtos através da rota /produtos, que agora é uma requisição do tipo GET e retorna a lista completa de produtos cadastrados.

Na sequência, temos uma rota de POST para o histórico de compras, que cria o histórico de compras para a pessoa usuária:

# código omitido

# Rota para simular a criação do histórico de compras de um usuário

@app.post("/historico_compras/{usuario_id}")
def adicionarhistoricocompras(usuario_id: int, compras: HistoricoCompras):
    if usuario_id not in [usuario.id for usuario in Usuarios]:
        raise HTTPException(status_code=404, detail="Usuário não encontrado")
    HistoricoDeCompras[usuario_id] = compras.produtos_ids
    return {"mensagem": "Histórico de compras atualizado"}

# código omitido

Primeiro, verifica se a pessoa usuária existe. Se existir, cria o histórico de compras e retorna uma mensagem de "Histórico de compras atualizado".

Em seguida, temos a rota POST de recomendações, que verifica se a pessoa usuária informado possui um histórico de compras já que recebe usuario_id e as preferencias da pessoa usuária, e, caso exista um histórico de compras, retorna os produtos_recomendados, filtrando-os por categoria e tags de preferência:

# código omitido

# Rota para recomendações de produtos

@app.post("/recomendacoes/{usuario_id}", response_model=List[Produto])
def recomendarprodutos(usuario_id: int, preferencias: Preferencias):
    if usuario_id not in HistoricoDeCompras:
        raise HTTPException(status_code=404, detail="Histórico de compras não encontrado")

    produtos_recomendados = []
        
    # Buscar produtos com base no histórico de compras do usuário

    produtos_recomendados = [produto for produto_id in HistoricoDeCompras[usuario_id] for produto in Produtos if produto.id == produto_id]

    # Filtrar as recomendações com base nas preferências
    produtos_recomendados = [p for p in produtos_recomendados if p.categoria in preferencias.categorias] # Preferencias de categorias
    produtos_recomendados = [p for p in produtos_recomendados if any(tag in preferencias.tags for tag in p.tags)] # Preferencias de tags

    return produtos_recomendados

# código omitido

Por fim, temos as rotas relacionadas às pessoas usuárias:

# código omitido

@app.post("/usuarios/", response_model=Usuario)
def criarusuario(nome: str):
    global ContadorUsuario
    NovoUsuario = Usuario(id=ContadorUsuario, nome=nome)
    Usuarios.append(NovoUsuario)
    ContadorUsuario += 1
    return NovoUsuario

# Rota para listar usuários

@app.get("/usuarios/", response_model=List[Usuario])
def listarusuarios():
    return Usuarios

# código omitido

A primeira é uma rota POST, semelhante à de produtos, que cria um usuário a partir do modelo Usuario e retorna o usuário criado. Também existe a rota app.get() para /usuarios, que retorna a lista completa de pessoas usuárias cadastrados.

Em resumo, é assim que nosso projeto está organizado e estruturado.

Rodando o projeto

Para rodar o projeto, abrimos o terminal utilizando o atalho "Ctrl + J". Para iniciar o servidor, utilizamos o comando uvicorn, que é o servidor responsável pela execução do projeto. O comando a ser utilizado é:

uvicorn app:app --reload

O primeiro app do comando é o nome do arquivo python, os :app é o nome da instância que criamos dentro da aplicação e o reload serve para reinicializar de forma automática.

Teclamos "Enter" para rodar e obtemos o retorno a seguir:

INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

INFO: Started reloader process [16744] using StatReload

INFO: Started server process [8196]

INFO: Waiting for application startup.

INFO: Application startup complete.

O uvicorn verifica se o projeto está correto, ou seja, se não há erros que possam impedir seu funcionamento. Ele então informa que o servidor está em execução na porta 8000. Pressionamos "Ctrl" e clicamos no link fornecido para ser redirecionado automaticamente para o navegador.

Endereço: 127.0.0.1:8000

{"mensagem": "Bem-vindo à API de Recomendação de Produtos"}

No navegador, a primeira página é a home (podemos inserir uma barra no final do endereço para confirmar), que é o endpoint principal, onde recebemos a mensagem de boas-vindas.

Analisando a estrutura da aplicação no FastAPI

Para verificar e trabalhar com a API, o FastAPI disponibiliza uma documentação em /docs:

127.0.0.1:8000/docs

Nela, temos acesso a todas as rotas criadas na API e todos os schemas ou modelos criados na aplicação.

Primeiro, clicamos em GET /Home, abaixamos a seta à direita, observe que temos informações como parâmetros. Para testar a aplicação, clicamos no botão "Try it out" à direita. Em "Servers" temos o servidor, mais abaixo clicamos no botão "Execute" na parte inferior central. Ele retorna a resposta, informando a URL solicitada e a resposta recebida, que foi um código 200, a mensagem de boas-vindas e o response headers:

Curl

curl -X 'GET' \

'http://127.0.0.1:8000/' \

-H 'accept: application/json'

Request URL

http://127.0.0.1:8000/

Response body

{

"mensagem": "Bem-vindo à API de Recomendação de Produtos"

}

Response headers

content-length: 61

content-type: application/json

date: Mon, 28 Oct 2024 18:02:56 GMT

server: uvicorn

Mais abaixo, informa as possíveis respostas, no caso é somente o 200.

Para fechar a aba do GET /Home, clicamos na seta à direita novamente. Agora, vamos testar o funcionamento da aplicação.

Testando o funcionamento da aplicação

Primeiro, criamos um produto. Clicamos na seta da aba POST /produtos/ Criarproduto, depois no botão "Try it out", e organizamos o JSON com os dados do produto. Por exemplo:

{
  "nome": "celular",
  "categoria": "eletrônico",
  "tags": 
    ["novo", "iphone"
    ]
}

Clicamos em "Execute" e recebemos a resposta com código 200 e o produto criado no responde body mais abaixo:

{

"nome": "celular",

"categoria": "eletrônico",

"tags": [

"novo",

"iphone"

],

"id": 1

}

Criaremos outro produto, um fone:

{
  "nome": "fone",
  "categoria": "eletrônico",
  "tags": 
    ["novo", "jbl"
    ]
}

Enviamos novamente clicando em "Execute". O ID muda conforme criamos novos produtos:

{

"nome": "fone",

"categoria": "eletrônico",

"tags": [

"novo",

"jbl"

],

"id": 2

}

Conforme criamos mais produtos, o id mudou.

Listando produtos salvos

Podemos listar os produtos salvos ao clicar na seta da aba GET /produtos/ Listarprodutos e, em seguida, selecionar o botão "Try it out" e clicar em "Execute". A partir disso, será exibida a lista de produtos, contendo os dois produtos previamente criados, acompanhados dos respectivos IDs:

[ {

"nome": "celular",

"categoria": "eletrônico",

"tags": [

"novo",

"iphone"

],

"id": 1

}, {

"nome": "fone",

"categoria": "eletrônico",

"tags": [

"novo",

"jbl"

],

"id": 2

}

]

Fechamos a aba clicando na seta.

Registrando no histórico

Para registrar o histórico, que requer o parâmetro usuario_id, é necessário primeiro criar um usuário. No POST de /usuarios/ Criarusuario, clicamos no botão "Try it out", inserimos, por exemplo, o nome "Laís" no "nome".

Executamos a ação clicando no botão "Execute". O sistema criará um usuário com ID 1 e o nome "Laís" no "Response body" mais abaixo:

{

"id": 1,

"nome": "lais"

}

Para confirmar a criação, podemos listar as pessoas usuárias expandindo a aba GET /usuarios/ Listarusuarios e verificar a inclusão do novo registro clicando em "Try it out" e depois em "Execute". Descendo um pouco a página, temos:

[

{

"id": 1,

"nome": "lais"

}

]

Confirmamos que foi criado com sucesso, podemos fechar essa aba.

Testando as recomendações

Agora, realizamos os testes das recomendações. Primeiramente, criamos um histórico de compras. Na aba POST destinada ao histórico de compras, clicamos no botão "Try it out", inserimos o usuario_id como 1 e o produto_id como 1 no json:

{

"produtos_ids": [

]

}

Executamos a ação clicando em "Execute". Como resposta, recebemos a mensagem "Histórico de compras atualizado" no response body. Isso significa que a informação foi salva.

Em seguida, na aba POST das recomendações (POST /recomendacoes/{usuario_id} Recomendarprodutos), novamente selecionamos o botão "Try it out", inserimos o usuario_id como 1, além das categorias "eletrônico" e das tags "novo" no json:

{
    "categorias": [
        "eletrônico"
    ],
    "tags": [
        "string"
    ]
}

Clicamos em "Execute". Lembrando que compramos apenas um celular. No response body, a recomendação recebida foi de um celular, que correspondia ao produto previamente criado:

{

"nome": "celular",

"categoria": "eletrônico",

"tags": [

"novo",

"iphone"

],

"id": 1

}

]

Através desses testes, conseguimos compreender o funcionamento da API, observando o fluxo de ações: criação de produtos, cadastro de usuário, registro de histórico de compras e, finalmente, a geração das recomendações.

Verificando no VSCode

No VSCode, todas as informações de requisições GET e POST são exibidas no terminal, incluindo as respostas 200 OK e as rotas acessadas:

Retorno abaixo parcialmente transcrito:

INFO: 127.0.0.1:62019 - "GET / HTTP/1.1" 200 OK

INFO: 127.0.0.1:62019 - "GET /favicon.ico HTTP/1.1" 404 Not Found

INFO: 127.0.0.1:62020 - "GET / HTTP/1.1" 200 OK

INFO: 127.0.0.1:62021 - "GET /docs HTTP/1.1" 200 OK

INFO: 127.0.0.1:62021 - "GET /openapi.json HTTP/1.1" 200 OK

Fechamos o terminal com o atalho "Ctrl + J". Ao fazer isso, notamos que o código está um pouco desorganizado, com várias variáveis, como modelos e rotas, agrupadas sem seguir um padrão claro de estrutura ou nomenclatura.

Próximo passo

Nosso objetivo é melhorar o projeto, aplicando boas práticas e reorganizando o código para aumentar sua legibilidade e facilitar contribuições, já que é um projeto de código aberto. A partir daqui, começaremos esse processo de melhoria!

Sobre o curso Python: aplicando boas práticas com PEP 8

O curso Python: aplicando boas práticas com PEP 8 possui 138 minutos de vídeos, em um total de 46 atividades. Gostou? Conheça nossos outros cursos de Python em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Python acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas