防止Python包重新导出导入的名称

Bol*_*wyn 19 python module

在Python包中,我有文件结构

package/
    __init__.py
    import_me.py
Run Code Online (Sandbox Code Playgroud)

该文件import_me.py被认为提供了功能片段:

import re
import sys

def hello():
    pass
Run Code Online (Sandbox Code Playgroud)

这样就package.import_me.hello可以通过动态导入import.不幸的是,这也让进口resyspackage.import_me.repackage.import_me.sys分别.

有没有办法防止导入的模块import_me.py再次重新导出?优选地,这应该超出名称修改或下划线前缀导入的模块,因为在我的情况下,它可能在某些情况下造成安全问题.

Eth*_*man 13

没有简单的方法可以禁止从模块导入全局名称; Python根本就不是这样构建的.

如果您编写自己的__import__函数并隐藏内置函数,则可能实现令人生畏的目标,但我怀疑时间和测试的成本是否值得,也不完全有效.

您可以做的是使用前导下划线导入依赖模块,这是用于传达" 实现细节,使用风险自负 " 的标准Python习惯用法:

import re as _re
import sys as _sys

def hello():
    pass
Run Code Online (Sandbox Code Playgroud)

注意

虽然删除导入的模块作为一种不允许导入它们的方式似乎可能有用,但它实际上并没有:

import re
import sys

def hello():
    sys
    print('hello')

del re
del sys
Run Code Online (Sandbox Code Playgroud)

然后导入和使用hello:

>>> import del_mod
>>> del_mod.hello()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "del_mod.py", line 5, in hello
    sys
NameError: global name 'sys' is not defined
Run Code Online (Sandbox Code Playgroud)

  • 这是“现实生活中”的运作方式吗?例如,当导入 `pandas as pd` 时,我不能执行 `pd.np`,即使 Pandas 显然使用 Numpy。*更正*事实证明你*可以*做`pd.np`。哇。从来没有注意到这一点。 (4认同)
  • 是的,我担心会出现这种情况,而没有推出轨道炮(超载`__import__`,沙箱,......)。谢谢你的回答! (2认同)

kdb*_*kdb 8

1.初始化函数

另一种方法可能是将定义包装到初始化函数中

## --- exporttest.py ---
def _init():
    import os                       # effectively hidden

    global get_ext                  # effectively exports it
    def get_ext(filename):
        return _pointless_subfunc(filename)

                                      # underscore optional, but good 
    def _pointless_subfunc(filename): # for the sake of documentation
        return os.path.splitext(filename)[1]

    if __name__ == '__main__':      # for interactive debugging (or doctest)  
        globals().update(locals())  # we want all definitions accessible
        import doctest
        doctest.testmod()
        
_init()

print('is ``get_ext`` accessible?           ', 'get_ext' in globals())
print('is ``_pointless_subfunc`` accessible?', '_pointless_subfunc' in globals())
print('is ``os`` accessible?                ', 'os' in globals())
Run Code Online (Sandbox Code Playgroud)

比较:

>>> python3 -m exporttest
is ``get_ext`` accessible?            True
is ``_pointless_subfunc`` accessible? True
is ``os`` accessible?                 True

>>> python3 -c "import exporttest"
is ``get_ext`` accessible?            True
is ``_pointless_subfunc`` accessible? False
is ``os`` accessible?                 False
Run Code Online (Sandbox Code Playgroud)

1.1. 好处

  • 实际隐藏进口。
  • 更方便交互式代码探索,因为 dir(exporttest)它没有杂乱。

1.2. 缺点

  • 可悲的是,与import MODULE as _MODULE模式不同,它不能很好地与 pylint 配合使用。

    C:  4, 4: Invalid constant name "get_ext" (invalid-name)
    W:  4, 4: Using global for 'get_ext' but no assignment is done (global-variable-not-assigned)
    W:  5, 4: Unused variable 'get_ext' (unused-variable)
    
    Run Code Online (Sandbox Code Playgroud)

2. 拥抱 __all__

在进一步的阅读中,我发现Python的方式做到这一点是依靠__all__。它不仅控制导出的内容from MODULE import *,还控制显示的内容help(MODULE),根据“我们都是成年人”的口头禅,如果用户使用未公开记录的任何内容,则是用户自己的错。

2.1. 好处

工装在这种方法(例如,通过编辑器支持autoimports经历了最好的支持importmagic库)。

2.2. 缺点

就我个人而言,我觉得整个“我们都是成年人”的口头禅相当天真;当在时间压力下工作而在交付更改之前没有机会完全理解代码库时,我们可以利用我们所能获得的任何帮助来防止“射中自己的脚”的情况。此外,即使是许多流行的软件包也没有真正遵循最佳实践,例如提供有用的交互式文档字符串或定义__all__. 但这pythonic的方式。


Mon*_*eal 5

没有简单的方法可以禁止从模块中导入全局名称。但实际上,您不需要。Python允许使用本地导入而不是全局导入:

def foo():
    import sys
    print(sys.copyright)

sys.copyright # Throws NameError
Run Code Online (Sandbox Code Playgroud)

简洁明了。

实际上,我认为使用本地进口应该是一个好习惯,而全球进口仅仅是对C或对C的继承的敬意。

UPD:明显的缺点是,import sys每次调用此函数都会执行该语句,这可能负担不起。但是您可以改为创建一个可调用对象:

class Callable(object):
    import sys as _sys
    def __call__(self):
        print(self._sys.copyright)

foo = Callable()
foo()
Run Code Online (Sandbox Code Playgroud)

尽管我个人不喜欢这种方法,但对于通用类可能会更好。