如何在python模块中执行导入而不污染其命名空间?

2024-05-12 22:37:04 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在开发一个处理一些科学数据的Python包。在包的任何模块中定义的每个函数中,实际上都需要来自其他模块和包(包括numpy)的多个常用类和函数。

对付他们的方法是什么?我考虑过多个变体,但每个都有自己的缺点。

  • 使用from foreignmodule import Class1, Class2, function1, function2
    导入模块级的类 然后导入的函数和类很容易从每个函数访问。另一方面,它们污染了模块名称空间,使得dir(package.module)help(package.module)与导入的函数杂乱无章

  • 使用from foreignmodule import Class1, Class2, function1, function2
    在函数级导入类 函数和类很容易访问,并且不会污染模块,但是每个函数中多达十几个模块的导入看起来都是大量重复的代码。

  • 使用import foreignmodule
    在模块级导入模块 不需要为每个函数或类调用预先设置模块名来补偿太多的污染。

  • 使用一些人工的解决方法,比如对所有这些操作使用函数体,并只返回要导出的对象。。。像这样

    def _export():
        from foreignmodule import Class1, Class2, function1, function2
        def myfunc(x):
            return function1(x, function2(x))
        return myfunc
    myfunc = _export()
    del _export
    

    这设法解决了这两个问题,模块命名空间污染和易于使用的功能。。。但它似乎一点也不Python。

那么,什么样的解决方案是最Python?有没有另一个我忽略的好办法?


Tags: 模块方法函数fromimportpackage空间export
3条回答

我见过使用的一种技术,包括在标准库中,是使用import module as _modulefrom module import var as _var,即将导入的模块/变量分配给以下划线开头的名称。

其结果是,其他代码遵循通常的Python约定,将这些成员视为私有成员。这甚至适用于不查看__all__的代码,例如IPython的autocomplete函数。

来自Python 3.3的random模块的示例:

from warnings import warn as _warn
from types import MethodType as _MethodType, BuiltinMethodType as _BuiltinMethodType
from math import log as _log, exp as _exp, pi as _pi, e as _e, ceil as _ceil
from math import sqrt as _sqrt, acos as _acos, cos as _cos, sin as _sin
from os import urandom as _urandom
from collections.abc import Set as _Set, Sequence as _Sequence
from hashlib import sha512 as _sha512

另一种技术是在函数范围内执行导入,以便它们成为局部变量:

"""Some module"""
# imports conventionally go here
def some_function(arg):
    "Do something with arg."
    import re  # Regular expressions solve everything
    ...

这样做的主要原因是它实际上是懒惰的,延迟了模块依赖项的导入,直到它们被实际使用。假设模块中的一个函数依赖于一个特定的大型库。导入文件顶部的库意味着导入模块将加载整个库。这样,导入模块就可以很快,而且只有真正调用该函数的客户机代码才会产生加载库的成本。此外,如果依赖关系库不可用,则不需要依赖特性的客户端代码仍然可以导入模块并调用其他函数。缺点是使用函数级导入会模糊代码的依赖关系。

来自Python 3.3的示例os.py

def get_exec_path(env=None):
    """[...]"""
    # Use a local import instead of a global import to limit the number of
    # modules loaded at startup: the os module is always loaded at startup by
    # Python. It may also avoid a bootstrap issue.
    import warnings

继续执行通常的from W import X, Y, Z,然后使用__all__特殊符号来定义您希望用户从模块中导入的实际符号:

__all__ = ('MyClass1', 'MyClass2', 'myvar1', …)

这定义了如果符号从您的模块import *导入到用户模块中的符号。

一般来说,Python程序员应该而不是使用dir()来找出如何使用您的模块,如果他们这样做,则可能表示其他地方有问题。他们应该阅读您的文档或键入help(yourmodule)来了解如何使用您的库。或者他们可以自己浏览源代码,在这种情况下(a)您导入的东西和您定义的东西之间的区别非常明显,(b)他们将看到__all__声明并知道他们应该玩哪些玩具。

如果你试图在这样的情况下支持一个不是为其设计的任务,dir(),你将不得不对自己的代码设置恼人的限制,我希望这里的其他答案是清楚的。我的建议是:不要这样做!请看一下标准库以获得指导:只要代码清晰和简洁性需要,它就会from … import …,并提供(1)信息性文档字符串,(2)完整文档和(3)可读代码,这样就不必在模块上运行dir(),并尝试将导入与模块中实际定义的内容区分开来。

将模块作为一个整体导入:import foreignmodule。你所说的缺点实际上是一种好处。也就是说,预先设置模块名可以使代码更易于维护,并使其更具自文档性。

六个月后,当你看到像foo = Bar(baz)这样的一行代码时,你可能会问自己Bar来自哪个模块,但对于foo = cleverlib.Bar来说,这就不那么神秘了。

当然,进口越少,问题就越小。对于依赖关系很少的小程序来说,这并不重要。

当你发现自己问这样的问题时,问问自己是什么使代码更容易理解,而不是什么使代码更容易编写。你写过一次,但你读了很多。

相关问题 更多 >