Python中如何解决Type Hints的问题?分享经验与解决方案
说实话 Python 循环 import 一直是个不是问题的问题,我们可以通过提取出两个模块共同的部分来规避这个问题。我也感觉代码里最好不要出现循环,如果出现,一定是设计的问题。
不过 PEP484 ( Type Hints )出来以后,循环 import 的问题在我代码里出现比较多了,因为需要注明变量(参数)类型,所以不得不将一些不需要 import 的类导入。
所以这里有一个相关讨论: https://github.com/python/typing/issues/105
Python 之父 Guido 参与了讨论并给出了一个临时通用的解决方案: https://hg.python.org/peps/rev/06fbe54fcfe1
,就是用import foo来代替from foo import bar。
PEP-0563 里给出了另一个解决方案:使用typing.TYPE_CHECKING。这个常量在编辑器检查变量类型的时候为 True,在代码实际运行的时候为 False。于是,我们可以用如下代码来导入声明类型时用到的类:
if typing.TYPE_CHECKING:
from foo import bar
这个常量在 3.5.2 后加入。
不过这个方法又引入了新的问题:在代码运行时,实际上是没有导入 bar 的,那么作为 Type Hints 使用会出错:
def f(foo: bar): pass
我们必须把 bar 用引号包裹:
def f(foo: 'bar'): pass

PEP-0563 里也给出了相关的解决方案。在 Python 4 以后,函数的 annotations 将不再运行时被执行,所以也就不会报错了
在 Python3.7 下,我们可以使用from __future__ import annotations来体验这个 4 里的特性。

这几个方式结合在一起,就能完美解决我们遇到的问题了。
总的来说,这些新特性让我的代码更加 humanize,我也十分期待 3.7 的正式发布,我觉得 3.7 里异步 context 的部分还挺好用的~
Python中如何解决Type Hints的问题?分享经验与解决方案
很快就 4 了吗 版本号飙起来
Type Hints(类型提示)在Python里是个好东西,能让代码更清晰,配合IDE和mypy这类工具能提前发现不少低级错误。但用起来确实会遇到一些坑,我分享一下常见的几个问题和我的处理方式。
1. 循环引用(Circular Imports)
这是最烦人的问题之一。当两个类互相引用时,比如User类里有个Post列表,Post类里有个User作者,直接import会报错。
解决方案:使用字符串字面量或者from __future__ import annotations
# 方法1:使用字符串
from __future__ import annotations # Python 3.7+ 可以更优雅
class User:
def __init__(self, name: str):
self.name = name
self.posts: list[Post] = [] # 直接使用类名,或者用字符串 "Post"
class Post:
def __init__(self, title: str, author: User): # 这里User已经可用了
self.title = title
self.author = author
2. 前向引用(Forward References) 和循环引用类似,但更通用。当你需要引用尚未定义的类时。
解决方案:同上,用字符串。from __future__ import annotations会让所有注解在运行时都自动存为字符串,基本一劳永逸。
3. 泛型(Generics)和容器类型
标注list、dict里具体放什么类型。
解决方案:用typing模块,Python 3.9+直接用内置类型。
from typing import Dict, List, Optional # Python 3.8及以前
# Python 3.9+ 可以这样写:
def process_items(items: list[str]) -> dict[str, int]:
counts: dict[str, int] = {}
for item in items:
counts[item] = counts.get(item, 0) + 1
return counts
4. 标注“可能为None”的值
解决方案:用Optional[X],或者Python 3.10+的 | 语法。
from typing import Optional
def find_user(user_id: int) -> Optional["User"]:
# ... 可能返回None
pass
# Python 3.10+
def find_user(user_id: int) -> User | None:
pass
5. 第三方库或动态类型没有存根(stubs)
解决方案:自己写.pyi存根文件,或者用typing.cast、Any来临时绕过。
from typing import Any, cast
import some_untyped_module
result = cast(int, some_untyped_module.funky_function()) # 告诉类型检查器,我相信它是int
data: Any = get_dynamic_data() # 实在不行就Any,但尽量少用
6. 类型检查太严格,需要忽略某行
解决方案:用 # type: ignore 注释。
x = some_untyped_call() # type: ignore
我的工作流建议:
- 统一代码风格:团队约定好用
from __future__ import annotations。 - 渐进式添加:老项目不用一次性全加上,新代码和重点模块先加。
- 善用工具:在CI里集成
mypy或pyright,提交前跑一下。 - 理解本质:类型提示主要是给人和工具看的,运行时影响很小(除了
get_type_hints)。
总结:核心就是处理好循环引用,善用字符串注解和typing模块。
感谢感谢,解决了我这里互相 import 的问题
感谢分享,
话说看到 Python4 还是虎躯一震
不用 type hint 完美解决
厉害了,最近刚转入 3 的使用和学习,马上就 4 出来了
4 应该还早吧,这里只是用到了一个未来的特性
这几个问题都是在 www.mypy.com 里面直接告诉你解决方案的。
你们都没有去看官方文档吗?
老师对不起,我们没有好好学习,今天学习了一个新的官方文档 www.mypy.com
讲真的,如果我的代码里要写很多这种奇奇怪怪对实际 IDE 体验(比如这个 bar 到底是哪个 bar,目前 PyCharm 自动完成就搞不定,甚至搞不定 metaclass 或 Proxy Wrapper )、代码可读性都没有提升的东西,还不如选择换一门静态类型的语言。
http://mypy-lang.org/ sorry
这样注释之后,代码中如何解决不让解释器执行没引入的 type 呢?
也是如帖子里的 from future import annotations 那样不执行吗?
嘛刚才 Guido 说不会走这条路了……所以也没什么必要讨论这个了。
类型提示导致循环引用这种情况,不应该上 lazy settings 么?
没懂,我看他们没提到这个,可以给点详细信息么
就是所有的常量、配置项都写在一个文件里,然后在任意位置都能 import 到这个文件里的内容,类似于 django 的 settings.py 和 flask 的 g
并不是常量,比如 A 类里某个方法接受或返回一个 B 类对象,B 类里一个方法接受或返回 A 类对象。你可能还没遇到过这种情况,如果仅是配置的话就简单多了。
既然 python 官方需要有专门的方案来解决这个问题,说明这个问题是一个广泛问题,并不能通过你说的这种方式来解决。你说的这种方式仅能处理一些简单的逻辑。
数据类型的定义不也是常量么?难道类定义在运行时还会动态改?
明白你的意思了。 这是个鸡生蛋、蛋生鸡的问题啊~
这种情况,我都是放弃 annotations, 在函数内部用 assert 来做类型判断。。。。。反正也只是给 ide 看的
https://www.bernat.tech/the-state-of-type-hints-in-python/ 几天 python weekly 这篇不错
恩,之前看到的时候就收藏了


