朋友们,下午好。 而且,我们将继续加大启动新课程的力度,现在很高兴地宣布,
“ Python Web开发人员”课程的课程将于4月下旬开始。 在这方面,我们传统上会分享有用材料的翻译。 让我们开始吧。
Python被称为动态输入语言。 编写类似DSL的框架非常容易,而这些框架很难用静态类型检查工具来解析。 尽管如此,借助
mypy的最新功能创新(例如
协议和
文字类型 ,以及对元类和描述符支持的基本支持),我们通常可以获得准确的类型,但是仍然很难避免误报和其他负面因素。 为了解决此问题并避免需要为每个框架自定义类型系统,
mypy支持
插件系统。 插件是Python中的模块,提供
mypy在检查与库或框架进行交互的类和函数的类型时将调用的插件挂钩。 因此,可以更准确地区分返回函数的类型(否则很难表达),或者可以自动生成一些类方法以反映装饰器的效果。 要了解有关插件系统体系结构的更多信息并查看功能的完整列表,请查阅
文档 。
标准库的相关插件Mypy带有用于实现基本功能和类的默认插件,以及
ctypes
,
contextlib
和
dataclasses
。 它还包括用于
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
我们欢迎为存根添加更精确类型的PR(
在此跟踪核心模块的进展)。
这是我们在使用插头时发现的一些陷阱:
- 使用
__getattr__()
可以在存根未完成的早期阶段避免误报(如果模块属性丢失,则可以避免mypy错误)。 如果缺少任何子模块,也可以在__init__.py
文件中使用它。 - 描述符通常有助于为自定义属性访问提供更准确的类型定义(如我们在上面的“列”示例中所见)。 即使运行时的实际实现使用更复杂的机制(例如,包括元类),也可以使用描述符。
- 毫不犹豫地将框架类声明为通用类。 尽管它们在运行时并非如此,但该技术使您可以更准确地确定框架中某些元素的类型,同时可以轻松地规避运行时错误。 (我们希望框架将逐渐增加对泛型类型的内置支持,从
typing.Generic
显式继承相应的类。)
最近发布的mypy插件流行的Python框架已经有几个插件可用。 除了上面提到的
SQLAlchemy插件外,其他带有存根和内置
mypy插件的值得注意的示例程序包还包括
Django和
Zope接口的存根。 这些项目正在进行中。
安装和连接存根和插件包使用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附带的存根以开始使用它们。
Stubgen
在
mypy 0.670
有所改善。
如果您想了解有关
mypy插件
系统的更多信息,请查看
文档 。 您也可以在Internet上搜索本文中讨论的插件的源代码。 如有疑问,可以
在这里提问。
4月15日将是该课程的免费
开放式网络研讨会 ,该
研讨会将由莫斯科Python社区的组织者之一
-Vladimir Filonov举行 ,请注册,这将很有趣。 现在,我们正在等待您对翻译材料的评论。