跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • Python 使用静态类型标注时的循环导入问题
未分類
6 2 月 2021

Python 使用静态类型标注时的循环导入问题

Python 使用静态类型标注时的循环导入问题

資深大佬 : LeeReamond 6

一般来说,包设计中做抽象时,个人习惯把基类抽出来单独放一个文件,我看很多其他人也喜欢这么做,比如做成这样

--鸟类大全     |___ base.py     |___ 鸟人 1.py     |___ 鸟人 2.py     |___ 工具箱.py  

大概这种感觉。

最近在做 type hints 时遇到一个循环导入的问题,即比如我有一个 class BaseBird:存在于base.py中,而class BirdmanOne(BaseBird)存在于鸟人 1.py中,显然鸟人 1.py 需要from .base import BaseBird

正常情况下没问题,在老版本里也一直是这么处理的。但是现在如果想引入类型提示特性的话,如果BaseBird中的某个方法,或者base.py中的其他函数、方法的输入,是存在于其他文件中的子类,这种情况下没办法直接从子类导入这种类,因为会变成循环引用。

有办法解决吗?

大佬有話說 (21)

  • 資深大佬 : hareandlion

    base.py 中没有抽象所有的接口实现?那写个声明了所有方法的 mixin 供所有子类继承是不是好点?

  • 資深大佬 : abersheeran

    是的,这个问题我在写 Index.py 的时候也遇到了。我的解决方案是用 PEP563 。

  • 主 資深大佬 : LeeReamond

    @hareandlion 只是举了一个简单例子,实际还有比如子类之间互相引用等等情况,实际业务中完全解耦几乎不可能,

  • 主 資深大佬 : LeeReamond

    @abersheeran 我看了一下 pep563 没抓住重点他想表达什么,大佬能概述一下有什么作用吗

  • 資深大佬 : Kilerd

    有。

    from typing import TYPE_CHECKING

    if TYPE_CHECKING:
    from .base import BaseBird

    https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING

  • 主 資深大佬 : LeeReamond

    @abersheeran 手机上看的,不太方便,看起来似乎像是引入一个被动变量跳过编译检查?然后在 runtime 中类型提示已经被去除了,所以不会报错?

  • 主 資深大佬 : LeeReamond

    开机测试了一下,会报错啊

    https://gist.github.com/GoodManWEN/65464b2b3ed32c0c85d66e25002d417d

  • 資深大佬 : abersheeran

    @LeeReamond 你这么写当然会报错。目前的 CPython,type hint 可是会在运行时计算的。这和宣称的不符。才有了我上面所说的 PEP563 。3.7 以上版本可用 future 开启,3.10 默认开启。

  • 資深大佬 : 676529483

    原来-> BaseBird 改成字符串->”BaseBird”

  • 資深大佬 : no1xsyzy

    也可以转移到 .pyi 里面去,1. 对运行时隐形; 2. 不担忧循环引用。
    不好的地方是 pydantic 这种依赖运行时 hint 的就不行

  • 資深大佬 : popil1987

    你得考虑下设计问题了,先用 typing any 通过
    如果非得循环引用就得改变引入顺序,比如
    a.py
    from .b import B
    class A:
    def test(self, b:B)->None:
    pass
    b.py
    class B:
    pass
    from .a import A
    class C:
    def test(self, a:A):
    pass
    要是 A 需要 B,B 需要 A 那就没办法了,改设计吧,可以多用 mixin,少继承。

  • 資深大佬 : so1n

    @LeeReamond 第 10 行 def func(self , input:B.Bird):改为 def func(self , input:”B.Bird”): 即可

  • 主 資深大佬 : LeeReamond

    @popil1987 只是这么抽象在人类逻辑上比较清楚,可能对程序来说不清楚,我不太懂设计的问题,mixin 是什么意思,百度搜了一下没搜到

  • 主 資深大佬 : LeeReamond

    @so1n
    @no1xsyzy
    @676529483
    @abersheeran

    试了一下感觉目前这些方案都不行。我对静态类型的期望有两个,其一是在开发的时候可以使用 IDE 直接寻址到对象的目标位置,如果用双引号的写法的话无法实现,其二是我希望能够在 runtime 使用 inspect.trace 追踪到对象,这样可以在自动测试中依据这个特性自动检查所有函数和方法的输入输出是否符合要求,让程序更可靠,大概类似于编译中的静态类型检查吧。这个需求用双引号的方式也无法实现,用`from __future__ import annotations`也无法实现。

    目前来看只能获取到对象的字符串名称,而不能直接获取对象,而进行 isinstance 判断必须要输入对象,所以似乎无解。还有一种方式是通过某种方式设计重载,并且在重载后使用 eval()将字符串转换为类,但一来实现成本较高,二来 eval 总归让人不爽。

  • 資深大佬 : so1n

    @LeeReamond 那就需要写个抽象类了..

  • 主 資深大佬 : LeeReamond

    @so1n 没听懂,详细说一下?

  • 資深大佬 : no1xsyzy

    @LeeReamond
    方法 1,更好的 eval ( python>=3.7 ):
    pydantic (<https://github.com/samuelcolvin/pydantic>)用了一个类似 eval 的东西:
    /pydantic/main.py#L757-L765
    这里通过 sys.modules[cls.__module__].__dict__ 拿到 globals,然后
    /pydantic/typing.py#L51-L68
    应对不同版本调用 ForwardRef 的保护方法进行解析(但仍然是个 eval,你完全可以写出 `a: ‘type(1+2)’ = 5`,而且类型检查能过)。

    方法 2,typing.Protocol ( python>=3.8 )
    采用 typing.Protocol,通过结构子类型替换名义子类型。运行时可以采用 .runtime_checkable
    https://docs.python.org/zh-cn/3/library/typing.html#nominal-vs-structural-subtyping (这是我翻译的,就放中文文档了)
    ( Go 的 interface 就是结构子类型,至于 Ponylang 则同时实现了结构子类型( interface )和名义子类型( trait ))

    方法 3,控制反转(无要求)
    需要动代码结构。
    先解释下 mixin 和 abc:
    mixin 是一种设计模式,可以参考 Ponylang/rust 的 trait,把一些非关键依赖的方法从基类里剥离出去,最后依赖结构会形成一个 Diamond 。
    抽象类是指标准库 abc 模块,把最基础的要求提取出来可以在类型里留洞。

  • 主 資深大佬 : LeeReamond

    @no1xsyzy 感谢回复,有时间研究一下。关于 mixin 和抽象类,我不会 ponylang 和 rust,所以无法理解你提供的 mixin 的例子。抽象类这个功能对于代码复用不是很灾难么,因为本身继承就是希望可以复用基类的代码,如果每个子类必须重新实现一遍的话感觉很累赘啊

  • 主 資深大佬 : LeeReamond

    @no1xsyzy 我测试了一下方法 1,我没太看懂 https://github.com/samuelcolvin/pydantic/blob/master/pydantic/typing.py#L51-L68 这一段怎么用 eval 实现的字符串到实例的转换,不是很理解为什么 from typing import _eval_type 之后,_eval_type 就变成了 ForwardRef 的方法,我自己这么调用没有成功,所以不是很理解 sys.modules[cls.__module__].__dict__拿到的 globals 怎么转化,我自己从中没法提取想要的信息,虽然 runtime 当中这句执行已经在导入所有库之后,但仍然只能拿到相对该文件的 globals

  • 主 資深大佬 : LeeReamond

    @no1xsyzy 感谢,已经解决

  • 資深大佬 : maocat

    @LeeReamond 你好,你是怎么解决的呢

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具