Executando verificação de segurança...
1

Configurando o PyLint e YAPF no VSCode

https://www.lucasmantuan.com.br
https://github.com/lucasmantuan
https://www.linkedin.com/in/lucasmantuan

Introdução

Antes de iniciarmos a explicação das ferramentas em si, é importante esclarecer qual é a diferença entre um linter e um formatador de código. Sabemos que, para escrever um bom código, é preciso mantê-lo consistente e, ao mesmo tempo, compreensível para outros desenvolvedores.

O formatador de código é a ferramenta que analisa e reestrutura o seu código de acordo com um padrão pré-estabelecido. Ele pode seguir regras definidas por você ou pela comunidade.

Há vários formatadores disponíveis, como o Black e o AutoPEP8, mas, em nosso tutorial, vamos utilizar o YAPF, que, embora não seja oficial, é mantido pelo Google. A escolha pelo YAPF se deve à possibilidade de adicionar configurações personalizadas, ao contrário de outros formatadores.

Escolhi o YAPF porque tenho uma tendência a utilizar ferramentas mais personalizáveis, pois gosto de ter um controle mais refinado sobre aquilo que estou desenvolvendo.

Já o linter, diferente do formatador, tem como foco identificar erros e más práticas, como variáveis não utilizadas, funções muito complexas, nomes de variáveis que não seguem padrões, imports não utilizados, entre outros pontos, já nas etapas iniciais do desenvolvimento. Novamente, há diversas ferramentas de linting para Python, entre elas o Flake8, o Ruff e o PyLint. Vamos trabalhar com o PyLint, pois ele também permite, assim como o YAPF, diversas configurações personalizadas.

Por exemplo, no código abaixo, tanto o linter quanto o formatador iriam apontar problemas: o formatador, porque estamos usando espaços inadequados em (a, b), e o linter, porque a variável d não foi definida (além de outros erros, como veremos mais adiante).

def soma( a, b ):
	c = a + b
	return d

Instalação das Ferramentas

Com o Python e o VSCode já instalados, devemos proceder à instalação das ferramentas e plugins. Podemos instalar as ferramentas tanto em um ambiente virtual Python quanto de forma global. Em ambos os casos, é possível ter configurações personalizadas por projeto, através de arquivos de configuração específicos de cada ferramenta.

Entretanto, é recomendado instalar as ferramentas em ambientes virtuais para garantir melhor isolamento e facilitar a reprodução das configurações por outros desenvolvedores, evitando conflitos com outros projetos.

  • Primeiro, criamos um ambiente virtual e, em seguida, ativamos o ambiente com os comandos abaixo:
python -m venv .venv
source .venv/bin/activate
  • Depois, instalamos via pip tanto o Pylint quanto o YAPF (ao instalar, observe as versões recomendadas pelos plugins para garantir compatibilidade) e registramos as versões instaladas com os comandos abaixo:
pip install yapf pylint
pip freeze > requirements.txt

Configuração do Pylint

Com tudo instalado, precisamos configurar o Pylint. Podemos adicionar alguns parâmetros diretamente no VSCode, mas acredito que a melhor abordagem seja a criação do arquivo .pylintrc na raiz do projeto, com todos os parâmetros desejados. Para criar um template do arquivo, digite o comando abaixo:

pylint --generate-rcfile > .pylintrc

Dentro desse arquivo, podemos especificar o estilo de nomes para argumentos, atributos, classes, constantes, funções etc. Por exemplo, o parâmetro abaixo define o estilo correto para nomes de funções.

function-naming-style=snake_case 

Também podemos especificar o número máximo de argumentos para uma função, o número máximo de expressões booleanas dentro de um if, o número máximo de instruções return dentro do corpo de uma função, o número máximo de linhas em um módulo, o número máximo de blocos aninhados em uma função, entre outros. No exemplo abaixo, definimos o máximo de blocos aninhados em uma função ou método.

max-nested-blocks=5 

Ao executarmos o Pylint via terminal (pylint exemplo.py) em nosso simples código anterior (depois mostraremos como fazer a integração via VSCode), recebemos uma lista de correções que devem ser aplicadas, cada uma com um código de erro de 5 dígitos e a categoria da mensagem.

