【问题标题】:Excluding modules when importing everything in __init__.py在 __init__.py 中导入所有内容时排除模块
【发布时间】:2020-04-17 18:17:46
【问题描述】:

问题

考虑以下布局:

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))]

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

【问题讨论】:

  • 是的,这正是它的用途。
  • 定义__all__后别忘了做del types
  • 我已经发布了答案。希望它能增加您已经发现的内容。

标签: python python-import


【解决方案1】:

一方面有很多很好的理由不做明星导入,但另一方面,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__ 的命名空间中运行模块的代码。任何在顶层绑定的名称,无论是通过类定义、函数定义、直接赋值还是其他方式,都存在于该字典中。有时,需要清理中间变量,正如我建议对 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,也可以。类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

所以是的,请永远不要这样做!但是如果你这样做了,把它放在某个实用程序类中。

【讨论】:

  • 1.我知道导入* 在完成时会让人皱眉头,这样您就看不到名称的来源,但是将所有内容都导入math_helpers 有什么问题? 2.del types 是干什么用的? 3. 我不明白“因为每个函数对象都有一个绑定到其父模块的__dict__. 的__globals__ 引用” - 谢谢!
  • 我会在使用桌面时更新插图
  • @actual_panda。将所有内容都放入math_helpers 并没有错。您在问题中提出的解决方案绝对是推荐的解决方案。抱歉,如果我的漫谈中不清楚。为了回答你的其他问题,我添加了比你想知道的更多的漫无边际。
猜你喜欢
  • 1970-01-01
  • 2016-06-14
  • 1970-01-01
  • 2015-07-30
  • 2013-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多