Python中的意外相对导入行为

6 投票
2 回答
507 浏览
提问于 2025-04-16 16:57

今天我遇到了一个非常让人惊讶的相对导入行为(不幸的是,这让我抓了快四个小时的头发)。

我一直以为,如果你在一个叫“module_a.py”的模块里有“Class A”,而这个模块又在一个叫“package”的包里,那么你可以用以下两种方式导入:

from package.module_a import ClassA

或者

from module_a import ClassA

只要你是在“package”里的某个模块中导入。我理解这就是相对导入。

直到今天我才遇到问题,当我需要检查一个对象的实例是否属于Class A时,结果让我很惊讶,出现了非常不寻常的行为。

考虑以下内容:

package/module_a.py

class ClassA(object):
    pass

def check_from_module_a(obj):
    print 'from module_a'
    print '-------------'
    print 'class is:', ClassA
    print 'object is', type(obj) 
    print 'is obj a ClassA:', isinstance(obj, ClassA)

package/module_b.py

from package.module_a import ClassA
from module_a import check_from_module_a

a = ClassA()
check_from_module_a(a)

print ' '
print 'from module_b'
print '-------------'
print 'class is:', ClassA
print 'object is', type(a) 
print 'is obj a ClassA:', isinstance(a, ClassA)

现在,当你执行module_b.py时,你会得到:

from module_a
-------------
class is: <class 'module_a.ClassA'>
object is <class 'package.module_a.ClassA'>
is obj a ClassA: False

from module_b
-------------
class is: <class 'package.module_a.ClassA'>
object is <class 'package.module_a.ClassA'>
is obj a ClassA: True

我跟着逻辑走,现在明白为什么会这样——这并不是很明显,因为我以为ClassA的绑定无论是绝对导入还是相对导入都是一样的。这给我带来了一个非常棘手的bug,难以定位。

我有几个问题:

  1. 这是预期中的行为吗?

  2. 如果这是应该的逻辑,那我不明白为什么相对导入和绝对导入在上面的情况下不兼容。如果我遗漏了什么好的解释,请告诉我。

  3. 我一直认为相对导入在大型重构时提供了额外的便利,因为子包结构可能会被移动。这是相对导入的主要好处吗?

2 个回答

0

(1) 是的,这种情况是正常的。

(2) 明确的相对导入是

from .module_a import ClassA

,而不是

from module_a import ClassA

,后者可以是相对的,也可以是绝对的,这样可能会导致顶层包和模块之间的冲突。

(3) 是的,这就是相对导入的一个好处。最大的好处可能就是需要输入的内容更少了 :)

4

因为隐式相对导入会引发一些问题,所以在Python 3中已经把它们去掉了。使用隐式相对导入时,你常常得不到预期的结果。想了解更多,可以看看PEP-328。这在你定义一个子包的名字和基础模块的名字相同时尤其明显。

撰写回答