Extensão Mypy com plugins

Boa tarde amigos E continuamos a aumentar a intensidade do lançamento de novos cursos e agora temos o prazer de anunciar que as aulas no curso "Desenvolvedor da Web em Python" começarão no final de abril. A esse respeito, tradicionalmente compartilhamos a tradução de material útil. Vamos começar.

O Python é conhecido por ser uma linguagem de digitação dinâmica. É muito fácil escrever estruturas do tipo DSL que são difíceis de analisar com ferramentas de verificação de tipo estático. Apesar disso, com as mais recentes inovações funcionais do mypy , como protocolos e tipos literais , além de suporte básico para metaclasses e suporte a descritores, geralmente podemos obter tipos exatos, mas ainda é difícil evitar falsos positivos e outros fatores negativos. Para resolver esse problema e evitar a necessidade de personalizar o sistema de tipos para cada estrutura, o mypy suporta um sistema de plug-in . Plugins são módulos em Python que fornecem ganchos de plugins que o mypy chamará ao verificar os tipos de classes e funções que interagem com uma biblioteca ou estrutura. Assim, é possível distinguir com mais precisão o tipo da função retornada, que é extremamente difícil de expressar ou gerar automaticamente alguns métodos de classe para refletir os efeitos do decorador. Para saber mais sobre a arquitetura do sistema de plug-in e ver a lista completa de recursos, consulte a documentação .



Plugins relacionados para a biblioteca padrão

O Mypy vem com plugins padrão para implementar funções e classes básicas, bem como dataclasses ctypes , contextlib e dataclasses . Ele também inclui plugins para attrs (historicamente, foi o primeiro plug-in de terceiros criado para mypy ). Esses plugins permitem ao mypy determinar com mais precisão os tipos e verificar o código corretamente, usando essas funções da biblioteca. Para mostrar isso com um exemplo, dê uma olhada em um trecho de código:

 from dataclasses import dataclass from typing import Generic, TypeVar @dataclass class TaggedVector(Generic[T]): data: List[T] tag: str position = TaggedVector([0, 0, 0], 'origin') 

Acima, get_class_decorator_hook() é chamado quando a classe é definida. Isso adiciona métodos gerados automaticamente, incluindo __init__() , ao corpo da função. Mypy usa esse construtor para calcular corretamente TaggedVector[int] como o tipo de position . Como você pode ver no exemplo, os plugins funcionam mesmo com classes genéricas.

Aqui está outro pedaço de código:

 from contextlib import contextmanager @contextmanager def timer(title: str) -> Iterator[float]: ... with timer(9000) as tm: ... 

Aqui, get_function_hook() fornece o tipo de retorno exato para o decorador do contextmanager , para que as chamadas para a função decorada possam ser verificadas quanto à conformidade com um tipo específico. Agora mypy pode reconhecer o erro: o argumento para timer() deve ser uma string.

Uma combinação de plugins e stubs

Além de usar funções dinâmicas Python, as estruturas frequentemente enfrentam o problema de ter grandes APIs. Mypy precisa de arquivos stub para bibliotecas para testar o código que usa essas bibliotecas (apenas se a biblioteca não contiver anotações internas, o que não é tão comum). Distribuir stubs para grandes estruturas com datilografado não é uma prática comum:

  • Typeshed tem um ciclo de liberação relativamente lento (enviado com mypy ).
  • Os stubs incompletos podem levar a chamadas falsas, o que será extremamente difícil de evitar.
  • Não basta misturar stubs de diferentes versões datilografadas .

Os pacotes de stub introduzidos no PEP 561 fazem o seguinte:

  • Os desenvolvedores podem liberar pacotes stub quantas vezes quiserem.
  • Os usuários que não optaram por usar o pacote não verão falsos positivos.
  • Você pode instalar com segurança versões arbitrárias de vários pacotes stub diferentes.

Além disso, o pip permite combinar vários stubs para bibliotecas e os plugins mypy correspondentes em uma distribuição. Os stubs da estrutura mypy ou do plug-in correspondente podem ser facilmente desenvolvidos e agrupados em uma distribuição, o que é extremamente útil, pois os plug-ins preenchem definições ausentes ou imprecisas nos stubs.

