所有动态语言都存在循环导入问题吗?

5
以下是Python代码:
first.py
# first.py
from second import Second

class First:
    def __init__(self):
        print 'Second'

second.py

# second.py
from first import First

class Second:
    def __init__(self):
        print 'Second'

创建文件后,从shell中运行以下命令:

python first.py

我得到了这个错误:ImportError: 无法导入名称 Second 其他动态语言(如Ruby)是否有这种问题?我问这个问题的原因是因为在一个Django项目中,我遇到了两个模型相互依赖的问题。我知道可能的解决方案是重新设计项目或按需导入。我只想知道其他动态语言中的开发人员是否遇到过这个问题。

2
你应该总是继承object而不是什么都不继承,这样你就可以使用新式类。 - Mike Graham
1
@Mike,他没有说明他使用的Python版本。 - Hamish Grubijan
4
@Hamish Grubijan,i) 他使用了print语句,在Python 3中已被删除,ii) 他说他正在使用Django,而Django不支持Python 3,iii) 没有人在使用Python 3;Python 2是合理的默认选择。 - Mike Graham
5个回答

13

Python可以在一定程度上处理循环导入。在一些情况下,即使解决了问题,在其他语言中也可能仍然不合理。通过使用 import first 并在后面引用 first.First 而不是 from first import First,大部分问题可以得到解决。

最好将共享代码移到自己的模块中,或以某种方式重构以消除循环导入的需要。 循环导入总是表示设计问题。


3
如果我想将成千上万行代码拆分为多个文件,这是一个设计问题吗? - asmeurer
1
当你将一个文件中的代码拆分成多个文件,并且这些文件之间存在循环引用时,你并没有使用最优设计。 - Mike Graham
1
循环导入并不一定意味着设计问题。例如,您想将每个模型类放在自己的文件/模块中。但是它们之间存在关系。那里没有设计问题,存在循环依赖的必要性。在Rails中,他们通过黑客方式解决了这个问题,并将它们指定为字符串而不是对象,然后在下一个框架内部将它们连接在一起。 - Lance

4

递归定义并不是动态语言所特有的问题。在静态类型语言中,这也经常是一个问题。它可能表现为编译错误,因为其中一个类型将在定义之前被使用。

在某些语言中,解决方案是使用前向声明。其他语言通过一次编译多个文件来解决这个问题。

在Python中,您可以通过将导入从顶层移动到需要它们的函数中来解决问题。此外,循环引用实际上并不是一个错误,因此如果您小心谨慎,仍然可以使其正常工作。


3

所有其他海报都正确指出循环导入是一个严重的问题,你应该在结构上进行修复。

然而,特别是在Python/Django模型中,你可以使用字符串名称来设置外键,以避免这些循环依赖问题 --

#appA/models.py
class A(models.Model):
  b = models.ForeignKey('appB.b')

#appB/models.py
class B(models.Model):
  a = models.ForeignKey('appA.a')

数据库表中的循环引用不一定是坏事(但也不总是好事);Django允许使用字符串定义键以帮助需要时解决这种情况。如果您确实需要在彼此内部实例化两个类,则可能存在更大的问题。


1
请注意,如果您将导入语句移到模块末尾,循环导入将按预期工作。像这样:

first.py

# first.py
class First:
  def __init__(self):
    print 'Second'
from second import Second

second.py

# second.py
class Second:
    def __init__(self):
        print 'Second'
from first import First

Fredrik Lundh的导入参考值得一读。正如其他人建议的那样,你最好重新调整你的代码,完全避免循环导入。


1

从逻辑上讲,这是一个悖论。这就像代码形式的鸡和蛋问题。其中一个必须先出现。正如其他人建议的那样,请回到起点重新规划,从长远来看,你会受益匪浅。编程语言之所以限制你做这些事情,是有原因的!


2
这可能不是一个好主意,但两个模块相互依赖并不是一个悖论。 - Draemon
相互依赖,也许是逐步增加的,但像OP建议的那样明显吗?这是个悖论。 - jathanism
不是这样的。你从来没有听说过递归吗? - asmeurer
递归函数当然可以,但递归模块就不行了。 - jathanism
如果不同文件中的两个函数递归地互相调用,则会出现此问题。解决方法是从一个文件中导入另一个文件。仅仅因为这两个函数在不同的文件中,并不意味着它们在不同的模块中。这只是表示它们在逻辑上分离,或者是因为将它们合并起来将产生一个太大的文件。 - asmeurer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接