Estilo de Código, Formatação e Linters para Pacotes Python#

Conclusões

  • A pyOpenSci exige que os autores sigam as diretrizes de formatação de código da PEP 8.

  • Configurar formatadores de código, como Black e isort, ajudará você a aplicar as diretrizes de estilo da PEP 8 e também manter um formato de código consistente e legível.

  • Algumas ferramentas geralmente utilizadas são: Black, isort, flake8 e Ruff.

  • Você também pode configurar hooks de pre-commit, que executarão formatadores de código localmente sempre que fizer um commit.

  • O precommit.ci é um bot que você pode adicionar ao seu repositório no GitHub. Ele aplica automaticamente a formatação de código em todos os PRs usando as ferramentas especificadas no arquivo pre-commit-config.yaml. Isso pode economizar bastante tempo e facilitar as contribuições de novos contribuidores.

  • Automação é uma coisa boa! Ao deixar as ferramentas de qualidade de código cuidarem do seu código, você pode focar em tarefas estruturais e de maior valor.

Um formato e estilo de código consistentes são úteis tanto para o seu pacote quanto para todo o ecossistema científico Python, porque usar formatos semelhantes torna o código mais fácil de ler.

Por exemplo, se você visse uma frase como esta sem espaços ou pontuação, seu cérebro levaria mais tempo para processá-la.

forinstanceifyousawasentencelikethisonewithoutany...

O processo de revisão por pares da pyOpenSci exige que você siga, as regras padrão de formatação da PEP 8 o máximo possível.

A pyOpenSci não exige que você use uma ferramenta específica de formatação de código. No entanto, buscamos consistência e legibilidade no estilo do código. A seguir, você encontrará uma discussão sobre:

  1. Os benefícios de usar linters e ferramentas de formatação de código no seu fluxo de trabalho

  2. Algumas ferramentas bastante utilizadas no ecossistema científico Python

  3. Configurar hooks de pre-commit e o bot pre-commit.ci para facilitar o uso de ferramentas de formatação de código nos fluxo de trabalho cotidiano e em pull requests no GitHub.

Use uma ferramenta (ou ferramentas) de formatação de código para facilitar sua vida#

Sugerimos que você use uma ferramenta de formatação de código, ou um conjunto delas, porque aplicar manualmente todas as especificações de formatação da PEP 8 consome muito tempo dos mantenedores e também pode se tornar uma barreira para novos contribuidores em potencial. Ferramentas de formatação de código reformatam automaticamente seu código, seguindo os padrões da PEP 8 e aplicando decisões de estilo consistentes em todo o projeto.

Configurar um conjunto de ferramentas de formatação de código irá:

  • Economizar tempo seu e da equipe de mantenedores na correção de inconsistências com a PEP 8.

  • Garantir que a formatação e o estilo sejam consistentes em toda a sua base de código.

  • Evitar discussões longas com contribuidores e outros mantenedores sobre preferências pessoais de formatação de código durante revisões.

  • Evitar alterações puramente visuais na base de código, permitindo que as revisões de código foquem no que realmente agrega valor

Muitos pacotes usam um conjunto de ferramentas para aplicar regras de formatação de código, eliminando o trabalho manual de implementar esses requisitos de formatação.

Uma formatação de código consistente entre pacotes dentro do ecossistema (científico) Python também torna o código, de forma geral, mais fácil de ler, entender e contribuir.

Linting vs. formatação e estilo#

Antes de começarmos, vamos esclarecer algumas definições.

Linting de código#

Um linter de código é uma ferramenta que analisa seu código e identifica erros ou problemas. Normalmente, um linter não modifica seu código. Ele informa qual é o erro e em qual linha ele foi encontrado. O Flake8, discutido abaixo, é um exemplo de linter de código bastante utilizado.

Formatadores de código (e estilizadores)#

Formatadores de código reformatam seu código automaticamente. Formatadores de código voltados para Python geralmente seguem os padrões da PEP 8. No entanto, eles também tomam decisões de estilo para garantir a consistência do código.

O Black é um exemplo de formatador de código bastante utilizado. O Black aplica os padrões da PEP 8 e também toma decisões sobre aspectos como o uso consistente de aspas duplas em strings e o espaçamento de itens em listas.

Você aprenderá mais sobre o Black abaixo.