O exemplo mais recente desse pacote é o stub e o plug-in SQLAlchemy , com o primeiro lançamento público da versão 0.1, publicado há algum tempo no PyPI. Apesar de esse projeto estar na versão Alpha inicial, podemos usá-lo com segurança no DropBox para melhorar a verificação de tipo. O plug-in entende as declarações básicas do ORM:

 from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String Base = declarative_base() class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) name = Column(String) 

No trecho de código acima, o plug-in usa get_dynamic_class_hook() para informar ao mypy que Base é uma classe base válida, mesmo que não pareça. Em seguida, get_base_class_hook() é chamado para definir Usuário e adiciona vários atributos gerados automaticamente. Em seguida, criamos uma instância do modelo:

user = User(id=42, name=42)

get_function_hook() chamado, então mypy pode indicar um erro: um valor integer é recebido em vez do nome de usuário.

Os stubs definem a Column como um descritor genérico , para que os atributos do modelo obtenham os tipos corretos:

 id_col = User.id # Inferred type is "Column[int]" name = user.name # Inferred type is "Optional[str]" 

Congratulamo-nos com PRs que adicionam tipos mais precisos aos stubs (o progresso dos módulos principais é rastreado aqui ).

Aqui estão algumas armadilhas que descobrimos ao trabalhar em plugues:

  • Use __getattr__() para evitar falsos positivos nos estágios iniciais quando os stubs não forem concluídos (isso evita erros mypy se houver falta de atributos do módulo). Você também pode usar isso em arquivos __init__.py se algum sub-módulo estiver ausente.
  • Os descritores geralmente ajudam com definições de tipo mais precisas para acesso a atributos personalizados (como no exemplo da coluna que analisamos acima). O uso de descritores é bom, mesmo que a implementação real do tempo de execução use um mecanismo mais complexo, incluindo uma metaclasse, por exemplo.
  • Sem hesitar, declare as classes de estrutura como generalizadas. Apesar do fato de que eles não são assim no tempo de execução, essa técnica permite determinar com mais precisão o tipo de alguns elementos da estrutura, enquanto os erros de tempo de execução podem ser facilmente contornados . (Esperamos que as estruturas adicionem gradualmente suporte typing.Generic para tipos genéricos, herdando explicitamente as classes correspondentes da typing.Generic .)

Plugins mypy lançados recentemente

Já existem vários plugins disponíveis para as estruturas populares do Python. Além do plug-in SQLAlchemy mencionado acima, outros pacotes de amostra dignos de nota com stubs e o plug-in mypy embutido incluem stubs para as interfaces Django e Zope . O trabalho ativo está em andamento nesses projetos.

Instalando e Conectando Pacotes de Stub e Plugins

Use pip para instalar o pacote de plugins para mypy e / ou stub em um ambiente virtual em que mypyesteja instalado :

  $ pip install sqlalchemy-stubs 

O Mypy detectará automaticamente os stubs instalados. Para conectar plug-ins instalados, inclua-os diretamente no mypy.ini (ou no arquivo de configuração do usuário):

 [mypy] plugins = sqlmypy, mypy_django_plugin.main 

Desenvolvendo plugins mypy e escrevendo stubs

Se você deseja desenvolver um pacote de stubs e plugins para a estrutura que você usa, podemos usar o repositório sqlalchemy-stubs como modelo. Ele inclui um setup.py , teste de infraestrutura usando testes controlados por dados e um exemplo de classe de plug-in com um conjunto de ganchos para o plug-in (ganchos de plug-in). Recomendamos o uso do stubgen para gerar automaticamente os stubs que acompanham o mypy para começar a usá-los. Stubgen melhorou um mypy 0.670 em mypy 0.670 .

Confira a documentação se você quiser saber mais sobre o sistema de plugins mypy . Você também pode pesquisar na Internet os códigos-fonte dos plug-ins discutidos no artigo. Se você tiver dúvidas, pode perguntar aqui .

O dia 15 de abril será um webinar gratuito e aberto sobre o curso, que será realizado por um dos organizadores da comunidade Python de Moscou - Vladimir Filonov , inscreva-se, será interessante. E agora estamos aguardando seus comentários sobre o material traduzido.

Source: https://habr.com/ru/post/pt447556/


All Articles