没有循环导入的Python类型提示

vel*_*lis 65 python type-hinting pycharm python-3.4 python-3.5

我想把我的大班分成两部分; 好吧,基本上进入"主"类和具有附加功能的mixin,如下所示:

import mymixin.py

class Main(object, MyMixin):
    def func1(self, xxx):
        ...
Run Code Online (Sandbox Code Playgroud)

现在,虽然这很好用,但MyMixin.func2中的类型提示当然无法正常工作.我无法导入main.py,因为我得到一个循环导入而没有提示,我的编辑器(PyCharm)无法分辨出什么main.py是.

使用Python 3.4,如果有可用的解决方案,愿意转向3.5.

有没有什么办法可以将我的课分成两个文件并保留所有的"连接",以便我的IDE仍然提供自动完成功能以及知道类型的所有其他好东西?

Mic*_*x2a 104

一般来说,没有一种非常优雅的方式来处理进口周期,我担心.您的选择是重新设计代码以消除循环依赖,或者如果不可行,请执行以下操作:

# some_file.py

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from main import Main

class MyObject(object):
    def func2(self, some_param: 'Main'):
        ...
Run Code Online (Sandbox Code Playgroud)

TYPE_CHECKING常数是始终False在运行,所以进口不会进行评估,但mypy(和其他类型检查工具)将评估该块的内容.

我们还需要将Main类型注释转换为字符串,有效地转发声明它,因为Main符号在运行时不可用.

如果您使用的是Python 3.7+,我们至少可以通过利用PEP 563来跳过必须提供明确的字符串注释:

# some_file.py

from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from main import Main

class MyObject(object):
    # Hooray, cleaner annotations!
    def func2(self, some_param: Main):
        ...
Run Code Online (Sandbox Code Playgroud)

from __future__ import annotations进口将使所有类型提示弦而跳过评估他们.这有助于使我们的代码更温和地符合人体工程学.