Ferramentas de linting, formatação e estilo de código#

Black#

O Black é um formatador de código. O Black corrigirá automaticamente (e descaradamente) problemas de espaçamento e garantirá que a formatação do código seja consistente em todo o seu pacote. O Black também geralmente segue as diretrizes de estilo da PEP 8, com algumas exceções. Alguns exemplos dessas exceções estão abaixo:

  • O Black usa, por padrão, um comprimento de linha de 88 caracteres (79 + 10%), em vez dos 79 caracteres especificados pela PEP 8. No entanto, o comprimento da linha é uma configuração que pode ser alterada manualmente na configuração do Black.

  • O Black não ajustará o comprimento das linhas em comentários ou docstrings.

  • Essa ferramenta não revisará nem corrigirá a ordem dos imports (você precisará do isort ou ruff para fazer isso — veja abaixo).

Dica

Se você tiver interesse em ver como o Black formatará seu código, pode usar o Black playground

Usar um formatador de código como o Black deixará você com mais tempo para trabalhar na funcionalidade do código, em vez de se preocupar com formatação.

Flake8#

Para seguir os padrões de formatação pep8 do Python, talvez você queira adicionar o flake8 ao seu conjunto de ferramentas de formatação de código.

O flake8 irá:

  • Sinalizar todas as linhas do seu código que ultrapassem 79 caracteres (incluindo linhas em docstrings e comentários)

  • Sinalizar problemas de espaçamento que entrem em conflito com as diretrizes da PEP 8, como ausência de espaços após vírgulas

O Flake8 também sinaliza imports não utilizados e variáveis declaradas mas não utilizadas nos seus módulos.

Abaixo, você pode ver a saída da execução de flake8 filename.py na linha de comando para um arquivo Python dentro de um pacote chamado stravalib.

O padrão de comprimento de linha da PEP 8 é de 79 caracteres.

Observe que o flake8 retorna uma lista de problemas encontrados no módulo model.py na linha de comando. O arquivo Python em si não é modificado. Usando essa saída, você pode corrigir cada problema manualmente, linha por linha.

(stravalib-dev) username@computer stravalib % flake8 stravalib/model.py
stravalib/model.py:8:1: F401 'os' imported but unused
stravalib/model.py:29:80: E501 line too long (90 > 79 characters)
stravalib/model.py:34:80: E501 line too long (95 > 79 characters)
stravalib/model.py:442:80: E501 line too long (82 > 79 characters)
stravalib/model.py:443:39: E231 missing whitespace after ','
stravalib/model.py:493:20: E225 missing whitespace around operator
stravalib/model.py:496:80: E501 line too long (82 > 79 characters)

Isort#

Imports em Python se referem aos pacotes Python que um módulo do seu pacote requer. Os imports devem sempre estar localizados no topo de cada módulo Python do seu pacote.

A PEP 8 possui padrões específicos para a ordem desses imports. Esses padrões estão listados abaixo:

Os imports devem ser agrupados na seguinte ordem:

  • Imports da biblioteca padrão.

  • Imports de bibliotecas de terceiros.

  • Imports específicos da aplicação/biblioteca local.

Embora o flake8 identifique imports não utilizados no seu código, ele não corrigirá nem identificará problemas relacionados à ordem dos imports.

O isort identificará onde os imports no seu código estão fora de ordem. Em seguida, ele modificará seu código, reorganizando automaticamente todos os imports. Isso deixa você com uma preocupação a menos ao limpar seu código.

Exemplo de uso do isort#

Imports do código antes da execução do isort:

Abaixo, pandas é um pacote de terceiros, typing é um pacote central do Python distribuído com o Python, e examplePy.temperature é um módulo do próprio projeto, o que significa que ele pertence ao mesmo pacote do arquivo que está fazendo o import. Observe também que não há espaços nos imports listados abaixo.

from typing import Sequence

import pandas

from examplePy.temperature import fahrenheit_to_celsius


A partir da raiz do projeto, execute:

isort src/examplePy/temporal.py

Imports do arquivo Python temporal.py após a execução do isort

from typing import Sequence

import pandas

from examplePy.temperature import fahrenheit_to_celsius


Ruff#

