Vários ajudantes na escrita de códigos legais nos cercam, linter, typekchera, utilitário para encontrar vulnerabilidades, todos conosco. Estamos acostumados a usá-lo sem entrar em detalhes como uma "caixa preta". Por exemplo, poucas pessoas entendem os princípios do Pylint, uma dessas ferramentas indispensáveis para otimizar e melhorar o código Python.
Mas
Maxim Mazaev sabe o quanto é importante entender suas ferramentas e ele nos disse no
Moscow Python Conf ++ . Usando exemplos da vida real, ele mostrou como o conhecimento do dispositivo interno da Pylint e de seus plug-ins ajudou a reduzir o tempo de revisão do código, melhorar a qualidade do código e, geralmente, melhorar a eficiência do desenvolvimento. Abaixo está uma instrução de descriptografia.

Por que precisamos do Pylint?
Se você já o usa, pode surgir a pergunta: "Por que saber o que há dentro do Pylint, como esse conhecimento pode ajudar?"
Normalmente, os desenvolvedores escrevem código, iniciam o linter, recebem mensagens sobre o que melhorar, como tornar o código mais bonito e fazer as alterações propostas. Agora o código é mais fácil de ler e não tem vergonha de mostrar aos colegas.
Por um longo tempo, eles trabalharam exatamente da mesma maneira com Pylint no Cyan Institute, com pequenas adições: eles mudaram configurações, removeram regras desnecessárias e aumentaram o comprimento máximo da string.
Mas em algum momento eles se depararam com um problema, pelo qual eu tive que me aprofundar no Pylint e descobrir como ele funciona. Qual é esse problema e como resolvê-lo, continue lendo.
Sobre o palestrante: Maxim Mazaev (
barra invertida ), 5 anos em desenvolvimento, trabalha no CIAN. Aprende profundamente Python, assincronia e programação funcional.
Sobre cyan
Muitos acreditam que o CIAN é uma agência imobiliária com corretores de imóveis e ficam muito surpresos ao descobrir que, em vez de corretores, temos programadores.
Somos uma empresa técnica na qual não existem corretores de imóveis, mas existem muitos programadores.
- 1 milhão de usuários únicos por dia.
- O maior quadro de avisos para a venda e aluguel de imóveis em Moscou e São Petersburgo. Em 2018, eles entraram no nível federal e trabalham em toda a Rússia.
- Quase 100 pessoas na equipe de desenvolvimento, das quais 30 escrevem código Python diariamente.
Todos os dias, centenas e milhares de linhas de novo código entram em produção. Os requisitos para o código são bem simples:
- Código de qualidade decente.
- Homogeneidade estilística. Todos os desenvolvedores devem escrever código aproximadamente semelhante, sem "vinagrete" nos repositórios.
Para conseguir isso, é claro, você precisa de uma revisão de código.
Revisão de código
A revisão de código no CIAN ocorre em dois estágios:
- O primeiro estágio é automatizado . O robô Jenkins executa testes, executa Pylint e verifica a consistência da API entre microsserviços, uma vez que usamos microsserviços. Se, nesse estágio, os testes falharem ou o linter mostrar algo estranho, será uma ocasião para rejeitar a solicitação de recebimento e enviar o código para revisão.
- Se o primeiro estágio foi bem-sucedido, o segundo vem - a aprovação de dois desenvolvedores . Eles podem avaliar a qualidade do código em termos de lógica de negócios, aprovar uma solicitação pull ou retornar o código para revisão.
Problemas de revisão de código
A solicitação de recebimento pode não passar na revisão do código devido a:
- erros na lógica de negócios quando um desenvolvedor resolveu um problema de maneira ineficaz ou incorreta;
- problemas de estilo de código.
Quais poderiam ser os problemas de estilo se o linter verifica o código?
Todo mundo que escreve em Python sabe que existe um guia para escrever código
PEP-8 . Como qualquer padrão, o PEP-8 é bastante geral e, para nós, como desenvolvedores, isso não é suficiente. Quero especificar o padrão em alguns lugares e expandir em outros.
Portanto, criamos nossos arranjos internos sobre a aparência e o funcionamento do código e os chamamos de
"Recusar propostas Cian" .