exemplo.py:3:0: C0304: Final newline missing (missing-final-newline)
exemplo.py:1:0: C0114: Missing module docstring (missing-module-docstring)
exemplo.py:1:0: C0116: Missing function or method docstring (missing-function-docstring)
exemplo.py:3:11: E0602: Undefined variable 'd' (undefined-variable)
exemplo.py:2:4: W0612: Unused variable 'c' (unused-variable)
Your code has been rated at 0.00/10

As categorias são C para Convention, R para Refactor, W para Warning, E para Error e F para Fatal, todas definidas em nosso arquivo de configuração. Ainda recebemos a pontuação de nosso código, que, em nosso exemplo, foi 0 de 10, representando o quão bom ou ruim é o código. Aplicando as sugestões anteriores, temos então o seguinte código refatorado:

"""
Módulo exemplo com função de soma.
"""

def soma(a, b):
    """
    Retorna a soma de dois números.
    """
    return a + b

Cabe aqui ressaltar duas configurações interessantes que podem ser definidas: a primeira é a configuração de mensagens personalizadas de aviso e a segunda é o uso de regex para diversas configurações do Pylint. Além disso, é possível adicionar arquivos que devem ser ignorados dentro do parâmetro ignore do arquivo .pylintrc.

Configuração do YAPF

Assim como o Pylint, no YAPF podemos configurar suas opções diretamente no arquivo de configurações do VSCode ou em um arquivo de configuração separado. Pelos mesmos motivos anteriores, vamos trabalhar com um arquivo de configuração chamado .style.yapf na raiz do projeto.

Podemos configurar algumas opções mais básicas e genéricas, além de opções mais avançadas de formatação. Por exemplo, nas configurações abaixo, estamos utilizando como base o guia de estilo do Facebook, com 4 colunas de recuo e o comprimento máximo da linha.

[style] 
based_on_style = facebook 
indent_width = 4
column_limit = 110

Mas há diversas configurações interessantes que podemos aplicar, como, por exemplo, a quantidade de linhas em branco entre funções, classes e importações, o posicionamento e o espaçamento de docstrings e a organização visual de dicionários e listas.

Também há configurações de alinhamento e a indentação de valores em dicionários, a forma como parênteses e colchetes são agrupados ou indentados, entre outros aspectos.

Por exemplo, vamos alterar nossa função de exemplo, adicionando uma nova função chamada subtracao com algumas formatações equivocadas. Além disso, vamos adicionar o parâmetro blank_lines_around_top_level_definition = 2, que adiciona duas linhas em branco entre funções e classes.

def subtracao( a , b ):
	"""
	Retorna a soma de dois números.
	"""
	return a-b

Após a execução do YAPF via terminal com o comando yapf --style=.style.yapf -i exemplo.py, temos o código abaixo formatado:

"""
Módulo exemplo com função de soma e subtração.
"""


def soma(a, b):
	"""
	Retorna a soma de dois números.
	"""
	return a + b
  

def subtracao(a, b):
	"""
	Retorna a soma de dois números.
	"""
	return a - b

Podemos adicionar arquivos ou padrões que devem ser ignorados pelo YAPF dentro do arquivo .yapfignore na raiz do projeto.

Execução das Ferramentas no Projeto

Como ambas as ferramentas possuem diversas configurações personalizáveis, pode-se, para evitar conflitos, ajustar regras específicas no Pylint que eventualmente gerem incompatibilidades com o YAPF. Por exemplo, se estamos utilizando column_limit = 110 no YAPF, é necessário configurar max-line-length=110 no Pylint.

Outros pontos de atenção dizem respeito às linhas em branco, indentação e alinhamento. No exemplo, ao configurarmos indent_width = 4 no YAPF, devemos ajustar no Pylint a opção indent-string=' '.

O ideal é definir um estilo de código no Pylint e ajustar o YAPF para trabalhar conforme esse estilo. No entanto, vale lembrar que nem sempre as configurações terão correspondência exata, podendo ser necessário adaptar o Pylint para que ele aceite o formato gerado pelo YAPF, especialmente se houver alguma regra de estilo personalizada.

Integração com VSCode

O primeiro passo para a integração com o VSCode é instalar os plugins de cada uma das ferramentas. Para o Pylint, podemos instalar o plugin Pylint da Microsoft e, para o YAPF, a extensão yapf de EeyoreLee. Com eles instalados e habilitados, configuramos ambos alterando o arquivo settings.json do VSCode.