所有这一切,使用mixins和mypy可能需要比现在更多的结构.Mypy 推荐一种基本上deceze就是描述的方法 - 创建一个你的MainMyMixin类都继承的ABC .如果您最终需要做类似的事情以使Pycharm的检查员满意,我不会感到惊讶.

  • 谢谢你 我目前的python 3.4没有`typing`,但是PyCharm也很满意`if False:`。 (3认同)
  • 这是“打字”对应的 pep。类型检查`:https://www.python.org/dev/peps/pep-0484/#runtime-or-type-checking (3认同)

Ben*_*res 22

从 Python 3.5 开始,将类分成单独的文件很容易。

\n

实际上可以使用块import语句将方法导入到类中。例如,class ClassName:

\n

class_def.py

\n
class C:\n    from _methods1 import a\n    from _methods2 import b\n\n    def x(self):\n        return self.a() + " " + self.b()\n
Run Code Online (Sandbox Code Playgroud)\n

在我的例子中,

\n
    \n
  • C.a()将是一个返回字符串的方法hello
  • \n
  • C.b()将是一个返回的方法hello goodbye
  • \n
  • C.x()将因此返回hello hello goodbye
  • \n
\n

要实施ab,请执行以下操作:

\n

_methods1.py

\n
from __future__ import annotations\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from class_def import C\n\ndef a(self: C):\n    return "hello"\n
Run Code Online (Sandbox Code Playgroud)\n

解释TYPE_CHECKINGTrue当类型检查器正在读取代码时。由于类型检查器不需要执行代码,因此当循环导入发生在if TYPE_CHECKING:块内时就可以了。导入__future__启用延迟注释。这是可选的;如果没有它,您必须引用类型注释(即def a(self: "C"):)。

\n

我们_methods2.py同样定义:

\n
from __future__ import annotations\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from class_def import C\n\ndef b(self: C):\n    return self.a() + " goodbye"\n
Run Code Online (Sandbox Code Playgroud)\n

self.a()在 VS Code 中,我可以看到悬停时检测到的类型:\n在此输入图像描述

\n

一切都按预期运行:

\n
>>> from class_def import C\n>>> c = C()\n>>> c.x()\n\'hello hello goodbye\'\n
Run Code Online (Sandbox Code Playgroud)\n

关于旧 Python 版本的注释

\n

对于 Python 版本 \xe2\x89\xa43.4,TYPE_CHECKING未定义,因此此解决方案不起作用。

\n

对于 Python 版本 \xe2\x89\xa43.6,未定义推迟注释。作为解决方法,请省略from __future__ import annotations并引用上面提到的类型声明。

\n


Sim*_*Art 21

与其强迫自己参与typing.TYPE_CHECKING恶作剧,有一种简单的方法可以避免循环类型提示:不要使用from导入,而使用from __future__ import annotations或 字符串注释。

# foo.py
from __future__ import annotations
import bar


class Foo:
    bar: bar.Bar
Run Code Online (Sandbox Code Playgroud)
# bar.py
import foo


class Bar:
    foo: "foo.Foo"
Run Code Online (Sandbox Code Playgroud)

这种导入方式是“延迟评估”的,而使用from foo import Foo会强制 Python 运行整个模块以立即在导入行foo获取最终值。Foo如果您还需要在运行时使用它,例如如果需要在函数/方法中使用foo.Foo或,那么它非常有用,因为您的函数/方法应该只被调用一次并且可以使用。bar.Barfoo.Foobar.Bar


dec*_*eze 9

更大的问题是你的类型开始时并不理智.MyMixin做出一个硬编码的假设,它将被混合Main进来,而它可以被混合到任意数量的其他类中,在这种情况下它可能会破坏.如果你的mixin被硬编码以混合到一个特定的类中,你也可以将方法直接写入该类而不是将它们分离出来.

要通过合理的输入正确地执行此操作,MyMixin应该使用Python语言对接口或抽象类进行编码:

import abc


class MixinDependencyInterface(abc.ABC):
    @abc.abstractmethod
    def foo(self):
        pass


class MyMixin:
    def func2(self: MixinDependencyInterface, xxx):
        self.foo()  # ? mixin only depends on the interface


class Main(MixinDependencyInterface, MyMixin):
    def foo(self):
        print('bar')
Run Code Online (Sandbox Code Playgroud)

  • 嗯,我并不是说我的解决方案很好。这正是我试图做的,以便使代码更易于管理。您的建议可能会通过,但这实际上意味着将整个 Main 类移至我的 __specic__ 案例中的接口。 (2认同)
  • @velis `typing.Protocol` 可以用来代替 `abc.ABC`,因为您实际上不需要子类化它来注册它。这是提供您计划使用的接口的正确方法,而“abc.ABC”更适合您提供部分完成的实现,即您实际上想要对其进行子类化。 (2认同)

Tom*_*iak 7

对于仅在导入类以进行类型检查时陷入困境的人们:您可能希望使用前向引用(PEP 484-类型提示):

当类型提示包含尚未定义的名称时,该定义可以表示为字符串文字,稍后再解析。

所以代替:

class Tree:
def __init__(self, left: Tree, right: Tree):
    self.left = left
    self.right = right
Run Code Online (Sandbox Code Playgroud)

你做:

class Tree:
def __init__(self, left: 'Tree', right: 'Tree'):
    self.left = left
    self.right = right
Run Code Online (Sandbox Code Playgroud)

  • 如果类型驻留在另一个模块中,则这不起作用(至少 pycharm 不理解它)。如果该字符串可以是完全限定的路径,那就太好了。 (16认同)
  • @JacobLee 除了“if False:”之外,您还可以“通过键入 import TYPE_CHECKING”和“if TYPE_CHECKING:”。 (2认同)

vel*_*lis 5

原来我最初的尝试也非常接近解决方案。这是我目前使用的:

# main.py
import mymixin.py

class Main(object, MyMixin):
    def func1(self, xxx):
        ...


# mymixin.py
if False:
    from main import Main

class MyMixin(object):
    def func2(self: 'Main', xxx):  # <--- note the type hint
        ...
Run Code Online (Sandbox Code Playgroud)

请注意if False永远不会导入的 import inside语句(但 IDE 无论如何都知道它)并使用Main该类作为字符串,因为它在运行时未知。

  • @Phil:是的,当时我使用的是Python 3.4。现在正在输入。TYPE_CHECKING (2认同)
  • 看起来很愚蠢,但可以与 PyCharm 配合使用。投我一票吧!:) (2认同)