如何动态定义包级别 __all__ 而不在包的组成模块中运行任何缓慢的代码?

问题描述 投票:0回答:1

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 原生解决方案)。

python-3.x dynamic python-import python-importlib
1个回答
0
投票

我得出的结论是,最好重构我的模块,以便缓慢的代码仅在根据需要调用(和缓存)的函数内部运行 - 并且决定何时运行此代码的负担不应该下降在 init.py.

不搞乱 init 的另一个好处是我的 IDE 可以更好地理解正在发生的事情并为我提供相关的工具提示。

© www.soinside.com 2019 - 2024. All rights reserved.