Para ativar o plugin YAPF como formatador padrão, basta adicionar "editor.defaultFormatter": "eeyore.yapf". Para ativar o Pylint como linter padrão, adicionamos "pylint.enabled": true e, se desejar que o Pylint faça o lint ao digitar, utilize a opção "pylint.lintOnChange": true.

Outra opção que vale a pena ser ativada é "editor.formatOnSave": true, que aplica a formatação padrão sempre que salvamos o documento.

Exemplo

Para exemplificar, segue o código de uma classe, tanto com erros de lint quanto erros de formatação:

import sys, os, math, datetime
class pessoa:
  def __init__( self, nome, idade ):
      self.nome = nome
      self.idade = idade
  def apresentacao(self ):
   print("Olá, meu nome é " + self.nome + " e eu tenho " + str(self.idade) + " anos.")
   print("Hoje é:", datetime.datetme.now())
   raiz_idade = math.sqr(self.idade)
   return raiz_idade

def    cria_pessoa():
   p = pessoa( "Lucas", 30 )
   p.apresentacao()
   print(sys.version)
   d = {('usuario', 'id'): 123, 'usuario_dados': {'nome': p.nome, 'idade': p.idade, 'email': {'principal': '[email protected]'}}}
   print(d)

Assim que inserimos este código e o salvamos, o YAPF faz a formatação. O código fica mais organizado, mas ainda apresenta diversos erros de lint:

import sys, os, math, datetime


class pessoa:
    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade

    def apresentacao(self):
        print("Olá, meu nome é " + self.nome + " e eu tenho " + str(self.idade) + " anos.")
        print("Hoje é:", datetime.datetme.now())
        raiz_idade = math.sqr(self.idade)
        return raiz_idade


def cria_pessoa():
    p = pessoa("Lucas", 30)
    p.apresentacao()
    print(sys.version)
    d = {
        ('usuario', 'id'): 123,
        'usuario_dados': {
            'nome': p.nome,
            'idade': p.idade,
            'email': {
                'principal': '[email protected]'
            }
        }
    }
    print(d)

Quando rodamos o Pylint via linha de comando, recebemos o seguinte retorno de erros:

classe.py:1:0: C0114: Missing module docstring (missing-module-docstring)
classe.py:1:0: C0410: Multiple imports on one line (sys, os, math, datetime) (multiple-imports)
classe.py:4:0: C0115: Missing class docstring (missing-class-docstring)
classe.py:4:0: C0103: Class name "pessoa" doesn't conform to PascalCase naming style (invalid-name)
classe.py:9:4: C0116: Missing function or method docstring (missing-function-docstring)
classe.py:11:26: E1101: Module 'datetime' has no 'datetme' member; maybe 'datetime'? (no-member)
classe.py:12:21: E1101: Module 'math' has no 'sqr' member; maybe 'sqrt'? (no-member)
classe.py:4:0: R0903: Too few public methods (1/2) (too-few-public-methods)
classe.py:16:0: C0116: Missing function or method docstring (missing-function-docstring)
classe.py:1:0: W0611: Unused import os (unused-import)
Your code has been rated at 0.00/10

Corrigindo esses pontos, temos o código final:

"""
Módulo que define a classe Pessoa e uma função para criar uma instância dessa classe.
"""

import sys
import math
import datetime


class Pessoa:
    """
    Classe Pessoa que representa uma pessoa com nome e idade.
    """
    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade

    def apresentacao(self):
        """
        Método que imprime uma apresentação da pessoa.
        """
        print("Olá, meu nome é " + self.nome + " e eu tenho " + str(self.idade) + " anos.")
        print("Hoje é:", datetime.datetime.now())

    def raiz_idade(self):
        """
        Método que calcula a raiz quadrada da idade da pessoa.
        """
        raiz_idade = math.sqrt(self.idade)
        return raiz_idade


def cria_pessoa():
    """
    Função que cria uma instância da classe Pessoa e retorna o objeto.
    """
    p = Pessoa("Lucas", 30)
    p.apresentacao()
    print(sys.version)
    d = {
        ('usuario', 'id'): 123,
        'usuario_dados': {
            'nome': p.nome,
            'idade': p.idade,
            'email': {
                'principal': '[email protected]'
            }
        }
    }
    print(d)

Referências

Carregando publicação patrocinada...
2

Bacana, mas eu acho mais válido ferramentas que já aplicam correção mesmo no código do que apenas a indicação dos erros. Por vezes, até o "funcionar" prevalece sobre estética.