TL;DR:如何读取模块的
__all__
定义并将其动态添加到包级别 __init__.py
而无需在模块本身中实际运行任何缓慢的代码?
我正在编写一个库,并且有一个与此不同的包结构:
library/
package1/
__init__.py # sub-package __init__
_module_a.py
_module_b.py
package2/
__init.__py # package level __init__
subpackage/
__init__.py # sub-package __init__
_module_d.py
_module_e.py
_module_f.py
_module_g.py
__init__.py # Library level __init__
我在所有模块上使用“_”前缀,因为我想严格控制用户在调用诸如
dir(library.package1)
之类的内容时可以看到的内容。为此,我确保每个模块都定义了一个 __all__
列表。
例如,
"""Inside of _module_e.py"""
import time
__all__ = ["Foo", "Bar"]
# do computationally intensive stuff
time.sleep(5)
class Foo:
pass
class Bar:
pass
和
"""Inside of _module_f.py"""
import time
__all__ = ["Baz"]
# do more stuff that takes a long time
time.sleep(5)
class Baz:
pass
为了确保时间不会浪费在运行 all 计算量大的代码上,想要使用
Baz
类的用户通常可能会这样写
from library.package2.subpackage._module_f import Baz
但我认为这比写一些像
from library.package2.subpackage import Baz
这样的好东西要笨拙得多。显然,我必须在子包的 _init_.py 文件中执行一些操作才能启用此所需的导入行为。
在不重组我的文件的情况下,是否可以在需要时动态导入模块?我应该以某种方式重组/重构我的文件吗?我还缺少其他方法吗?
我知道我可以在 _
init_.py 文件中定义一个
__getattr__(name)
并使用 importlib
从模块动态导入,但这仍然需要我手动复制每个模块的 __all__
列表的内容进入 _init_.py 文件的
__all__
列表,如下
"""Inside of subpackage/__init__.py"""
import importlib
# I have to create the below dictionary and maintain it manually!!!
defined_classes = {
"Foo": "_module_e",
"Bar": "_module_e",
"Baz": "_module_f"
}
__all__ = [] + list(defined_classes.keys())
def __dir__():
return __all__
def __getattr__(name):
if name in defined_classes:
file = defined_classes[name]
return getattr(_importlib.import_module(f'library.package2.subpackage.{file}'), name)
else:
try:
return globals()[name]
except KeyError:
raise AttributeError(f"Module 'subpackage' has no attribute '{name}'")
我确信我可以编写一个快速而肮脏的方法来
with open(filename) as f
并解析每个模块文件的行,直到找到看起来像 __all__
列表的东西来按程序生成我的 defined_classes
映射,但我不这样做知道做到这一点的最佳方法是什么(或者是否已经有更好的 Python 原生解决方案)。
我得出的结论是,最好重构我的模块,以便缓慢的代码仅在根据需要调用(和缓存)的函数内部运行 - 并且决定何时运行此代码的负担不应该下降在 init.py.
不搞乱 init 的另一个好处是我的 IDE 可以更好地理解正在发生的事情并为我提供相关的工具提示。