O Ruff é uma nova adição ao ecossistema de qualidade de código, ganhando bastante adoção desde seu lançamento. O ruff é ao mesmo tempo um linter e um formatador de código para Python, com o objetivo de substituir várias ferramentas por meio de uma única interface. Assim, o ruff pode ser usado como substituto de todas as outras ferramentas mencionadas aqui, ou em complemento a algumas delas.

O ruff possui alguns recursos interessantes que o diferenciam de outros linters:

  • Configuração do linter no pyproject.toml

  • Várias centenas de regras incluídas, muitas das quais podem ser corrigidas automaticamente

  • Explicação das regras, veja F403 como exemplo

  • Tempo de execução rápido, permitindo um ciclo de feedback ágil mesmo em projetos grandes.

Aqui está uma configuração simples para começar a usar o ruff. Ela deve ser adicionada ao seu pyproject.toml:

[tool.ruff]
select = [
    "E", # pycodestyle errors
    "W", # pycodestyle warnings
    "F", # pyflakes. "E" + "W" + "F" + "C90" (mccabe complexity) is equivalent to flake8
    "I", # isort
]

Dependendo do seu projeto, talvez você queira adicionar o seguinte para ordenar corretamente os imports:

[tool.ruff.isort]
known-first-party = ["examplePy"]

Como usar formatadores de código no seu fluxo de desenvolvimento local#

Linters, formatadores de código e seus editores e IDEs favoritos#

Linters podem ser executados como ferramentas de linha de comando, como mostrado acima. Eles também podem ser executados dentro da sua ferramenta de desenvolvimento favorita (por exemplo, VSCode, PyCharm, etc). Por exemplo, você pode preferir que ferramentas como Black e isort sejam executadas ao salvar um arquivo. Em alguns editores, também é possível configurar atalhos que executam suas ferramentas favoritas de formatação de código sob demanda.

Use hooks de pre-commit para executar formatadores de código e linters nos commits#

Você também pode configurar um pre-commit hook no repositório do seu pacote Python.

Um pre-commit hook é uma ferramenta que permite que uma ação (ou ações) seja acionada quando você aplica um commit ao seu repositório Git.

Exemplo de fluxo de trabalho com pre-commit hook#

O fluxo de trabalho do pre-commit funciona assim: você digita e executa:

git commit -m "message here" na linha de comando

  • Assim que você pressionar Enter, o pre-commit executará todas as ferramentas configuradas no arquivo .pre-commit-config.yaml.

  • Se as ferramentas configuradas no pre-commit hook forem executadas com sucesso sem fazer alterações ou encontrar erros no seu código, o commit será aplicado ao repositório.

  • Se as ferramentas configuradas no hook encontrarem erros nos seus arquivos, o commit NÃO será aplicado ao repositório. Lembre-se da discussão acima: um formatador de código como o Black executará e reformatará seu código. Um linter como o flake8 fornecerá uma saída detalhando onde existem problemas de sintaxe no seu código. Você então precisará corrigir esses problemas manualmente.

  • Depois que todas as correções forem aplicadas, você poderá adicionar (stage) novamente os arquivos para commit e executar o commit outra vez.

Diagrama mostrando as etapas de um fluxo de trabalho com pre-commit, da esquerda para a direita.

O fluxo de trabalho do pre-commit começa quando você adiciona arquivos com alterações para serem colocados em stage no Git. Em seguida, você executa git commit. Quando isso acontece, os hooks de pre-commit são executados. Neste exemplo, o Black, que é o formatador de código, e o flake8, que é um linter, são executados. Se todos os arquivos passarem pelas verificações do Black e do flake8, o commit será registrado. Caso contrário, o commit será cancelado. Você precisará corrigir quaisquer problemas apontados pelo flake8 e então adicionar novamente os arquivos ao stage para commit. Fonte da imagem#

Importante

Se você tiver uma base de código Python com múltiplos mantenedores trabalhando ativamente no código e pretende usar uma ferramenta como o Black, certifique-se de coordenar isso com sua equipe. Um commit inicial que aplique o Black em todo o pacote provavelmente alterará uma parte significativa do código. Isso pode gerar conflitos de merge em PRs abertos e novos antes que as alterações sejam mescladas.

Verificações gerais de pre-commit#

Além de executar ferramentas, o Pre-commit também possui um conjunto de hooks de formatação embutidos que você pode usar. Alguns deles, como trailing-whitespace, também podem ser úteis no seu fluxo de trabalho com pre-commit para garantir arquivos de código limpos e organizados.

