如何重新导出 python __init__.py 文件中星号 (*) 导入的所有内容

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

当我编写 python 库时,我经常会得到这样的结果:

/my_lib
   __init__.py
   module_a.py
   module_b.py
   ...

现在假设

module_a
module_b
非常大,并且定义了很多公共和非公共方法。例如:

import ...
from ... import ...
... etc

__all__ = [
    "PublicClass",
    "public_fn",
    "PUBLIC_STATIC_VAR",
    "OtherPublicClass",
    ...
]

class _PrivateClass:
    ...

我花了很多精力精心策划了一个经过深思熟虑的符号列表,以便在

__all__
中导出。现在在我的
__init__.py
中我想这样做(当然这是行不通的):

from my_lib.module_a import * as module_a
from my_lib.module_b import * as module_b
from third_party_lib import * as third_party
from other_lib import OtherA, OtherB

__all__ = [
    *module_a,
    *module_b,
    *third_party,
    "OtherA",
    "OtherB",
]

本质上,我想要的是,如果有人导入

my_lib
,他们可以从同一命名空间访问
module_a
module_b
third_party
中的所有公共符号。即:

import my_lib

obj1 = my_lib.pub_fn_from_module_a()
obj2 = my_lib.PubClassFromModuleB()
obj3 = my_lib.CONSTANT_FROM_THIRD_PARTY_LIB
obj4 = my_lib.OtherA()
obj5 = my_lib.OtherB()

糟糕的解决方案:

1.隐含的
__all__

我知道我能做到:

from my_lib.module_a import *
from my_lib.module_b import *
from third_party_lib import *
from other_lib import OtherA, OtherB

# __all__ is not defined in __init__.py

这将隐式定义

__all__
并实现所需的行为,但静态代码分析工具无法将其与死代码区分开来。例如,预提交挂钩或代码格式化工具可能会删除这些导入。

2.重新输入所有内容

我也可以:

from my_lib.module_a import (
    # <type all public symbols from module_a here>
)
from my_lib.module_b import (
    # <type all public symbols from module_b here>
)
from third_party_lib import (
    # <type all public symbols from third_party_lib here>
)
from other_lib import OtherA, OtherB

__all__ = [
    # <copy-paste the body of module_a.__all__ here>
    # <copy-paste the body of module_b.__all__ here>
    # <type all public symbols from third_party_lib here>
    "OtherA",
    "OtherB",
]

但现在我基本上必须在

module_a
中重新输入每个符号的名称三次,一次在
module_a.__all__
中,一次在
__init__.py
的导入中,一次在
__all__
中的
__init__.py
定义中。
module_b
也是如此,我还需要确保所有 3 在开发过程中保持同步。

3.重复使用
__all__

这种方法可行,但看起来真的很尴尬:

from my_lib.module_a import * as module_a
from my_lib.module_b import * as module_b
from my_lib import module_a, module_b
from third_party_lib import * as third_party
from other_lib import OtherA, OtherB

# only purpose of `_keep` is to fake the usage of a few symbols 
# such that the star imports are not flagged as dead code.
# It does nothing at runtime.
def _keep(x): pass

_keep(random_symbol_from_module_a)
_keep(random_symbol_from_module_b)
_keep(random_symbol_form_third_party)

__all__ = [
    *module_a.__all__,
    *module_b.__all__,
    *third_party_lib.__all__,
    "OtherA",
    "OtherB",
]

需要导入星星 (

from ... import *
),因为例如pycharm 或其他静态代码分析工具无法推断
*module_a.__all__
语句的作用。因此,如果没有明星导入,静态代码分析工具将报告到处都缺少导入。

python python-3.x package
1个回答
0
投票

您想要的代码模式的一个很好的示例可以在标准库的

__init__
包的
asyncio
模块中找到: https://github.com/python/cpython/blob/1b1f8398d0ffe3c8ba2cca79d0c0f19a6a34e72a/Lib/asyncio/__init__.py

from .base_events import *
from .coroutines import *
from .events import *
from .exceptions import *
from .futures import *
from .locks import *
from .protocols import *
from .runners import *
from .queues import *
from .streams import *
from .subprocess import *
from .tasks import *
from .taskgroups import *
from .timeouts import *
from .threads import *
from .transports import *

__all__ = (base_events.__all__ +
           coroutines.__all__ +
           events.__all__ +
           exceptions.__all__ +
           futures.__all__ +
           locks.__all__ +
           protocols.__all__ +
           runners.__all__ +
           queues.__all__ +
           streams.__all__ +
           subprocess.__all__ +
           tasks.__all__ +
           taskgroups.__all__ +
           threads.__all__ +
           timeouts.__all__ +
           transports.__all__)

如您所见,它采用与解决方案 #3 基本相同的方法,但它不是解压所有

__all__
列表,而是使用
+
运算符来加入它们,事实证明,大多数静态分析器都装备得更好静态推断与
+
运算符连接的列表的结果值,因此通过这种方法,静态分析器不会抱怨到处都缺少导入。

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