跳转到主要内容

用于管理类型检查导入和前向引用的flake8插件

项目描述

Package version Code coverage Test status Supported Python versions Checked with mypy

flake8-type-checking

让您知道哪些导入需要移入或移出 类型检查 块。

该插件假定您仅用于类型提示的导入 在运行时不是必需的。当导入在运行时不是严格必需时,这意味着我们可以保护它们。

保护导入提供了3个主要好处

  • 🔧  它减少了导入循环问题,
  • 🧹  它组织导入,
  • 🚀  它完全消除了运行时类型提示导入的开销

本质上,此代码

import pandas  # 15mb library

x: pandas.DataFrame

变成这样

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import pandas  # <-- no longer imported at runtime

x: "pandas.DataFrame"

更多示例可以在示例部分找到。


如果您正在使用 pydanticfastapicattrsinjector,请参阅配置了解如何启用支持。

主要功能

该插件将

  • 告诉您何时应该将导入移动到类型检查块中
  • 告诉您何时应该将导入再次移动出来

并且根据您选择的错误代码范围,它还将告诉您

  • 您是否需要添加 from __future__ import annotations 导入
  • 您是否需要引用注解
  • 您是否可以取消引用已引用的注解

错误代码

代码 描述
TC001 将应用程序导入移动到类型检查块中
TC002 将第三方导入移动到类型检查块中
TC003 将内置导入移动到类型检查块中
TC004 将导入从类型检查块中移出。导入用于类型提示以外的用途。
TC005 发现空的类型检查块
TC006 在 typing.cast() 中的注解应该是字符串字面量
TC007 类型别名需要是字符串字面量
TC008 类型别名不需要是字符串字面量
TC009 将声明从类型检查块中移出。变量用于类型提示以外的用途。
TC010 | 的操作数不能是字符串字面量

选择如何处理前向引用

您需要选择是否选择使用 TC100TC200 范围的错误代码。

它们代表了解决同一问题的两种不同方式,因此请只选择一种。

TC100TC101 通过利用 注解的延迟评估 来管理前向引用。

代码 描述
TC100 添加 'from __future__ import annotations' 导入
TC101 注解不需要是字符串字面量

TC200TC201 使用 字符串字面量 来管理前向引用。

代码 描述
TC200 注解需要是字符串字面量
TC201 注解不需要是字符串字面量

启用错误范围

TCTC1TC2 添加到您的 flake8 配置中,如下所示

[flake8]
max-line-length = 80
max-complexity = 12
...
ignore = E501
# You can use 'extend-select' (new in flake8 v4):
extend-select = TC, TC2
# OR 'select':
select = C,E,F..., TC, TC2  # or TC1
# OR 'enable-extensions':
enable-extensions = TC, TC2  # or TC1

如果您不确定选择哪个 TC 范围,请参阅理由获取更多信息。

安装

pip install flake8-type-checking

配置

这些选项是可配置的,可以在您的 flake8 配置中设置。

免除模块

如果您希望免除某些模块需要移动到类型检查块中的要求,您可以指定要忽略哪些模块。

  • 设置名称: type-checking-exempt-modules
  • 类型: list
[flake8]
type-checking-exempt-modules = typing_extensions  # default []

严格

默认情况下,插件将报告 TC00[1-3] 错误,因为该模块尚未有其他导入。当有来自同一模块的其他导入时,导入循环和性能优势不再适用于保护导入。

当启用严格模式时,插件将标记所有可以移动的导入。

  • 设置名称: type-checking-strict
  • 类型: bool
[flake8]
type-checking-strict = true  # default false

Pydantic 支持

如果您在代码中使用 Pydantic 模型,您应该启用 Pydantic 支持。这将任何类变量注解视为在运行时需要。

  • 名称: type-checking-pydantic-enabled
  • 类型: bool
[flake8]
type-checking-pydantic-enabled = true  # default false

Pydantic 支持基类白名单

禁用所有类注解检查有些激进。

如果您确信所有命名为,例如,NamedTuple 的基类都不是 Pydantic 模型,那么您可以在该设置中传递基类名称,以重新启用继承它们的类的检查。

  • 名称: type-checking-pydantic-enabled-baseclass-passlist
  • 类型: list
[flake8]
type-checking-pydantic-enabled-baseclass-passlist = NamedTuple, TypedDict  # default []

FastAPI 支持

如果您正在使用FastAPI项目的插件,应启用支持。这将在运行时处理任何装饰函数的注解。

启用FastAPI支持还将启用Pydantic支持。

  • 名称: type-checking-fastapi-enabled
  • 类型: bool
[flake8]
type-checking-fastapi-enabled = true  # default false

对于FastAPI用户来说,还有一点需要注意,那就是(在Depends中使用的)依赖项将产生假阳性,除非您启用以下所述的依赖项支持。

FastAPI依赖项支持

除了防止装饰器的假阳性外,我们还可以防止依赖项的假阳性。然而,我们做出了一些相当糟糕的权衡:通过启用此选项,我们将整个项目中每个函数定义中的每个注解都视为可能的依赖项注解。换句话说,我们完全停止检查所有函数注解,以避免假阳性的可能性。如果您更喜欢稳妥一些,您应该启用此功能——否则,可能只需要意识到作为依赖项使用的函数可能会出现假阳性。

启用依赖项支持还将启用FastAPI和Pydantic支持。

  • 名称: type-checking-fastapi-dependency-support-enabled
  • 类型: bool
