You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Python类型提示与Protocol时如何避免循环导入

使用Protocol和类型提示时避免循环导入的最优方案

问题场景

现有代码存在循环导入问题:storage.py导入Event类,event.py又导入StorageProtocol协议,导致运行时报错。

storage.py 代码

from typing import Protocol
from event import Event


class StorageProtocol(Protocol):
    def push(self, event: Event):
        ...

class StoragePostgres(StorageProtocol):
    def push(self, event: Event):
        """pushes Event to Postgres"""
        pass

class StorageS3(StorageProtocol):
    def push(self, event: Event):
        """pushes Event to S3"""
        pass

event.py 代码

from dataclasses import dataclass
from storage import StorageProtocol


@dataclass
class Event:
    id: str
    start_datetime: str
    end_datetime: str
    
    def push(self, storage: StorageProtocol):
        storage.push(self)

尝试过from __future__ import annotations、TYPE_CHECKING条件导入、类内导入等方法,但都觉得代码不够整洁,希望找到更干净的解决方案。

最优方案:拆分公共类型到独立模块

最整洁的解决方式是将**协议(Protocol)和核心数据类(Event)**抽离到单独的公共模块,彻底切断循环依赖链,同时符合单一职责原则。

步骤1:创建公共类型模块types.py

把Event和StorageProtocol都移到这个模块,统一管理公共类型:

from typing import Protocol
from dataclasses import dataclass

@dataclass
class Event:
    id: str
    start_datetime: str
    end_datetime: str
    
    def push(self, storage: "StorageProtocol"):
        storage.push(self)

class StorageProtocol(Protocol):
    def push(self, event: Event):
        ...

步骤2:修改storage.py

从公共模块导入所需类型,不再直接依赖event.py:

from types import StorageProtocol, Event

class StoragePostgres(StorageProtocol):
    def push(self, event: Event):
        """pushes Event to Postgres"""
        pass

class StorageS3(StorageProtocol):
    def push(self, event: Event):
        """pushes Event to S3"""
        pass

步骤3:简化event.py

直接从公共模块导入Event,无需重复定义:

from types import Event

如果需要对Event进行扩展,也可以继承公共模块的类:

from types import Event, StorageProtocol

class ExtendedEvent(Event):
    extra_field: str
    
    def push(self, storage: StorageProtocol):
        # 自定义逻辑
        super().push(storage)

备选方案:组合TYPE_CHECKING与字符串类型提示

如果不想调整模块结构,推荐使用TYPE_CHECKING条件导入+字符串类型提示的组合,既保留完整的类型检查支持,又避免运行时循环导入:

修改后的storage.py

from typing import Protocol, TYPE_CHECKING

# TYPE_CHECKING仅在类型检查阶段为True,运行时不会执行导入
if TYPE_CHECKING:
    from event import Event

class StorageProtocol(Protocol):
    def push(self, event: "Event"):
        ...

class StoragePostgres(StorageProtocol):
    def push(self, event: "Event"):
        """pushes Event to Postgres"""
        pass

class StorageS3(StorageProtocol):
    def push(self, event: "Event"):
        """pushes Event to S3"""
        pass

修改后的event.py

from dataclasses import dataclass
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from storage import StorageProtocol

@dataclass
class Event:
    id: str
    start_datetime: str
    end_datetime: str
    
    def push(self, storage: "StorageProtocol"):
        storage.push(self)

相比其他零散方法,这个组合方案不会破坏代码结构,也能保证类型提示正常工作。

内容的提问来源于stack exchange,提问作者Carlos Azevedo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 15:58:14