在 __init__.py 中导入所有内容时排除模块
Excluding modules when importing everything in __init__.py
问题
考虑以下布局:
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是成年人同意的。
__all__
是确定在明星导入中显示的内容的推荐方法。您的做法是正确的,您可以在完成后进一步清理命名空间:
import types
__all__ = [name for name, thing in globals().items()
if not (name.startswith('_') or isinstance(thing, types.ModuleType))]
del types
虽然不那么推荐,但您也可以直接从模块中清理元素,这样它们就不会出现了。如果您需要在模块中定义的函数中使用它们,这将是一个问题,因为每个函数对象都有一个绑定到其父模块的 __dict__
的 __globals__
引用。但是如果你只导入 math_helpers
来调用 math_helpers.foo()
,并且不需要在模块的其他地方持久引用它,你可以在最后简单地取消链接:
del math_helpers
长版
模块导入 运行 模块的代码在模块的 __dict__
的命名空间中。在顶层绑定的任何名称,无论是通过 class 定义、函数定义、直接赋值还是其他方式,都存在于那个字典中。有时,需要清理中间变量,正如我建议的 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
,它将从命名空间中删除该名称。显然这是安全的,因为一旦构建了 __all__
,types
就不会在任何地方被引用。
同样,如果您想通过添加 del np
来删除 np
,那也可以。 class A
完全由模块代码末尾构建,因此它不需要全局名称 np
来引用其父级 class.
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
(基于 )来实现这一点:
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
所以是的,请永远不要这样做!但是如果你这样做了,把它放在一个实用程序 class 的某个地方。
问题
考虑以下布局:
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是成年人同意的。
__all__
是确定在明星导入中显示的内容的推荐方法。您的做法是正确的,您可以在完成后进一步清理命名空间:
import types
__all__ = [name for name, thing in globals().items()
if not (name.startswith('_') or isinstance(thing, types.ModuleType))]
del types
虽然不那么推荐,但您也可以直接从模块中清理元素,这样它们就不会出现了。如果您需要在模块中定义的函数中使用它们,这将是一个问题,因为每个函数对象都有一个绑定到其父模块的 __dict__
的 __globals__
引用。但是如果你只导入 math_helpers
来调用 math_helpers.foo()
,并且不需要在模块的其他地方持久引用它,你可以在最后简单地取消链接:
del math_helpers
长版
模块导入 运行 模块的代码在模块的 __dict__
的命名空间中。在顶层绑定的任何名称,无论是通过 class 定义、函数定义、直接赋值还是其他方式,都存在于那个字典中。有时,需要清理中间变量,正如我建议的 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
,它将从命名空间中删除该名称。显然这是安全的,因为一旦构建了 __all__
,types
就不会在任何地方被引用。
同样,如果您想通过添加 del np
来删除 np
,那也可以。 class A
完全由模块代码末尾构建,因此它不需要全局名称 np
来引用其父级 class.
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
(基于 )来实现这一点:
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
所以是的,请永远不要这样做!但是如果你这样做了,把它放在一个实用程序 class 的某个地方。