我试图将我的大班分成两部分;好吧,基本上进入"主要" 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;知道类型的所有其他好东西?
答案 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
描述的内容 - 用于创建您的Main
和MyMixin
类继承的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 *
。
在这种情况下,你是
答案 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