Python:导入子包或子模块

126

由于我已经使用了平面包,所以我没有预料到我在嵌套包中遇到的问题。以下是…

目录结构

dir
 |
 +-- test.py
 |
 +-- package
      |
      +-- __init__.py
      |
      +-- subpackage
           |
           +-- __init__.py
           |
           +-- module.py

init.py文件的内容

package/__init__.pypackage/subpackage/__init__.py都是空的。

module.py文件的内容

# file `package/subpackage/module.py`
attribute1 = "value 1"
attribute2 = "value 2"
attribute3 = "value 3"
# and as many more as you want...

test.py文件内容(3个版本)

版本1

# file test.py
from package.subpackage.module import *
print attribute1 # OK

那是一种不好且不安全的导入方式(批量导入所有内容),但它确实有效。

版本 2

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
from module import attribute1

一种更安全的逐个导入模块的方法,但它失败了,Python不想要这样:它会报错 "No module named module"。然而...

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
print module # Surprise here

...显示<module 'package.subpackage.module' from '...'>。那是一个模块,但不是一个模块/-P 8-O ... 呃

版本3

# file test.py v3
from package.subpackage.module import attribute1
print attribute1 # OK
这一个可用。所以你要么被迫一直使用过度的前缀,要么使用不安全的方式(如版本#1中禁止使用的方式),但 Python 不允许使用方便且安全的方式?更好的方法是安全的,避免了不必要的长前缀,但 Python 拒绝了这种方法?这是因为它喜欢import *还是因为它喜欢过长的前缀(这并没有帮助强制执行这个惯例)?
抱歉用了一些刻薄的话,但我已经尝试两天来解决这种愚蠢的行为。除非我完全错了,否则这会让我感到Python的包和子包模型真的出了问题。
注:
- 我不想依赖于sys.path,以避免全局副作用,也不想依赖于*.pth文件,这只是另一种使用相同全局效果的sys.path的方法。为了使解决方案干净,它必须是本地的。Python 要么能够处理子包,要么不能,但它不应该需要在全局配置上进行操作才能处理本地东西。 - 我也尝试在package/subpackage/__init__.py中使用导入,但这解决不了问题,它做了同样的事情,并抱怨subpackage不是已知模块,而print subpackage则表明它是一个模块(又是奇怪的行为)。
除了我尝试过的三种方法之外,还有其他已知的方法吗?还有我不知道的东西吗?
(叹气)
结论(在人们的评论之后):
在 Python 中没有真正的子包,因为所有包引用都指向全局字典,这意味着没有本地字典,这意味着没有办法管理本地包引用。
您必须使用完整前缀、短前缀或别名。例如:
from package.subpackage.module import attribute1
# An repeat it again an again
# But after that, you can simply:
use_of (attribute1)

短前缀版本(但是有重复的前缀)

from package.subpackage import module
# Short but then you have to do:
use_of (module.attribute1)
# and repeat the prefix at every use place
否则,以上内容的一个变体。
from package.subpackage import module as m
use_of (m.attribute1)
# `m` is a shorter prefix, but you could as well
# define a more meaningful name after the context

分解版本

如果您不介意一次批量导入多个实体,可以采取以下方法:

from package.subpackage.module import attribute1, attribute2
# and etc.

这并不是我最喜欢的方式(我更喜欢每个导入实体都有一个import语句),但可能是我个人喜欢的方式。

更新(2012-09-14):

实践中似乎没什么问题,只是布局需要注意。我使用了以下方式代替以上方式:

from package.subpackage.module import (

    attribute1, 
    attribute2,
    attribute3,
    ...)  # and etc.

当您在“/package/subpackage/init.py”中编写“from . import module”时,情况如何? - Markus Unterwaditzer
你的“因式分解版本”似乎完全符合你想要做的事情。如果你为attribute1和attribute2分别使用单独的导入行(就像你“喜欢”的那样),那么你只是故意给自己增加更多的工作量。没有理由这样做。 - BrenBarn
抱歉,我不明白你想要什么。你能用更清晰的方式重新表达你的问题吗?你想要做什么?我的意思是,你想要写什么代码,它目前不能正常工作,你希望它如何工作? 根据我所读的,我认为你想要的导入语义类似于Java或者C的include。 最后一件事:你可以创建一个模块“星号导入”安全添加一个__all__变量,其中包含应在星号导入时导出的名称列表。 编辑:好的,看了BrenBarn的回答,我明白你的意思了。 - Bakuriu
3个回答

106

您似乎误解了 import 如何搜索模块。当使用import语句时,它始终搜索实际的模块路径(和/或 sys.modules );它不利用之前导入的本地命名空间中存在的模块对象。 当您执行以下操作时:

import package.subpackage.module
from package.subpackage import module
from module import attribute1
第二行代码寻找名为package.subpackage的包,并从中导入module。这一行代码对第三行没有影响。第三行只是在寻找一个名为module的模块,但没有找到。它不会“重用”你从上一行得到的名为module的对象。
换句话说,from someModule import ...并不意味着“从我之前导入的名为someModule的模块中...” ,而是表示“从sys.path上找到的名为someModule的模块中...”。没有办法通过导入通向该模块的包来“逐步”构建模块的路径。每当导入时,您都必须引用整个模块名称。
不清楚你想要实现什么目标。如果你只想导入特定的对象属性1,那么只需执行from package.subpackage.module import attribute1即可。在导入所需的名称后,您再也不需要担心长长的package.subpackage.module了。
如果你确实想要访问该模块以便稍后访问其他名称,那么可以执行from package.subpackage import module,就像你已经看到的一样,你可以像愿意的那样使用module.attribute1等名称。
如果你既想要attribute1直接可访问,又想要module可以访问,那么只需两者都执行即可。
from package.subpackage import module
from package.subpackage.module import attribute1
attribute1 # works
module.someOtherAttribute # also works

如果你不想打两次 package.subpackage,你可以手动创建一个对 attribute1 的本地引用:

from package.subpackage import module
attribute1 = module.attribute1
attribute1 # works
module.someOtherAttribute #also works

你的评论与Ignacio Vazquez-Abrams的评论方向相同(我已经评论了他的消息)。你在结尾处提到使用module.attribute1的想法是我也考虑过的,但我认为应该有一种避免在每个地方都需要前缀的方法。所以我要么在每个地方使用前缀,要么创建一个本地别名,重复名称。这不是我期望的风格,但如果没有其他办法(毕竟,我习惯于Ada,它需要类似的重命名声明),那就只能这样了。 - Hibou57
@Hibou57:我仍然不清楚你在“版本2”中想要实现什么。你想做什么是不可能的?你想要永远不必重新输入包/模块/属性名称的任何部分,但仍然导入模块及其属性吗? - BrenBarn
我想要一个本地包引用,就像你可以有一个本地对象引用一样。似乎现在终于有了真正的本地模块引用,但你不能从这些引用中导入。它是本地和全局的混合体,有点奇怪(有些东西可以是本地的,有些必须是全局的,我不喜欢它,但只要我现在更好地理解它是如何工作的,我就没问题了)。顺便说一下,谢谢你的信息。 - Hibou57
1
我不确定你是否还清楚它的工作原理,或者在任何情况下你是否在2012年就已经明白了。 - Hejazzman
1
每次我在6个月的停顿后回到Python,我最终都会来到这里。如果我每次访问此页面都能点赞就好了!我将制作一张巨大的海报,上面写着这句话:“没有办法通过导入导致它的包来“逐步”构建模块的路径。” - PatrickT
attribute1 = module.attattribute1. 这是一种不被推荐的导入组织方式吗? - sinekonata

11

#2失败的原因是因为sys.modules ['module']不存在(导入过程有自己的范围,无法看到module本地名称),并且磁盘上没有module模块或包。请注意,您可以逗号分隔多个导入名称。

from package.subpackage.module import attribute1, attribute2, attribute3

此外:

from package.subpackage import module
print module.attribute1

你提到的 sys.modules['name'] 直到现在我才知道,让我想到了我一直担心的事情(BrenBarn 也证实了):Python 中没有真正的子包。正如其名称所示,sys.modules 是全局的,如果所有对模块的引用都依赖于此,则不存在对模块的本地引用(可能会在 Python 3.x 中出现?)。 - Hibou57
你在那里使用的“reference”不太明确;#2中的第一个import生成了一个本地引用,指向绑定到modulepackage.subpackage.module - Ignacio Vazquez-Abrams
是的,但那是一个“模块”,我无法导入它;-) - Hibou57

0

如果你只是想在全局命名空间中获取attribute1,那么版本3似乎就可以了。为什么要使用过度的前缀?

在版本2中,而不是

from module import attribute1

你可以做到

attribute1 = module.attribute1

attribute1 = module.attribute1 只是重复名称,没有增加任何价值。我知道它可以工作,但我不喜欢这种风格(这并不意味着我不喜欢你的回答)。 - Hibou57
3
我猜想,像这里评论的所有人一样,我不明白你想做什么。在你提供的所有示例中,似乎你想从子包中获得一个符号并将其放入你的命名空间中。你的不可行示例(示例2)想通过导入包中的子模块,然后导入该子模块中的符号来实现。我不知道为什么你要分两步来做这件事而不是一步。也许可以更多地解释你的理想解决方案是什么以及为什么要这样做。 - Thomas Vander Stichele

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