224

我正试图将我的大班分成两部分;好吧,基本上进入“主”类和带有附加功能的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当然不能工作。我不能 import main.py,因为我会得到一个循环导入并且没有提示,我的编辑器(PyCharm)无法分辨是什么self

我正在使用 Python 3.4,但如果那里有可用的解决方案,我愿意迁移到 3.5。

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

4

7 回答 7

333

恐怕没有一种非常优雅的方式来处理一般的导入周期。您的选择是重新设计代码以删除循环依赖,或者如果不可行,请执行以下操作:

# 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在运行时,因此不会评估导入,但 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):
        ...

from __future__ import annotations导入将使所有类型提示成为字符串并跳过评估它们这可以帮助我们的代码稍微更符合人体工程学。

综上所述,在 mypy 中使用 mixins 可能需要比目前更多的结构。Mypy推荐了一种基本上deceze描述的方法——创建一个你MainMyMixin类都继承的 ABC。如果您最终需要做类似的事情来让 Pycharm 的检查器满意,我不会感到惊讶。

于 2016-09-28T20:48:54.037 回答
65

对于仅在为类型检查导入类时遇到循环导入问题的人:您可能希望使用前向引用(PEP 484 - 类型提示):

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

所以而不是:

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

你做:

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right
于 2019-07-12T11:18:39.600 回答
18

更大的问题是你的类型一开始就不健全。MyMixin做了一个硬编码的假设,即它将被混合到Main中,而它可以被混合到任意数量的其他类中,在这种情况下它可能会中断。如果您的 mixin 被硬编码为混合到一个特定的类中,那么您最好将方法直接写入该类,而不是将它们分开。

要使用健全的打字正确地做到这一点,MyMixin应该根据 Python 用语中的interface或抽象类进行编码:

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')
于 2016-09-28T07:52:05.360 回答
9

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

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

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

于 2017-03-27T08:10:51.200 回答
6

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

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

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

要实现aand 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_CHECKINGTrue类型检查器正在读取代码时。由于类型检查器不需要执行代码,因此循环导入在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 版本的注意事项

对于 Python 版本≤3.4,TYPE_CHECKING未定义,因此此解决方案不起作用。

对于小于等于 3.6 的 Python 版本,未定义延迟注释。作为一种解决方法,请省略from __future__ import annotations并引用上面提到的类型声明。

于 2021-10-03T17:59:22.270 回答
-1

正如其他一些人所建议的那样,我建议重构您的代码。

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

前:

# 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

于 2021-03-15T11:14:22.470 回答
-6

我认为完美的方法应该是将所有类和依赖项导入一个文件(如__init__.py),然后导入所有from __init__ import *其他文件。

在这种情况下,您是

  1. 避免对这些文件和类的多次引用,以及
  2. 也只需要在每个其他文件中添加一行,并且
  3. 第三个是 pycharm 了解您可能使用的所有类。
于 2017-08-17T10:42:08.303 回答