[flake8]
type-checking-fastapi-dependency-support-enabled = true  # default false

SQLAlchemy 2.0+支持

如果您正在使用SQLAlchemy 2.0+,您可以启用支持。这将在运行时处理任何Mapped[...]类型。它还将特别处理包含的类型,因为它可能需要或不需要在运行时可用,这取决于封装的类型是否是模型,因为模型可能存在循环依赖。

  • 名称: type-checking-sqlalchemy-enabled
  • 类型: bool
type-checking-sqlalchemy-enabled = true  # default false

SQLAlchemy 2.0+支持映射点名称

由于可以创建sqlalchemy.orm.Mapped的子类,为映射属性定义一些自定义行为,但除此之外仍然像任何其他映射属性一样行为,即相同的运行时限制适用,因此可以提供应像Mapped子类对待的附加点名称。默认情况下,我们检查sqlalchemy.orm.Mappedsqlalchemy.orm.DynamicMappedsqlalchemy.orm.WriteOnlyMapped

如果有多个导入路径指向同一个Mapped子类,则您需要将它们指定为单独的点名称。

  • 名称: type-checking-sqlalchemy-mapped-dotted-names
  • 类型: list
type-checking-sqlalchemy-mapped-dotted-names = a.MyMapped, a.b.MyMapped  # default []

Cattrs支持

如果您在使用的项目中使用了cattrs,您可以启用支持。这将在运行时处理任何装饰的attrs类的注解,因为当加载类型不可在运行时使用的类时,cattrs.unstructure调用将失败。

注意:cattrs支持设置尚未检测并忽略数据类或其他非attrs类类型上的类变量注解。如果需要,这可以在将来添加。

  • 名称: type-checking-cattrs-enabled
  • 类型: bool
[flake8]
type-checking-cattrs-enabled = true  # default false

注入器支持

如果您正在使用注入器库,您可以启用支持。这将处理任何Inject[Dependency]类型,使其在运行时所需。

  • 名称: type-checking-injector-enabled
  • 类型: bool
type-checking-injector-enabled = true  # default false

原因

我们为什么创建这个插件?

良好的类型提示通常需要大量的项目导入,这可能会增加项目中导入循环的风险。防止此问题的推荐方法是使用typing.TYPE_CHECKING块来保护这些类型的导入。特别是,TC001有助于防止此问题。

导入受到保护后,它们将不再在运行时评估/导入。这种做法的后果是,这些导入不能再被视为在块外导入。相反,我们需要使用前向引用

对于 Python 版本 >= 3.7,实际上有两种解决这个问题的方法。你可以将你的注释做成字符串字面量,或者使用一个 __futures__ 导入来启用 注释的延迟评估。请参阅这篇优秀的 stackoverflow 回答,以获得对差异的精彩解释。

示例

性能示例

类型提示的导入可能会影响性能。

import pandas


def dataframe_length(df: pandas.DataFrame) -> int:
    return len(df)

在这个示例中,我们导入了一个15MB的库,只为了一个类型提示。

我们根本不需要在运行时执行这个操作。如果我们知道导入在周围的代码中不会以其他方式被需要,我们可以简单地保护它,如下所示

from typing import TYPE_CHECKING


if TYPE_CHECKING:
    import pandas  # <-- no longer imported at runtime


def dataframe_length(df: "pandas.DataFrame") -> int:
    return len(df)

现在导入不再在运行时进行。如果你对此不确定,请参阅mypy 文档以获得基本介绍。

导入循环示例

糟糕的代码

models/a.py

from models.b import B

class A(Model):
    def foo(self, b: B): ...

models/b.py

from models.a import A

class B(Model):
    def bar(self, a: A): ...

会导致以下错误

>> a.py: TC002 Move third-party import 'models.b.B' into a type-checking block
>> b.py: TC002 Move third-party import 'models.a.A' into a type-checking block

并且如果导入仅仅被移动到类型检查块中,而没有正确处理前向引用,则会引发这些错误

>> a.py: TC100 Add 'from __future__ import annotations' import
>> b.py: TC100 Add 'from __future__ import annotations' import

或者

>> a.py: TC200 Annotation 'B' needs to be made into a string literal
>> b.py: TC200 Annotation 'A' needs to be made into a string literal

好的代码

models/a.py

# TC1
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from models.b import B

class A(Model):
    def foo(self, b: B): ...

或者

# TC2
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from models.b import B

class A(Model):
    def foo(self, b: 'B'): ...

models/b.py

# TC1
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from models.a import A

class B(Model):
    def bar(self, a: A): ...

或者

# TC2
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from models.a import A

class B(Model):
    def bar(self, a: 'A'): ...
野外示例

以下是一些使用 flake8-type-checking 的公开项目的示例

作为 pre-commit 钩子运行插件

你可以将此 flake8 插件作为pre-commit 钩子运行

- repo: https://github.com/pycqa/flake8
  rev: 4.0.1
  hooks:
    - id: flake8
      additional_dependencies:
        - flake8-type-checking

贡献

请随时打开一个问题或 PR 👏

项目详情


下载文件

下载适用于您平台的应用程序文件。如果您不确定选择哪个,请了解更多关于安装软件包的信息。

源分发

flake8_type_checking-2.9.1.tar.gz (32.2 kB 查看散列)

构建分发

flake8_type_checking-2.9.1-py3-none-any.whl (30.2 kB 查看散列)

上传于 Python 3

由以下机构支持