使另一个模块可从我的Python模块导入

时间:2019-08-16 14:31:57

标签: python python-import python-module python-packaging

我有这样的Python模块目录结构:

my_module
|--__init__.py
|--public_interface
|  |--__init__.py
|  |--my_sub_module
|  |  |--__init__.py
|  |  |--code.py
|  |--some_more_code.py
|--other directories omitted

现在,public_interface目录(在其他目录中)仅用于将代码组织成逻辑子单元,作为对我和其他开发人员的指导。最终用户my_module只能将其视为my_module.my_sub_module,而中间没有public_interface

我写了这些__init__.py文件:

  

my_module.__init__.py

     
from .public_interface import *

  

my_module.public_interface.__init__.py

     
from . import my_sub_module
from .some_more_code import *

  

my_module.public_interface.my_sub_module.__init__.py

     
from .code import *

只要用户仅导入顶层模块,此方法就可以正常工作:

import my_module

my_module.my_sub_module.whatever  # Works as intended

但是,这不起作用:

from my_module import my_sub_module

否:

import my_module.my_sub_module

要使这最后两个导入有效,我需要更改什么?

1 个答案:

答案 0 :(得分:1)

导入系统仅允许将实际的软件包和模块作为点分模块名称的一部分直接导入,但是您必须:

from .public_interface import *

hack只是使my_sub_modulemy_module包的属性,而不是出于导入系统目的的实际子模块。出于相同原因而中断:

from collections._sys import *

中断;是的,作为实现细节,collections包正好以别名sys的形式导入_sys,但这实际上并没有使_sys成为collections的子包。 ,它只是collections包中许多属性之一。从导入机制的角度来看,my_sub_module不再是my_module的子模块,而_sys却是collections的子模块;嵌套在my_module下的子目录中的事实无关紧要。

也就是说,导入系统提供了一个挂钩,使您可以将其他任意目录视为软件包the __path__ attribute的一部分。默认情况下,__path__仅包含包本身的路径(因此my_module的{​​{1}}默认为__path__),但是您可以根据需要以编程方式对其进行操作;解析子模块时,它将仅搜索['/absolute/path/to/my_module']的最终内容,就像导入顶级模块搜索__path__一样。因此,要解决您的特殊情况(要使sys.path中的所有程序包/模块都可导入而无需在导入行中指定public_interface),只需更改您的public_interface文件,使其具有以下内容: / p>

my_module/__init__.py

所有操作都告诉导入系统,当import os.path __path__.append(os.path.join(os.path.dirname(__file__), 'public_interface')) 出现时(import mymodule.XXXX是实名的占位符),如果找不到XXXX或{{1 }},它应该寻找my_module/XXXXmy_module/XXXX.py。如果要先搜索my_module/public_interface/XXXX,请将其更改为:

my_module/public_interface/XXXX.py

或仅 检查public_interface(因此根本无法导入__path__.insert(0, os.path.join(os.path.dirname(__file__), 'public_interface')) 下的任何内容),请使用:

public_interface

完全替换my_module的内容。


旁注:您可能想知道为什么__path__[:] = [os.path.join(os.path.dirname(__file__), 'public_interface')] 是该规则的例外;在CPython上,__path__是具有属性os.path的普通模块(根据平台的不同,它恰好是模块ospath),但是您可以执行{{1 }}。之所以可行,是因为posixpath模块在​​被导入时显式地(并且很隐秘地)填充了ntpath的{​​{1}}缓存。这是不正常的,而且会降低性能。 import os.path必须始终隐式导入os,即使从未使用过sys.modules中的任何内容。 os.path避免了这个问题;除非要求,否则不会导入任何内容。

通过使import os包含以下内容,您可以达到相同的结果:

os.path

这将允许人们使用仅完成os.path的{​​{1}},但是会{em>强制 __path__中的任何my_module/__init__.py来导入{{ 1}}和import sys from .public_interface import my_sub_module sys.modules['my_module.my_sub_module'] = my_sub_module ,即使从未使用my_module.my_submodule中的任何内容。 import my_module出于历史原因而继续这样做(很久以前仅使用import的{​​{1}} API,并且很多代码都依赖于这种不当行为,因为程序员是懒惰的并且可以工作),但是新代码不应使用此hack。