我想把我庞大的班级分成两部分;好吧,基本上变成了“main”类和一个带有额外函数的mixin,如下所示:

main.py文件:

import mymixin.py

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

mymixin.py文件:

class MyMixin(object):
    def func2(self: Main, xxx):  # <--- note the type hint
        ...

现在,虽然这工作得很好,但MyMixin中的类型提示。Func2当然不能工作。我不能导入main.py,因为我会得到一个循环导入,如果没有提示,我的编辑器(PyCharm)无法告诉self是什么。

我使用的是Python 3.4,但如果有解决方案,我愿意转到3.5。

有没有办法我可以把我的类分成两个文件,并保留所有的“连接”,这样我的IDE仍然为我提供自动补全和所有其他来自它知道类型的好处?


当前回答

我认为最好的方法应该是在一个文件中导入所有的类和依赖项(比如__init__.py),然后从__init__ import *导入所有其他文件。

在这种情况下,你是

避免对这些文件和类的多次引用 也只需要添加一行在每个其他文件和 第三个是pycharm,知道您可能使用的所有类。

其他回答

更大的问题是,你们这种类型的人一开始就不理智。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')

事实证明,我最初的尝试也很接近解决方案。这是我目前使用的:

# 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
        ...

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

一般来说,恐怕没有一种非常优雅的方法来处理导入周期。你的选择是重新设计你的代码以删除循环依赖,或者如果这是不可行的,就像这样做:

# some_file.py

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from main import Main

class MyObject(object):
    def func2(self, some_param: 'Main'):
        ...

TYPE_CHECKING常量在运行时总是False,因此导入不会被计算,但myypy(和其他类型检查工具)将计算该块的内容。

我们还需要将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):
        ...

from __future__ import注释导入将使所有类型提示都成为字符串,并跳过对它们求值。这有助于使我们的代码稍微更符合人体工程学。

综上所述,在mypy中使用mixins可能需要比当前更多的结构。myypy推荐一种方法,基本上就是欺骗所描述的方法——创建一个Main类和MyMixin类都继承的ABC。如果你最终需要做一些类似的事情来让Pycharm的检查者高兴,我不会感到惊讶。

我认为最好的方法应该是在一个文件中导入所有的类和依赖项(比如__init__.py),然后从__init__ import *导入所有其他文件。

在这种情况下,你是

避免对这些文件和类的多次引用 也只需要添加一行在每个其他文件和 第三个是pycharm,知道您可能使用的所有类。

而不是强迫自己打字。TYPE_CHECKING骗局,有一个简单的方法来避免循环类型提示:不要使用from import,使用from __future__ import注释或字符串注释。

# foo.py
from __future__ import annotations
import bar


class Foo:
    bar: bar.Bar
# bar.py
import foo


class Bar:
    foo: "foo.Foo"

这种类型的导入是“惰性计算的”,而使用from foo import foo将迫使Python运行整个foo模块,以立即在导入行获得foo的最终值。如果你需要在运行时使用它,这是非常有用的,例如if foo。Foo或bar。Bar需要在函数/方法中使用,因为你的函数/方法只应该被调用一次foo。Foo和bar。可以使用Bar。