“Recusar propostas Cian” - um conjunto de regras, agora existem cerca de 15. Cada uma dessas regras é a base para que a solicitação de recebimento seja rejeitada e enviada para revisão.
O que dificulta uma revisão produtiva do código?
Há um problema com nossas regras internas - o linter não sabe sobre elas, e seria estranho se ele soubesse - elas são internas.
O desenvolvedor que executa a tarefa deve sempre lembrar e manter as regras em mente. Se ele esquecer uma das regras, no processo de revisão de código os revisores apontarão o problema, a tarefa passará por revisão e o tempo de liberação da tarefa aumentará. Após a conclusão e a correção dos erros, os testadores precisam lembrar o que estava na tarefa, para mudar o contexto.
Isso cria um problema para o desenvolvedor e os revisores. Como resultado, a velocidade da revisão de código é reduzida drasticamente. Em vez de analisar a lógica do código, os testadores começam a analisar o estilo visual, ou seja, realizam o trabalho do linter: digitalizam o código linha por linha e procuram inconsistências na indentação no formato de importação.
Gostaríamos de nos livrar desse problema.
Mas não nos escreva seu linter?
Parece que o problema será resolvido por uma ferramenta que conhecerá todos os acordos internos e poderá verificar o código para sua implementação. Então, precisamos do nosso próprio linter?
Na verdade não. A ideia é estúpida, porque já usamos o Pylint. Este é um interface conveniente, apreciado pelos desenvolvedores e incorporado em todos os processos: é executado no Jenkins, gera relatórios bonitos que são completamente satisfeitos e vêm na forma de comentários na solicitação pull. Está tudo bem,
não é necessário um
segundo ponteiro .
Então, como resolver o problema se não queremos escrever nosso próprio linter?
Escreva um plug-in Pylint
Você pode escrever plugins para o Pylint, eles são chamados de damas. Sob cada regra interna, você pode escrever seu próprio verificador, que será verificado.
Considere dois exemplos de tais damas.
Exemplo No. 1
Em algum momento, verificou-se que o código contém muitos comentários no formato “TODO” - promete refatorar, excluir código desnecessário ou reescrevê-lo lindamente, mas não agora, mas mais tarde. Há um problema com esses comentários - eles absolutamente não o obrigam a nada.
O problema
O desenvolvedor escreveu uma promessa, exalou e ficou tranqüilo para executar a próxima tarefa.

