Mypy扩展插件

朋友们,下午好。 而且,我们将继续加大启动新课程的力度,现在很高兴地宣布, “ Python Web开发人员”课程的课程将于4月下旬开始。 在这方面,我们传统上会分享有用材料的翻译。 让我们开始吧。

Python被称为动态输入语言。 编写类似DSL的框架非常容易,而这些框架很难用静态类型检查工具来解析。 尽管如此,借助mypy的最新功能创新(例如协议文字类型 ,以及对元类和描述符支持的基本支持),我们通常可以获得准确的类型,但是仍然很难避免误报和其他负面因素。 为了解决此问题并避免需要为每个框架自定义类型系统, mypy支持插件系统。 插件是Python中的模块,提供mypy在检查与库或框架进行交互的类和函数的类型时将调用的插件挂钩。 因此,可以更准确地区分返回函数的类型(否则很难表达),或者可以自动生成一些类方法以反映装饰器的效果。 要了解有关插件系统体系结构的更多信息并查看功能的完整列表,请查阅文档



标准库的相关插件

Mypy带有用于实现基本功能和类的默认插件,以及ctypescontextlibdataclasses 。 它还包括用于attrs插件(历史上是第一个为mypy编写的第三方插件)。 这些插件允许mypy使用这些库函数更准确地确定类型并正确检查类型的代码。 为了举例说明,请看一下代码片段:

 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') 

在上面,定义类时将调用get_class_decorator_hook() 。 这会将自动生成的方法(包括__init__()到函数主体。 Mypy使用此类构造函数正确计算TaggedVector[int]作为position的类型。 从示例中可以看到,插件甚至可以与通用类一起使用。

这是另一段代码:

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

这里的get_function_hook()提供了contextmanager装饰器的确切返回类型,因此可以检查对装饰后的函数的调用是否符合特定类型。 现在, mypy可以识别该错误: timer()的参数应该是字符串。

插件和存根的组合

除了使用动态Python函数外,框架还经常遇到具有大型API的问题。 Mypy需要库存根文件来测试使用这些库的代码(仅当该库不包含内置注释时,这种情况并不常见)。 为带有框架的大型框架分配存根不是常见的做法:

  • Typeshed的发布周期相对较慢(使用mypy附带)。
  • 不完整的存根会导致错误的调用,这将是很难避免的。
  • 不要只是混用来自不同排版版本的存根。

PEP 561中引入的存根软件包可以执行以下操作:

  • 开发人员可以根据需要多次发布存根软件包。
  • 未选择使用该程序包的用户将不会看到误报。
  • 您可以安全地安装几个不同存根软件包的任意版本。

而且, pip允许您将库的各种存根和相应的mypy插件组合到一个发行版中。 可以轻松开发框架的存根或相应的mypy插件,并将其放到一个发行版中,这非常有用,因为插件会在存根中填充缺少或不准确的定义。

此类软件包的最新示例是SQLAlchemy stubs和plugin ,它的第一个公共版本为0.1,该版本早些时候已在PyPI上发布。 尽管该项目是早期的Alpha版本,但我们可以在DropBox中安全地使用它来改进类型检查。 该插件了解基本的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) 

在上面的代码片段中,该插件使用get_dynamic_class_hook()告诉mypy Base是有效的基类,即使它看起来不像它。 然后, get_base_class_hook()定义User,并添加几个自动生成的属性。 接下来,我们创建模型的实例:

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

get_function_hook() ,因此mypy可能指示错误:接收到integer数值而不是用户名。

存根将Column定义为通用描述符,以便模型属性获得正确的类型:

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

我们欢迎为存根添加更精确类型的PR( 在此跟踪核心模块的进展)。

这是我们在使用插头时发现的一些陷阱:

  • 使用__getattr__()可以在存根未完成的早期阶段避免误报(如果模块属性丢失,则可以避免mypy错误)。 如果缺少任何子模块,也可以在__init__.py文件中使用它。
  • 描述符通常有助于为自定义属性访问提供更准确的类型定义(如我们在上面的“列”示例中所见)。 即使运行时的实际实现使用更复杂的机制(例如,包括元类),也可以使用描述符。
  • 毫不犹豫地将框架类声明为通用类。 尽管它们在运行时并非如此,但该技术使您可以更准确地确定框架中某些元素的类型,同时可以轻松地规避运行时错误。 (我们希望框架将逐渐增加对泛型类型的内置支持,从typing.Generic显式继承相应的类。)

最近发布的mypy插件

流行的Python框架已经有几个插件可用。 除了上面提到的SQLAlchemy插件外,其他带有存根和内置mypy插件的值得注意的示例程序包还包括DjangoZope接口的存根。 这些项目正在进行中。

安装和连接存根和插件包

使用pip将mypy和/或stub的插件包安装到已安装mypy的虚拟环境中:

  $ pip install sqlalchemy-stubs 

Mypy将自动检测已安装的存根。 要连接已安装的插件,请将其直接包含在mypy.ini中(或在用户配置文件中):

 [mypy] plugins = sqlmypy, mypy_django_plugin.main 

开发mypy插件并编写存根

如果您想为所使用的框架开发一套存根和插件,我们可以使用sqlalchemy-stubs存储库作为模板。 它包括setup.py ,使用数据驱动的测试进行的基础结构测试以及示例插件类,该类带有一组用于插件的钩子(plugin hooks)。 我们建议使用stubgen自动生成mypy附带的存根以开始使用它们。 Stubgenmypy 0.670有所改善。

如果您想了解有关mypy插件系统的更多信息,请查看文档 。 您也可以在Internet上搜索本文中讨论的插件的源代码。 如有疑问,可以在这里提问。

4月15日将是该课程的免费开放式网络研讨会 ,该研讨会将由莫斯科Python社区的组织者之一-Vladimir Filonov举行 ,请注册,这将很有趣。 现在,我们正在等待您对翻译材料的评论。

Source: https://habr.com/ru/post/zh-CN447556/


All Articles