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

时间:2016-09-28 07:21:35

标签: python pycharm python-3.4 python-3.5 type-hinting

我试图将我的大班分成两部分;好吧,基本上进入"主要" class和带有附加功能的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。

有什么方法可以将我的课分成两个文件,并保留所有&#34;连接&#34;所以我的IDE仍然提供我自动完成&amp;知道类型的所有其他好东西?

6 个答案:

答案 0 :(得分:63)

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

# 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导入会使所有类型提示成为字符串并跳过评估它们。这有助于使我们的代码更符合人体工程学。

所有这一切,使用mixin和mypy可能需要比现在更多的结构。 Mypy recommends an approach基本上是deceze描述的内容 - 用于创建您的MainMyMixin类继承的ABC。如果您最终需要做类似的事情以使Pycharm的检查员满意,我不会感到惊讶。

答案 1 :(得分: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')

答案 2 :(得分:2)

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

# 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类作为字符串,因为它在运行时是未知的。

答案 3 :(得分:1)

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

在这种情况下,你是

  1. 避免多次引用这些文件和类
  2. 也只需在每个其他文件中添加一行
  3. 第三个是pycharm知道你可能会使用的所有类。

答案 4 :(得分:1)

对于仅在进行类检查时导入类而苦于周期性导入的人们:您可能希望使用Forward Reference(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

答案 5 :(得分:0)

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

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

之前:

# 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