[在__init__.py中导入所有内容时不包括模块

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

问题

请考虑以下布局:

package/
    main.py
    math_helpers/
        mymath.py
        __init__.py

mymath.py包含:

import math

def foo():
    pass

main.py中,我希望能够像这样使用mymath.py中的代码:

import math_helpers
math_helpers.foo()

为了这样做,__init__.py包含:

from .mymath import *

但是,在mymath.py中导入的模块现在位于math_helpers命名空间中,例如math_helpers.math是可访问的。


当前方法

我在mymath.py的末尾添加以下内容。

import types
__all__ = [name for name, thing in globals().items()
          if not (name.startswith('_') or isinstance(thing, types.ModuleType))]

这似乎可行,但这是正确的方法吗?

python python-import
1个回答
1
投票
一方面,有很多理由不进行星级导入,但另一方面,python是给成年人使用的。

__all__是确定星型导入中出现的内容的推荐方法。您的方法是正确的,完成后可以进一步清理名称空间:

import types __all__ = [name for name, thing in globals().items() if not (name.startswith('_') or isinstance(thing, types.ModuleType))] del types

虽然不建议使用,但您也可以直接在模块外清理元素,以使它们根本不显示。如果您需要在模块中定义的函数中使用它们,这将是一个问题,因为每个函数对象都有一个__globals__引用,该引用绑定到其父模块的__dict__。但是,如果仅导入math_helpers来调用math_helpers.foo(),并且不需要在模块的其他位置对其进行持久引用,则只需在最后将其取消链接即可:

del math_helpers

Long Version

模块导入在模块__dict__的命名空间中运行模块的代码。该词典中存在任何通过类定义,函数定义,直接分配或其他方式在顶层绑定的名称。有时,像我建议使用types一样,需要清理中间变量。

假设您的模块如下所示:

test_module.py

import math import numpy as np def x(n): return math.sqrt(n) class A(np.ndarray): pass import types __all__ = [name for name, thing in globals().items() if not (name.startswith('_') or isinstance(thing, types.ModuleType))]
在这种情况下,__all__将为['x', 'A']。但是,模块本身将包含以下名称:'math', 'np', 'x', 'A', 'types', '__all__'

如果最后运行del types,它将从名称空间中删除该名称。显然,这是安全的,因为一旦构建了types,就不会在任何地方引用__all__

类似地,如果您想通过添加np来删除del np,那就可以了。在模块代码的末尾完全构造了类A,因此它不需要全局名称np即可引用其父类。

math并非如此。如果要在模块代码的末尾执行del math,则功能x将不起作用。如果导入模块,则可以看到x.__globals__是模块的__dict__

import test_module test_module.__dict__ is test_module.x.__globals__

如果从模块词典中删除math并调用test_module.x,则会得到

NameError: name 'math' is not defined

因此,在某些非常特殊的情况下,您可能可以清理mymath.py的名称空间,但这不是推荐的方法,因为它仅适用于某些情况。

最后,坚持使用__all__

一个类似的故事

一次,我有两个模块实现了相似的功能,但是适用于不同类型的最终用户。我想从模块a中复制到模块b中,有几个功能。问题是我希望这些功能像在模块b中定义的那样工作。不幸的是,它们依赖于a中定义的常数。 b定义了自己的常量版本。例如:

a.py

value = 1 def x(): return value

b.py

from a import x value = 2
我希望b.x访问b.value而不是a.value。我通过在b.py中添加以下内容(基于https://stackoverflow.com/a/13503277/2988730)实现了这一目标:

import functools, types x = functools.update_wrapper(types.FunctionType(x.__code__, globals(), x.__name__, x.__defaults__, x.__closure__), x) x.__kwdefaults__ = x.__wrapped__.__kwdefaults__ x.__module__ = __name__ del functools, types

我为什么要告诉你这一切?好吧,您可以为模块创建一个版本,该版本在名称空间中没有任何杂乱的名称。但是,您将看不到函数中全局变量的更改。这只是使python超出其正常使用范围的一种做法。我强烈不建议这样做,但这是一个示例模块,可以有效冻结涉及功能的__dict__。它具有与上述test_module相同的成员,但全局名称空间中没有模块:

import math import numpy as np def x(n): return math.sqrt(n) class A(np.ndarray): pass import functools, types, sys def wrap(obj): """ Written this way to be able to handle classes """ for name in dir(obj): if name.startswith('_'): continue thing = getattr(obj, name) if isinstance(thing, FunctionType) and thing.__module__ == __name__: setattr(obj, name, functools.update_wrapper(types.FunctionType(thing.func_code, d, thing.__name__, thing.__defaults__, thing.__closure__), thing) getattt(obj, name).__kwdefaults__ = thing.__kwdefaults__ elif isinstance(thing, type) and thing.__module__ == __name__: wrap(thing) d = globals().copy() wrap(sys.modules[__name__]) del d, wrap, sys, math, np, functools, types

所以,请永远不要这样做!但是,如果这样做,请将其放在实用程序类中的某个位置。
© www.soinside.com 2019 - 2024. All rights reserved.