如何将子模块名称排除在Python包的命名空间之外?
我想让某个模块的接口里包含一些特定的函数和类(就这些,不要别的)。我可以把这些都放在一个文件里,这样就能轻松实现我想要的接口。但是因为代码很多,我更希望把它们分成几个文件,比如说:
mypackage/
__init__.py
a.py
b.py
c.py
d.py
为了实现我想要的接口,我在这个包里定义了一个 __init__.py
文件,里面导入了 a
、b
、c
和 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
那么这个包的命名空间里也会包含 a
、b
、c
和 d
这些符号。这些名字是实现细节,不是我想要的接口的一部分。我不想让它们作为“公共”符号出现。有没有什么好的办法可以把它们去掉呢?
我考虑过的选项有:
使用一个单独的模块,而不是包。接口看起来没问题,但实现会变得不太清晰。
在
__init__.py
的末尾添加这一行:del a, b, c, d
这样可以,但感觉有点像是变通方法。(比如说,你不能再
import __init__
,而没有这一行是可以的。)把
a
、b
、c
和d
重命名为_a
、_b
、_c
和_d
。这样它们就作为“私有”符号包含在mypackage
的命名空间里,我对此没问题,但所有文件名都以下划线开头感觉有点奇怪(实际上,当然还有超过四个子模块)。
有没有更好的建议?或者对哪个选项更好的看法?
还是说我太过于纠结,不应该在意这些?
4 个回答
这里有一个灵感来自于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,哪些是内部使用的。
如果一个包里的某些文件确实是实现细节,那就可以在它们前面加个下划线——这就是我们这么做的原因。
比如,你看看 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
在那个文件里也成了实现细节。
如果你真的想把命名空间里的名字去掉,你只需要用 del
这个命令,名字就会像风一样消失。