通过在__init__.py中导入子包来跳过导入路径中的目录名称

时间:2019-04-18 15:04:23

标签: python python-3.x

我对__init__.py中的导入动态感到困惑。 说我有这个结构:

package
├── __init__.py
└── subpackage
    ├── __init__.py
    └── dostuff.py

我想在dostuff.py中导入内容。我可以这样做:from package.subpackage.dostuff import thefunction,但是我想在import语句中删除subpackage级别,所以看起来像这样:

from package.dostuff import thefunction

我尝试将其放入package/__init__.py

from .subpackage import dostuff

我不明白的是:

# doing this works:
from package import dostuff
dostuff.thefunction()

# but this doesn't work:
from package.dostuff import thefunction
# ModuleNotFoundError: No module named 'package.dostuff'

为什么会这样,如何使from package.dostuff import thefunction工作?

2 个答案:

答案 0 :(得分:2)

我看到要实现目标的唯一方法是实际创建一个package/dostuff.py模块并将其中需要的所有内容导入为from .subpackage.dostuff import thefunction

问题在于,当您在from .subpackage import dostuff中使用package/__init__.py时,不会重命名原始模块。

更明确地说,这是同时使用导入和package/dostuff.py文件的示例:

# We import the dostuff link from package
>>> from package import dostuff
>>> dostuff
<module 'package.subpackage.dostuff' from '/tmp/test/package/subpackage/dostuff.py'>

# We use our custom package.dostuff
>>> from package.dostuff import thefunction
>>> package.dostuff
<module 'package.dostuff' from '/tmp/test/package/dostuff.py'>
>>> from package import dostuff
>>> dostuff
<module 'package.dostuff' from '/tmp/test/package/dostuff.py'>

# The loaded function is the same
>>> dostuff.thefunction
<function thefunction at 0x7f95403d2730>
>>> package.dostuff.thefunction
<function thefunction at 0x7f95403d2730>

更明确的说法是:

from X import Y仅在 X 是实际模块路径时才有效。 相反, Y 可以是此模块中导入的任何项目。

这也适用于在__init__.py中声明了任何内容的软件包。在这里,您在package.subpackage.dostuff中声明了package模块,因此可以导入并使用它。

但是,如果您尝试使用该模块进行直接导入,则该模块必须存在于文件系统中

资源:

我希望情况会更清楚

答案 1 :(得分:1)

实际上可以通过摆弄Python的sys.modules字典很容易地伪造此内容。问题是您是否真的需要这个,或者花点时间考虑一下您的包结构是否会很好。

我个人认为这种样式不好,因为它对模块和程序包名称施加了魔力,可能使用和扩展您程序包的人将很难弄清楚那里发生了什么。

按照上面的结构,将以下代码添加到package/__init__.py中:

import sys

from .subpackage import dostuff

# This will be package.dostuff; just avoiding to hard-code it.
_pkg_name = f"{__name__}.{dostuff.__name__.rsplit('.', 1)[1]}"

if _pkg_name not in sys.modules.keys():
    dostuff.__name__ = _pkg_name  # Will have no effect; see below
    sys.modules[_pkg_name] = dostuff

这会将dostuff模块从您的subpackage导入到package的范围内,更改其模块路径并将其添加到导入的模块中。本质上,这只是将模块的绑定复制到成员内存地址保持不变的另一个导入路径。您只需复制参考:

import package
print(package.dostuff)
print(package.subpackage.dostuff)
print(package.dostuff.something_to_do)
print(package.subpackage.dostuff.something_to_do)

...产生

<module 'package.subpackage.dostuff' from '/path/package/subpackage/dostuff.py'>
<module 'package.subpackage.dostuff' from '/path/package/subpackage/dostuff.py'>
<function something_to_do at 0x1029b8ae8>
<function something_to_do at 0x1029b8ae8>

请注意

  1. 即使在package.subpackage.dostuff中进行了更新,模块名称package/__init__.py仍未更改
  2. 函数引用相同:0x1029b8ae8

现在,您也可以去

from package.dostuff import something_to_do
something_to_do()

但是要小心。在模块导入期间更改导入的模块可能会产生意想不到的副作用(也可能与更新sys.modules以及从package导入其他子包或子模块的顺序有关)。通常,您通过应用这种“改进”来购买额外的工作和额外的复杂性。最好设置合适的包装结构并坚持下去。