问题 如何将子模块名称保留在Python包的名称空间之外?


我希望某个模块的接口包含一定数量的函数和类(没有别的)。我可以在一个文件中实现所有这些,并且可以轻松获得我想要的界面。但是,由于存在大量代码,我更愿意将整个内容分成几个文件

mypackage/
    __init__.py
    a.py
    b.py
    c.py
    d.py

无论如何要获得所需的界面,我定义了一个 __init__.py 从中导入所有公共符号的包的文件 abc 和 d

from a import func_a1, func_a2, ClassA1, ClassA2
from b import func_b1, func_b2, ClassB1, ClassB2
from c import func_c1, func_c2, ClassC1, ClassC2
from d import func_d1, func_d2, ClassD1, ClassD2

如果我使用导入包

import mypackage

包命名空间还包含符号 abc 和 d。这些名称是实现细节,而不是我的界面的一部分。我不希望它们显示为“公共”符号。摆脱它们的最佳方法是什么?

我考虑的选项是

  1. 使用单个模块而不是包。界面看起来很好,但实现将不如现在清晰。

  2. 添加行

    del a, b, c, d
    

    到了最后 __init__.py。工作正常,但似乎是一个黑客。 (例如,你不能 import __init__ 更多,没有这条线就可以工作。)

  3. 改名 abc 和 d 至 _a_b_c 和 _d。现在他们被包括在内 mypackage作为“私人”符号的命名空间,我很好,但感觉有点奇怪 所有 我的文件名以下划线开头(实际上,当然有四个以上的子模块)。

还有更好的建议?或者想要选择哪个选项?

或者我只是肛门,不应该关心整个事情?


11158
2018-03-10 20:25


起源

不妨去喝汽水。也就是说,除了你之外,没有人真正关心包名称空间中的内容。 - Ignacio Vazquez-Abrams
@Ignacio:可能你是对的:)例如在交互式使用中,当不需要的名称干扰标签扩展时,它会让我烦恼。 - Sven Marnach
这只是我的意见,但是 import __init__ 考虑到所有事情,在备选方案2中似乎更加苛刻。 - JAB


答案:


如果你真的想从命名空间中删除名称,那么你可以使用 del 对他们的陈述,他们会像风一样消失。


4
2018-03-10 20:39





如果包中的某些文件确实是实现细节,请继续在它们前面加上下划线 - 这就是我们使用它们的原因。

例如,如果你查看 ctypes 你会看到的

__init__.py
==================================================
"""create and manipulate C data types in Python"""

import os as _os, sys as _sys

__version__ = "1.1.0"

from _ctypes import Union, Structure, Array
from _ctypes import _Pointer
from _ctypes import CFuncPtr as _CFuncPtr
...

正如你所看到的,甚至 os 和 sys 成为该文件中的实现细节。


8
2017-11-10 00:16





这是一个受Javascript单功能模块启发的解决方案:

def __init__module():
    from os import path

    def _module_export_1():
        return path.abspath('../foo')

    def _module_export_2():
        return path.relpath('foo/bar', 'foo')

    g = globals()
    g['module_export_1'] = _module_export_1
    g['module_export_2'] = _module_export_2

__init__module()

虽然模块需要从os导入'path',但'path'不会污染模块名称空间。模块命名空间中唯一的缺点是__init_module(),它通过双下划线前缀明确标记为私有。

另一种选择是在每个函数的顶部导入所需的模块,而不是模块的顶部。第一次导入模块后,后续导入只是sys.modules字典中的查找。

但我同意这里的其他评论者 - Python约定不要担心模块命名空间污染,只是让模块的用户明白命名空间的哪些部分是你的公共API,哪些是内部的。


0
2018-04-04 17:54



不幸的是,这种方法不起作用。正如您在我的帖子中看到的,我从不导入名称 a 进入包的命名空间 - 我只是使用 from a import func_a1,...。如果我在普通模块中这样做,只有名字 func_a1, .. 将显示在模块的命名空间中。但是由于 a 是一个包的子模块,也是它的名字 a 被插入包的命名空间中。如果我从函数中导入所有内容,也会发生同样的事情。 - Sven Marnach


http://docs.python.org/tutorial/modules.html

import语句使用   遵循惯例:如果一个包   __init__.py代码定义了一个名为__all__的列表,它被视为应该导入的模块名称列表   当从包导入*是   遇到。

在你的 mypackage/__init__.py,尝试添加这个:

# add this line, replace "..." with the rest of the definitions
# you want made public
__all__ = ['func_a1', 'func_a2', 'ClassA1', 'ClassA2', ...]

from a import func_a1, func_a2, ClassA1, ClassA2
from b import func_b1, func_b2, ClassB1, ClassB2
from c import func_c1, func_c2, ClassC1, ClassC2
from d import func_d1, func_d2, ClassD1, ClassD2

0
2018-03-10 20:39



但是直接访问时,这不会影响命名空间的内容。 - Ignacio Vazquez-Abrams
嘎,我误解了你的问题...如果你这样做,这个有用 from mypackage import *,我以为你是。在这种情况下似乎不起作用,即使你定义也没有 __all__ 在你的模块中 a, b等等...... - dcrosta