如何使 Python 模块的 __init__.py 将其 help() 委托给同级文件?

Posted

技术标签:

【中文标题】如何使 Python 模块的 __init__.py 将其 help() 委托给同级文件?【英文标题】:How can I make a Python module's __init__.py delegate its help() to a sibling file? 【发布时间】:2019-01-10 19:50:34 【问题描述】:

目前我们有一个类似如下的 Python 库:

Jupiter/
  __init__.py
  Ganymede.py
  Callisto.py

Ganymede.py 又包含函数Foo()Bar() 等等。我们现有的脚本通过

使用这些功能
from Jupiter import Ganymede
# later in the program...
Ganymede.Foo()

我想重新组织一些东西,使目录看起来更像

Jupiter/
  __init__.py
  Ganymede/
    __init__.py
    functions.py
  Callisto/
    __init__.py
    functions.py

无需破坏或修改任何使用 Jupiter 的现有脚本。

如果Ganymede/__init__.py 使用"The import system" documentation 中描述的导入语法:

from .functions import *

然后Foo()Bar() 最终进入Ganymede 的命名空间,但Ganymede 的help() 没有提及它们。

>>> from Jupiter import Ganymede
>>> dir(Ganymede)
['Bar', 'Foo', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'functions']
>>> help(Ganymede)
Help on package Jupiter.Ganymede in Jupiter:

NAME
    Jupiter.Ganymede

PACKAGE CONTENTS
    functions

FILE
    x:\yadda\yadda\ganymede\__init__.py

我非常非常希望 help(Ganymede) 自动列出所有可用的函数和类。

我想我可以从Ganymede/__init__.pyexecfile("functions.py"),但我觉得必须有一些更清洁的方法来做到这一点。还是没有?

我需要在 Python 2.7.15 和 3.5.3 中都可以使用的东西。

【问题讨论】:

【参考方案1】:

__init__.py 中创建一个__all__


如果您在functions.py 中已经有一个__all__,您可以直接导入它:

from .functions import *
from .functions import __all__

如果要将多个模块合并在一起,则必须更详细一些

from .functions import *
from .classes import *
from . import functions
from . import classes
__all__ = functions.__all__ + classes.__all__

或者,当然,你总是可以明确的:

from .functions import *
__all__ = ['spam', 'eggs']

或者,如果您想动态构建它:

from .functions import *
from . import functions
__all__ = [name for name in dir(functions) if not name.startswith('_')]

… 或者(相当老套——但如果你有一个 __init__.py 从许多子模块中收集名称而不做其他任何事情,有时会很有用)…

from .functions import *
__all__ = [name for name in globals() if not name.startswith('_')]

… 或者你可以变得非常聪明并按照这种方式做事,例如,multiprocessing 会:1

from . import functions
__all__ = [name for name in functions if not name.startswith('_')]
globals().update(name: getattr(functions, name) for name in __all__)

请记住,__all__ 也会影响某人执行from Ganymede import *(实际上是that's the main purpose of __all__)时发生的情况,以及inspect 和其他工具报告为您的包的公共成员的内容。

我不确定在没有__all__ 的情况下help 的行为是否在任何地方都有记录(交互式help 的工作通常只是很少记录......),它与import 的行为不太一样认为是公开的。


1。好吧,multiprocessing 实际上更聪明/更hacky;它不是从子模块中提取属性,而是从该子模块的动态单例属性中提取属性,该属性在主进程和子进程上设置不同,并相应地更改包的一些***功能......

【讨论】:

有没有办法让我实现这一点,而不必在其__all__ 中明确列出functions.py 的每个成员(即,获得help() 的自动查找行为) ? @Crashworks 是的,如果你不介意有点hacky的话。请稍等,我会修改它。 vars 在这里会不会更好?你什么时候想要set(dir()) - set(vars) 中的一些东西? @juanpa.arrivillaga 哦,我想我明白了你的要求。如果模块有自定义__dir____getattr__ 来导出不在其__dict__ 中的动态公共成员,为什么要忽略它们? 哇,感谢您的所有研究!我很感谢你在这方面的时间,但它让我相信我应该把 functions.py 的内容放入 __init__.py 并完成它。

以上是关于如何使 Python 模块的 __init__.py 将其 help() 委托给同级文件?的主要内容,如果未能解决你的问题,请参考以下文章

Python:导入包含的包

Python __init__.py 作用详解

Python __init__.py 作用详解

python基础:__init__.py和__init__函数的作用

如何使包模块中的函数立即可用于 Python3 中的根应用程序

Python使用登录模块