为什么Python的__import__需要fromlist?

85 投票
3 回答
27334 浏览
提问于 2025-04-15 22:05

在Python中,如果你想通过代码来导入一个模块,可以这样做:

module = __import__('module_name')

如果你想导入一个子模块,你可能会认为只需要简单地这样做:

module = __import__('module_name.submodule')

当然,这样做是行不通的;你只会得到 module_name。你必须这样做:

module = __import__('module_name.submodule', fromlist=['blah'])

为什么呢? 其实,fromlist 的实际值似乎根本不重要,只要它不是空的。那么,要求提供一个参数的意义是什么呢?

在Python中,大多数事情都有其合理的原因,但我实在想不出为什么会有这种行为存在。

3 个回答

4

答案可以在 __import__ 的文档中找到:

fromlist 应该是一个名字的列表,用来模拟 from name import ...,或者是一个空列表,用来模拟 import name

当从一个包中导入模块时,注意 __import__('A.B', ...) 在 fromlist 为空时返回包 A,但当 fromlist 不为空时返回它的子模块 B。

简单来说,这就是 __import__ 的实现方式:如果你想要子模块,就传一个 fromlist,里面包含你想从子模块中导入的内容,这样 __import__ 就会返回这个子模块。

进一步解释

我觉得这样设计是为了返回最相关的模块。换句话说,假设我有一个包 foo,里面有一个模块 bar,而这个模块里有一个函数 baz。如果我:

import foo.bar

那么我可以这样引用 baz

foo.bar.baz()

这就像是 __import__("foo.bar", fromlist=[])

如果我用:

from foo import bar

那么我可以这样引用 baz

bar.baz()

这就类似于 __import__("foo.bar", fromlist=["something"])

如果我这样做:

from foo.bar import baz

那么我可以这样引用 baz

baz()

这就像是 __import__("foo.bar", fromlist=["baz"])

所以在第一种情况下,我必须使用完整的名称,因此 __import__ 返回的是你用来引用导入元素的第一个模块名,也就是 foo。在最后一种情况下,bar 是包含导入元素的最具体的模块,所以 __import__ 返回 foo.bar 模块是有道理的。

第二种情况有点奇怪,但我猜这样写是为了支持使用 from <package> import <module> 的语法,在这种情况下 bar 仍然是最具体的模块。

7

我读到这个答案时感觉有点奇怪,所以我尝试了下面的代码示例。

首先,试着建立下面的文件结构:

tmpdir
  |A
     |__init__.py
     | B.py
     | C.py

现在 A 是一个 ,而 BC 是一个 模块。所以当我们在 ipython 中尝试一些像这样的代码时:

接下来,在 ipython 中运行示例代码:

  In [2]: kk = __import__('A',fromlist=['B'])

  In [3]: dir(kk)
  Out[3]: 
  ['B',
   '__builtins__',
   '__doc__',
   '__file__',
   '__name__',
   '__package__',
   '__path__']

看起来 fromlist 的功能和我们预期的一样。但当我们尝试在一个 模块 上做同样的事情时,情况就变得奇怪了。假设我们有一个叫 C.py 的模块,里面的代码是:

  handlers = {}

  def hello():
      print "hello"

  test_list = []

现在我们尝试对它做同样的事情。

  In [1]: ls
  C.py

  In [2]: kk = __import__('C')

  In [3]: dir(kk)
  Out[3]: 
  ['__builtins__',
   '__doc__',
   '__file__',
   '__name__',
   '__package__',
   'handlers',
   'hello',
   'test_list']

那么当我们只想导入 test_list 时,这样做有效吗?

  In [1]: kk = __import__('C',fromlist=['test_list'])

  In [2]: dir(kk)
  Out[2]: 
  ['__builtins__',
   '__doc__',
   '__file__',
   '__name__',
   '__package__',
   'handlers',
   'hello',
   'test_list']

结果显示,当我们在一个 模块 上使用 fromlist,而不是在一个 上时,fromlist 参数根本没有帮助,因为 模块 已经被编译过了。一旦它被导入,就没有办法忽略其他的内容。

140

其实,__import__()的行为完全是因为import语句的实现,它会调用__import__()。基本上,__import__()可以通过import被调用五种略有不同的方式(主要分为两类):

import pkg
import pkg.mod
from pkg import mod, mod2
from pkg.mod import func, func2
from pkg.mod import submod

在第一种和第二种情况下,import语句应该把“最左边”的模块对象赋值给“最左边”的名称:pkg。在执行import pkg.mod后,你可以使用pkg.mod.func(),因为import语句引入了本地名称pkg,它是一个模块对象,并且有一个mod属性。所以,__import__()函数必须返回“最左边”的模块对象,以便可以赋值给pkg。因此,这两个导入语句可以理解为:

pkg = __import__('pkg')
pkg = __import__('pkg.mod')

在第三、第四和第五种情况下,import语句需要做更多的工作:它需要将多个名称(可能是多个)赋值,这些名称需要从模块对象中获取。__import__()函数只能返回一个对象,而且没有必要让它从模块对象中获取每一个名称(这样会让实现变得复杂得多)。所以简单的做法可能是这样的(针对第三种情况):

tmp = __import__('pkg')
mod = tmp.mod
mod2 = tmp.mod2

不过,如果pkg是一个包,而modmod2是这个包中的模块(而且这些模块还没有被导入),那么这个方法就不行了,就像在第三和第五种情况下那样。__import__()函数需要知道modmod2import语句想要访问的名称,这样它才能检查这些名称是否是模块,并尝试导入它们。所以调用应该更接近于:

tmp = __import__('pkg', fromlist=['mod', 'mod2'])
mod = tmp.mod
mod2 = tmp.mod2

这会导致__import__()尝试加载pkg.modpkg.mod2以及pkg(但如果modmod2不存在,这在__import__()调用中并不是错误;产生错误的责任留给import语句)。但这仍然不适用于第四和第五个例子,因为如果调用是这样:

tmp = __import__('pkg.mod', fromlist=['submod'])
submod = tmp.submod

那么tmp最终会变成pkg,而不是你想要获取submod属性的pkg.mod模块。实现可以选择让import语句做额外的工作,像__import__()函数那样在.上拆分包名并遍历名称,但这会导致一些工作重复。因此,最终的实现让__import__()返回最右边的模块,而不是最左边的模块,前提是传入了非空的fromlist。

import pkg as pfrom pkg import mod as m的语法并没有改变这个故事,只是改变了赋值给哪些本地名称——当使用as时,__import__()函数并没有看到什么不同,所有内容仍然在import语句的实现中。)

撰写回答