我想把我庞大的班级分成两部分;好吧,基本上变成了“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仍然为我提供自动补全和所有其他来自它知道类型的好处?


当前回答

从Python 3.5开始,将类分解为单独的文件就很容易了。

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

class_def.py:

class C:
    from _methods1 import a
    from _methods2 import b

    def x(self):
        return self.a() + " " + self.b()

在我的例子中,

C.a()将是一个返回字符串hello的方法 C.b()将是一个返回hello goodbye的方法 C.x()将返回hello hello goodbye。

要实现a和b,执行以下操作:

_methods1.py:

from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from class_def import C

def a(self: C):
    return "hello"

解释:TYPE_CHECKING在类型检查器读取代码时为True。因为类型检查器不需要执行代码,所以循环导入在if TYPE_CHECKING:块中出现时是很好的。__future__导入支持延迟注释。这是可选的;如果没有它,你必须引用类型注释(即def a(self: "C"):)。

我们类似地定义_methods2.py:

from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from class_def import C

def b(self: C):
    return self.a() + " goodbye"

在VS Code中,当悬停时,我可以看到从self.a()检测到的类型:

一切都按照预期运行:

>>> from class_def import C
>>> c = C()
>>> c.x()
'hello hello goodbye'

关于旧Python版本的注释

对于≤3.4的Python版本,TYPE_CHECKING没有定义,因此这个解决方案将不起作用。

对于≤3.6的Python版本,没有定义延迟注释。作为一种变通方法,省略__future__中的导入注释,并引用上面提到的类型声明。

其他回答

而不是强迫自己打字。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。

我建议重构你的代码,就像其他人建议的那样。

我可以向你展示我最近遇到的一个循环错误:

之前:

# person.py
from spell import Heal, Lightning

class Person:
    def __init__(self):
        self.life = 100

class Jedi(Person):
    def heal(self, other: Person):
        Heal(self, other)

class Sith(Person):
    def lightning(self, other: Person):
        Lightning(self, other)

# spell.py
from person import Person, Jedi, Sith

class Spell:
    def __init__(self, caster: Person, target: Person):
        self.caster: Person = caster
        self.target: Person = target

class Heal(Spell):
    def __init__(self, caster: Jedi, target: Person):
        super().__init__(caster, target)
        target.life += 10

class Lightning(Spell):
    def __init__(self, caster: Sith, target: Person):
        super().__init__(caster, target)
        target.life -= 10

# main.py
from person import Jedi, Sith

循序渐进:

# main starts to import person
from person import Jedi, Sith

# main did not reach end of person but ...
# person starts to import spell
from spell import Heal, Lightning

# Remember: main is still importing person
# spell starts to import person
from person import Person, Jedi, Sith

控制台:

ImportError: cannot import name 'Person' from partially initialized module
'person' (most likely due to a circular import)

一个脚本/模块只能被一个脚本导入。

后:

# person.py
class Person:
    def __init__(self):
        self.life = 100

# spell.py
from person import Person

class Spell:
    def __init__(self, caster: Person, target: Person):
        self.caster: Person = caster
        self.target: Person = target

# jedi.py
from person import Person
from spell import Spell

class Jedi(Person):
    def heal(self, other: Person):
        Heal(self, other)

class Heal(Spell):
    def __init__(self, caster: Jedi, target: Person):
        super().__init__(caster, target)
        target.life += 10

# sith.py
from person import Person
from spell import Spell

class Sith(Person):
    def lightning(self, other: Person):
        Lightning(self, other)

class Lightning(Spell):
    def __init__(self, caster: Sith, target: Person):
        super().__init__(caster, target)
        target.life -= 10

# main.py
from jedi import Jedi
from sith import Sith

jedi = Jedi()
print(jedi.life)
Sith().lightning(jedi)
print(jedi.life)

执行行顺序:

from jedi import Jedi  # start read of jedi.py
from person import Person  # start AND finish read of person.py
from spell import Spell  # start read of spell.py
from person import Person  # start AND finish read of person.py
# finish read of spell.py

# idem for sith.py

控制台:

100
90

文件组合是关键 D项“希望能有所帮助”

更大的问题是,你们这种类型的人一开始就不理智。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类作为字符串,因为它在运行时不知道。

从Python 3.5开始,将类分解为单独的文件就很容易了。

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

class_def.py:

class C:
    from _methods1 import a
    from _methods2 import b

    def x(self):
        return self.a() + " " + self.b()

在我的例子中,

C.a()将是一个返回字符串hello的方法 C.b()将是一个返回hello goodbye的方法 C.x()将返回hello hello goodbye。

要实现a和b,执行以下操作:

_methods1.py:

from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from class_def import C

def a(self: C):
    return "hello"

解释:TYPE_CHECKING在类型检查器读取代码时为True。因为类型检查器不需要执行代码,所以循环导入在if TYPE_CHECKING:块中出现时是很好的。__future__导入支持延迟注释。这是可选的;如果没有它,你必须引用类型注释(即def a(self: "C"):)。

我们类似地定义_methods2.py:

from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from class_def import C

def b(self: C):
    return self.a() + " goodbye"

在VS Code中,当悬停时,我可以看到从self.a()检测到的类型:

一切都按照预期运行:

>>> from class_def import C
>>> c = C()
>>> c.x()
'hello hello goodbye'

关于旧Python版本的注释

对于≤3.4的Python版本,TYPE_CHECKING没有定义,因此这个解决方案将不起作用。

对于≤3.6的Python版本,没有定义延迟注释。作为一种变通方法,省略__future__中的导入注释,并引用上面提到的类型声明。