在Python中导入包

81

我可能漏掉了一些显而易见的东西,但无论如何:

当你在Python中导入一个包(例如os),你可以立即使用其中的任何子模块/子包。例如,下面的代码就可以工作:

>>> import os
>>> os.path.abspath(...)

然而,我有自己的包,其结构如下:

FooPackage/
  __init__.py
  foo.py

这里相同的逻辑不起作用:

>>> import FooPackage
>>> FooPackage.foo
AttributeError: 'module' object has no attribute 'foo'

我做错了什么?

6个回答

72

当您导入FooPackage时,Python会在PYTHONPATH上搜索目录,直到找到名为FooPackage.py或包含名为__init__.py的文件的名为FooPackage的目录。但是,在找到包目录后,它并不会扫描该目录并自动导入所有.py文件。

这种行为有两个原因。第一个原因是导入模块执行Python代码可能需要时间,内存或产生副作用。因此,您可能希望导入a.b.c.d,而不一定要导入整个庞大的a包。由包设计者决定a的__init__.py是否显式导入其模块和子包,以使它们始终可用,或者让客户端程序具有选择加载什么的能力。

第二个原因更微妙,也是一个问题。如果没有显式的导入语句(无论是在FooPackage/__init__.py还是客户端程序中),Python并不一定知道应将foo.py导入为什么名称。在大小写不敏感的文件系统(例如Windows中使用的文件系统)上,这可以表示为命名为fooFooFOOfOofoOFoOFOofOO的模块。所有这些都是有效的,不同的Python标识符,因此仅从文件本身中,Python就没有足够的信息来知道您的意思。因此,为了在所有系统上保持一致的行为,它需要在某个地方有显式的导入语句来澄清名称,即使在完全支持大小写的文件系统上也是如此。


2
因此,您无法将包作为聚合体导入。您只能导入该包的特定模块。 - drlolly
4
@drlolly 除非包的作者希望你这样做,并且在他们的 __init__.py 文件中明确编写了子导入,否则你无法将包作为聚合体导入。 - Ben

43

您需要导入子模块:

import FooPackage.foo
你正在查找 FooPackage/__init__.py 中的 foo。你可以通过在FooPackage/__init__.py中添加 import FooPackage.foo as foo(或者 from . import foo)来解决它,这样Python就可以在那里找到 foo。但我建议使用我的第一个建议。

11
我理解问题不在于如何导入子模块,而是为什么可以访问os的子模块却没有导入该子模块,以及如何实现类似的操作。编辑:但是,你的回答可以解决这个问题。 - pycoder112358
3
为什么您更喜欢第一个建议? - Tengerye

20

您需要在包的 __init__.py 文件中添加 from .import foo


1
(这些信息已经在其他回答中发布了,但您必须稍微阅读一下。因此,我将其作为简单的答案再次发布。) - Richard Shepherd
最好使用 __all__ = ["foo"] - Guy L

18

有一些重要的误解需要解决,特别是术语方面。通常情况下,当你认为在Python中导入一个package时,实际上你导入的是一个module。当你从文件系统子结构角度考虑用于组织代码时,应该使用术语package。但是从代码角度来看,每当你导入一个package时,Python都将其视为一个模块。所有的包都是模块,但不是所有的模块都是包。带有__path__属性的模块被认为是一个包。

你可以检查os是一个模块。为了确认这一点,你可以执行以下操作:

import os
print(type(os)) # will print: <type 'module'>

在你的示例中,当你执行import FooPackage时,FooPackage也被视为模块,并且它的属性(函数、类等)应该在__init__.py中定义。由于你的__init__.py为空,它找不到foo

import语句之外,不能使用点表示法来寻址嵌套模块中的模块。唯一的例外是,如果一个module被导入到了它所属的父包的__init__.py文件中。为了清楚起见,让我们举几个例子:

考虑你的原始结构:

FooPackage/
  __init__.py
  foo.py

情况1:__init__.py是一个空文件

#FooPackage imported as a module
import FooPackage 

#foo is not a name defined in `__init__.py`. Error
FooPackage.foo 

#FooPackage.foo imported as a module
import FooPackage.foo

#Error, foo has not been imported. To be able to use foo like this,
#you need to do: import FooPackage.foo as foo
foo.anything

#Not error, if anything is defined inside foo.py
FooPackage.foo.anything

情况2:__init__.py文件中有import foo代码行:

import FooPackage

#Now this is good. `foo` is sort of considered to be an attribute of 
#FooPackage
FooPackage.foo

现在假设 foo 不再是一个模块,而是你在 __init__.py 中定义的一个函数。如果你执行 import FooPackage.foo,它会抛出一个错误,指示 foo 不是一个模块。


2
在“Case 2”中,您可能需要在__init__.py文件中使用from . import foo而不是import foo,否则它可能会导致导入错误,因为Python将import foo视为“绝对导入”(请参见https://docs.python.org/3/tutorial/modules.html#intra-package-references中的“Intra-package References”部分)。 - onelaview
非常有帮助的解释。在意识到我需要在 __init__.py 中添加导入语句之前,我一直在尝试无法成功执行第二种情况。 - wisbucky

0
你可以使用 os.path.func() 的原因是因为 os.path 是你特定系统路径模块的别名,以使代码更具可移植性。 os 模块导入适合您系统的正确路径模块设置 sys.modules['os.path'] = <path module>。这基本上会将其翻译为 Windows 的 ntpath.func() 或 Linux 的 posixpath.func()

-4

你可以使用import语句从库中导入一个包。

语法:import module_name

     ex: import math

您可以使用以下语法仅从包中导入特定的方法:

语法:from module_name import function_name

     ex: from math import radians

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