Em resumo:
- comentários com promessas são suspensos ao longo dos anos e não são seguidos;
- código está cheio;
- a dívida técnica vem se acumulando há anos.
Por exemplo, um desenvolvedor há 3 anos prometeu remover algo após um lançamento bem-sucedido, mas o lançamento aconteceu em 3 anos? Talvez sim. Devo excluir o código neste caso? Esta é uma grande questão, mas provavelmente não.
Solução: escreva seu verificador para a Pylint
Você não pode proibir os desenvolvedores de escrever esses comentários, mas pode fazê-los fazer um trabalho extra: crie uma tarefa no rastreador para finalizar a promessa. Então definitivamente não vamos esquecê-la.
Precisamos encontrar todos os comentários do formulário TODO e garantir que cada um deles tenha um link para uma tarefa no Jira. Vamos escrever
O que é um verificador em termos de Pylint? Esta é uma classe que herda da classe base do verificador e implementa uma certa interface.
class TodoIssueChecker(BaseChecker): _ _implements_ _ = IRawChecker
No nosso caso, este é o
IRawChecker - o chamado verificador "bruto".
Um verificador bruto itera sobre as linhas de um arquivo e pode executar uma determinada ação em uma linha. No nosso caso, em cada linha, o verificador procurará algo semelhante a um comentário e um link para uma tarefa.
Para o verificador, você precisa determinar a lista de mensagens que ele emitirá:
msgs = { '9999': (' TODO ', issue-code-in-todo', ' ')}
A mensagem tem:
- a descrição é curta e longa;
- código do verificador e um nome mnemônico curto que determina que tipo de mensagem é.
O código da mensagem tem o formato "C1234", no qual:
- A primeira letra é claramente padronizada para diferentes tipos de mensagens: [C] onvention; [W] arning; [E] yog; [F] atal; [R] efactoring. Graças à carta, o relatório mostra imediatamente o que está acontecendo: um lembrete dos acordos ou problemas fatais que precisam ser abordados com urgência.
- 4 números aleatórios exclusivos do Pylint.
O código é necessário para desativar a verificação se for desnecessária. Você pode escrever Pylint: disable e um código alfanumérico curto ou nome mnemônico:
Os autores do Pylint recomendam abandonar o código alfanumérico e usar o mnemônico, é mais visual.
O próximo passo é definir um método chamado
process_module .

O nome é muito importante. O método deve ser chamado dessa maneira, porque o Pylint o chamará.
O parâmetro do
nó é passado para o módulo. Nesse caso, não importa o que é ou o tipo, é importante lembrar que o nó possui um método de
fluxo que retorna um arquivo linha por linha.
Você pode percorrer o arquivo e, para cada linha, verificar comentários e links para a tarefa. Se houver um comentário, mas nenhum link,
emita um aviso no formulário
'emitir código-em-todo' com o código do verificador e o número da linha. O algoritmo é bastante simples.
Registre o verificador para que o Pylint saiba sobre ele. Isso é feito pela função de
registro :
def register(linter: Pylinter) -> None: linter. register_checker ( TodoIssueChecker(linter) )
- Uma instância do Pylint entra na função.
- Ele chama o método register_checker.
- Passamos o verificador para o método
Um ponto importante: o módulo verificador deve estar no PYTHONPATH para que o Pylint possa importá-lo mais tarde.
Um verificador registrado é verificado por um arquivo de teste com comentários sem links para tarefas.
$ cat work.
Para o teste, execute o Pylint, passe o módulo para ele, use o parâmetro
load-plugins para passar no verificador e, dentro do linter, execute duas fases.
Fase 1. Inicialização do Plugin
- Todos os módulos com plugins são importados. O Pylint possui verificadores internos e externos. Todos eles se reúnem e são importados.
- Nós registramos - module.register (self) . Para cada verificador, a função de registro é chamada, onde a instância do Pylint é passada.
- As verificações são realizadas: para a validade dos parâmetros, para a presença de mensagens, opções e relatórios no formato correto.
Fase 2. Analisar o conjunto de damas
Após a fase 1, permanece uma lista completa de diferentes tipos de damas:
- Verificador AST;
- Verificador bruto;
- Verificador de token.

Na lista, selecionamos aqueles relacionados à interface bruta do verificador: verificamos quais verificadores implementam a interface IRawChecker e os tomamos por conta própria.
Para cada verificador selecionado, chame o
método checker.process_module (module) e execute a verificação.
Resultado
Execute o verificador no arquivo de teste novamente:
$ cat work.
Aparecerá uma mensagem informando que há um comentário com TODO e nenhum link para a tarefa.
O problema foi resolvido e, agora, no processo de revisão do código, os desenvolvedores não precisam escanear o código com os olhos, encontrar comentários, escrever um lembrete ao autor do código de que existe um acordo e é recomendável deixar um link. Tudo acontece automaticamente e a revisão do código é um pouco mais rápida.
Exemplo No. 2. argumentos-chave
Existem funções que aceitam argumentos posicionais. Se houver muitos argumentos, quando eles chamam a função, não está muito claro onde está o argumento e por que ele é necessário.
O problema
Por exemplo, temos uma função:
get_offer_by_cian_id( "sale", rue, 859483, )
O código tem
venda e
True, e não está claro o que eles significam. É muito mais conveniente quando funções nas quais existem muitos argumentos seriam chamadas apenas com argumentos nomeados:
get_offer_by_cian_id( deal_type="sale", truncate=True, cian_id=859483, )
Este é um bom código, no qual fica imediatamente claro onde está o parâmetro e não confundiremos sua sequência. Vamos tentar escrever um verificador que verifique esses casos.
O verificador "bruto" usado no exemplo anterior é muito difícil de escrever para esse caso. Você pode adicionar expressões regulares super complexas, mas esse código é difícil de ler. É bom que o Pylint permita escrever outro tipo de verificador com base na árvore de sintaxe abstrata do
AST , e nós o usaremos.
Letras sobre AST
Uma árvore de sintaxe AST ou abstrata é uma representação em árvore do código, onde o vértice são os operandos e as folhas são operadores.
Por exemplo, uma chamada de função, onde há um argumento posicional e dois argumentos nomeados, é transformada em uma árvore abstrata:

Existe um vértice com o tipo
Call e ele possui:
- atributos de função chamados func;
- uma lista de argumentos posicionais args, onde há um nó com o tipo Const e um valor de 112;
- lista de argumentos nomeados Palavras-chave.
A tarefa neste caso:
- Encontre no módulo todos os nós com o tipo Chamada (chamada de função).
- Calcule o número total de argumentos que a função leva.
- Se houver mais de 2 argumentos, verifique se não há argumentos posicionais no nó.
- Se houver argumentos posicionais, mostre um aviso.
ll( func=Name(name='get_offer'), args=[Const(value=1298880)], keywords=[ … ]))]
Do ponto de vista do Pylint, um verificador baseado em AST é uma classe que herda da classe verificador base e implementa a interface
IAstroidChecker :
class NonKeywordArgsChecker(BaseChecker): -_ _implements_ _ = IAstroidChecker
Como no primeiro exemplo, a descrição do verificador, o código da mensagem e o nome mnemônico curto são indicados na lista de mensagens:
msgs = { '9191': (' ', keyword-only-args', ' ')}
A próxima etapa é definir o método
visit_call :
def visit_call(self, node: Call) …
O método não precisa ser chamado assim. O mais importante é o prefixo visit_, e depois vem o nome do vértice que nos interessa, com uma pequena letra.
- O analisador AST percorre a árvore e, para cada vértice, verifica se a interface checkr visit_ <Name> está definida.
- Se sim, então chame.
- Recursivamente passa por todos os seus filhos.
- Ao sair de um nó, ele chama o método leave_ <Name>.
Neste exemplo, o método visit_call receberá um nó do tipo Call como uma entrada e verá se ele possui mais de dois argumentos e se argumentos posicionais estão presentes para emitir um aviso e passar o código para o próprio nó.
def visit_call(self, n): if node.args and len(node.args + node.keywords) > 2: self.add_message( 'keyword-only-args', node=node )
Registramos o verificador, como no exemplo anterior: transferimos a instância do Pylint, chamamos register_checker, passando o próprio verificador e iniciando-o.
def register(linter: Pylinter) -> None: linter.register_checker( TodoIssueChecker(linter) )
Este é um exemplo de uma chamada de função de teste em que existem 3 argumentos e apenas um deles é nomeado:
$ cat work. get_offers(1, True, deal_type="sale") $ Pylint work.py --load-plugins non_kwargs_checker …
Essa é uma função potencialmente chamada incorretamente do nosso ponto de vista. Inicie o Pylint.
A fase de inicialização do plug-in 1 é completamente repetida, como no exemplo anterior.
Fase 2. Módulo analisando na AST
O código é analisado em uma árvore AST. A análise é realizada pela
biblioteca Astroid .
Por que Astroid, não AST (stdlib)
O Astroid usa internamente não o módulo AST padrão do Python, mas o
analisador AST do tipo
typed_ast , caracterizado por suportar as
dicas do tipo PEP 484.
Typed_ast é um ramo do AST, um fork que se desenvolve em paralelo. Curiosamente, existem os mesmos erros que estão no AST e são reparados em paralelo.
from module import Entity def foo(bar):
Anteriormente, o Astroid usava o módulo AST padrão, no qual era possível encontrar o problema de usar as dicas definidas nos comentários usados no segundo Python. Se você verificar esse código através do Pylint, até um certo ponto, ele juraria na importação não utilizada, porque a classe Entity importada está presente apenas no comentário.
Em algum momento, Guido Van Rossum chegou ao Astroid no GitHub e disse: “Pessoal, você tem o Pylint, que jura nesses casos, e temos um analisador AST digitado que suporta tudo isso. Vamos ser amigos!
O trabalho começou a ferver! Dois anos se passaram, nesta primavera, a Pylint mudou para um analisador AST digitado e parou de xingar essas coisas. As importações de taiphints não são mais marcadas como não utilizadas.
O Astroid usa um analisador AST para analisar o código em uma árvore e, em seguida, faz algumas coisas interessantes ao construí-lo. Por exemplo, se você usar
import * , ele importa tudo com um asterisco e adiciona aos locais para evitar erros com importações não utilizadas.
Os plug-ins de transformação são usados nos casos em que existem alguns modelos complexos baseados em meta classes, quando todos os atributos são gerados dinamicamente. Nesse caso, o Astroid é muito difícil de entender o que se entende. Ao verificar, o Pylint jurará que os modelos não têm esse atributo quando for acessado e, usando os plugins Transform, você pode resolver o problema:
- Ajude o Astroid a modificar a árvore abstrata e entender a natureza dinâmica do Python.
- Complemente o AST com informações úteis.
Um exemplo típico é o
pylint-django . Ao trabalhar com modelos complexos de django, o linter geralmente jura por atributos desconhecidos. O Pylint-django apenas resolve esse problema.
Fase 3. Analise o conjunto de damas
Voltamos ao verificador. Novamente, temos uma lista de verificadores, a partir dos quais encontramos aqueles que implementam a interface do verificador AST.
Fase 4. Analisar verificadores por tipos de nós
Em seguida, encontramos métodos para cada verificador, eles podem ser de dois tipos:
- visit_ <Nome do nó>
- leév_ <Nome do nó>.
Seria bom saber quais nós você precisa chamar para um nó enquanto caminhava em uma árvore. Portanto, eles entendem o dicionário, onde a chave é o nome do nó, o valor é uma lista dos verificadores que estão interessados no fato de acessar esse nó.
_visit_methods = dict( < > : [checker1, checker2 ... checkerN] )
O mesmo com os métodos leave: uma chave na forma de um nome de nó, uma lista de verificadores que estão interessados no fato de sair desse nó.
_leave_methods = dict( < >: [checker1, checker2 ... checkerN] )
Inicie o Pylint. Ele mostra um aviso de que temos uma função em que há mais de dois argumentos e há um argumento posicional:
$ cat work. get_offers(1, True, deal_type="sale") $ Pylint work.py --load-plugins non_kwargs_checker C: 0, 0: c >2 (keyword-only-args)
O problema está resolvido. Agora, os programadores de revisão de código não precisam ler os argumentos da função; o linter fará isso por eles.
Economizamos nosso tempo , tempo para revisão de código e as tarefas são mais rápidas na produção.
E para escrever testes?
O Pylint permite realizar testes de unidade de damas e é muito simples. Do ponto de vista do linter, o verificador de teste se parece com uma classe que herda do abstrato
CheckerTestCase . É necessário indicar o verificador que está sendo verificado nele.
class TestNonKwArgsChecker(CheckerTestCase): CHECKER_CLASS = NonKeywordArgsChecker
Etapa 1. Criamos um nó AST de teste a partir da parte do código que estamos verificando.
node = astroid.extract_node( "get_offers(3, 'magic', 'args')" )
Etapa 2. Verifique se o verificador, entrando no nó, lança ou não lança a mensagem correspondente:
with self.assertAddsMessages(message): self.checker.visit_call(node)
Tokenchecker
Há outro tipo de verificador chamado
TokenChecker . Ele trabalha com o princípio de um analisador lexical. O Python possui um módulo de
tokenize que faz o trabalho de um scanner lexical e divide o código em uma lista de tokens. Pode ser algo como isto:

Nomes de variáveis, nomes de funções e palavras-chave se tornam tokens do tipo NAME e delimitadores, colchetes e dois pontos se tornam tokens do tipo OP. Além disso, existem tokens separados para recuo, avanço de linha e conversão reversa.
Como o Pylint funciona com o TokenChecker:
- O módulo em teste é tokenizado.
- Uma lista enorme de tokens é passada para todos os verificadores que implementam ITokenChecker e o método process_tokens (tokens) é chamado .
Não encontramos o uso do TokenChecker, mas existem alguns exemplos que o Pylint usa:
- Verificação ortográfica . Por exemplo, você pode pegar todos os tokens com o texto do tipo e examinar a alfabetização lexical, verificar palavras nas listas de palavras de parada, etc.
- Verifique recuos , espaços.
- Trabalhe com strings . Por exemplo, você pode verificar se o Python 3 não usa literais Unicode ou se apenas caracteres ASCI estão presentes na cadeia de bytes.
Conclusões
Ocorreu um problema com a revisão de código. Os desenvolvedores executaram o trabalho do linter, gastaram seu tempo em varreduras inúteis de código e informaram o autor sobre erros. Com a Pylint, nós:
- Transferiu verificações de rotina para o linter, implementou acordos internos nele.
- Maior velocidade e revisão do código de qualidade.
- Reduzido o número de solicitações de recebimento rejeitadas, e o tempo para passar tarefas na produção tornou-se menor.
Um simples verificador é escrito em meia hora e outro complexo em poucas horas. O verificador economiza muito mais tempo do que leva para escrever e luta por várias solicitações de recebimento não rejeitadas.
Você pode aprender mais sobre o Pylint e como escrever damas na
documentação oficial , mas em termos de escrever damas é bastante ruim. Por exemplo, sobre o TokenChecker, há apenas uma menção, mas não sobre como escrever o próprio verificador. Mais informações estão disponíveis
nas fontes Pylint no GitHub . Você pode ver o que as damas estão no pacote padrão e se inspirar para escrever suas próprias.
O conhecimento do design interno da Pylint economiza horas de trabalho, simplifica
desempenho e melhora o código. Economize seu tempo, escreva um bom código e
use linter.A próxima conferência Moscow Python Conf ++ será realizada em 5 de abril de 2019 e você já pode reservar um ingresso antecipado de birf agora. É ainda melhor coletar suas opiniões e solicitar um relatório, pois a visita será gratuita e os pães agradáveis serão um bônus, incluindo treinamento na preparação do relatório.
Nossa conferência é uma plataforma para encontrar pessoas que pensam da mesma forma, mecanismos do setor, para comunicar e discutir o que os desenvolvedores de Python adoram: back-end e Web, coleta e processamento de dados, AI / ML, testes, IoT. Como foi no outono, veja a reportagem em vídeo em nosso canal Python e assine o canal - em breve publicaremos os melhores relatórios da conferência para acesso gratuito.