Abaixo está um exemplo de arquivo pre-commit-config.yaml mostrando como tudo isso pode ser configurado.

Pre-commit.ci#

O Pre-commit.ci é um bot que pode se tornar seu novo melhor amigo. Quando configurado em um repositório, esse bot pode ser ajustado para fazer o seguinte:

  • Ele verificará todos os pull requests usando todas as configurações dos hooks de pre-commit

  • Se desejar, ele também enviará um pull request para seu repositório com as correções do pre-commit, economizando seu tempo e o de novos contribuidores ao evitar a necessidade de reformatar um PR com problemas de formatação.

  • Você também pode chamar o bot em qualquer pull request para executar e corrigir o código.

O bot pre-commit.ci usa o mesmo arquivo pre-commit-config.yaml que você usa para configurar o pre-commit localmente.

Configurar um bot como esse pode ser valioso porque:

  • Isso pode facilitar a vida dos mantenedores, já que eles não precisarão mais se preocupar em corrigir a formatação do código. O bot fará esse trabalho por eles.

  • Também pode facilitar para novos contribuidores, já que eles não precisarão configurar o pre-commit localmente nem se preocupar com linting do código. Eles podem até fazer pequenas correções diretamente no GitHub sem preocupações.

Configurando um hook de pre-commit no Git#

Para configurar o pre-commit localmente, você precisa fazer 3 coisas:

  1. Instalar o pre-commit (e incluí-lo como dependência de desenvolvimento no seu repositório)

python -m pip install pre-commit

# or

conda install -c conda-forge pre-commit
  1. Criar um arquivo .pre-commit-config.yaml na raiz do diretório do seu pacote.

Abaixo está um exemplo de arquivo .pre-commit-config.yaml que pode ser usado para configurar o hook de pre-commit e o bot pre-commit.ci, caso você escolha implementar isso também.

repos:
  - repo: https://github.com/PyCQA/isort
    rev: dac090ce4d9ee313d086e2e89ab1acb8c2664fa1 # frozen: 9.0.0a3
    hooks:
      - id: isort
        files: \.py$
  # Misc commit checks using built in pre-commit checks
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
    # ref: https://github.com/pre-commit/pre-commit-hooks#hooks-available
    hooks:
      # Autoformat: Makes sure files end in a newline and only a newline.
      - id: end-of-file-fixer
      # Lint: Check for files with names that would conflict on a
      # case-insensitive filesystem like MacOS HFS+ or Windows FAT.
      - id: check-case-conflict
      - id: trailing-whitespace
  # Linting: Python code (see the file .flake8)
  - repo: https://github.com/PyCQA/flake8
    rev: c48217e1fc006c2dddd14df54e83b67da15de5cd # frozen: 7.3.0
    hooks:
      - id: flake8
  # Black for auto code formatting
  - repo: https://github.com/psf/black
    rev: c6755bb741b6481d6b3d3bb563c83fa060db96c9 # frozen: 26.3.1
    hooks:
      - id: black
        language_version: python3.10
# Tell precommit.ci bot to update code format tools listed in the file
# versions every quarter
# The default it so update weekly which is too many new pr's for many
# maintainers (remove these lines if you aren't using the bot!)
ci:
  autoupdate_schedule: quarterly

Esse arquivo especifica um hook que será acionado automaticamente antes de cada git commit; neste caso, ele especifica o flake8 usando a versão 6.0.0.

  1. Instale seus hooks de pre-commit usando pre-commit install. Isso instalará todos os hooks especificados no arquivo yaml do pre-commit no seu ambiente.

Depois de concluir as etapas acima, você estará pronto para começar a trabalhar no seu código. O pre-commit será executado sempre que você rodar git commit.

Resumo#

A pyOpenSci sugere configurar um linter e uma ferramenta de estilo de código para o seu pacote, independentemente de você usar hooks de pre-commit, CI ou outra infraestrutura para gerenciar a formatação do código. Configurar essas ferramentas fornecerá feedback automático sobre a estrutura do seu código enquanto você (ou um colaborador) o escreve. E usar uma ferramenta como o Black, que formata o código automaticamente, reduz o esforço necessário para tomar decisões relacionadas à formatação e ao estilo do código.