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

时间:2016-03-15 15:30:57

标签: python module

在Python包中,我有文件结构

package/
    __init__.py
    import_me.py

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

import re
import sys

def hello():
    pass

以便package.import_me.hello可以通过imp动态导入re。不幸的是,这也允许分别将syspackage.import_me.re导入package.import.sysimport_me.py

有没有办法阻止$uibModalInstance.close(JSON.stringify(ar)) 中导入的模块再次重新导出?最好这应该超出名称修改或下划线前缀导入的模块,因为在我的情况下,它可能会在某些情况下造成安全问题。

7 个答案:

答案 0 :(得分:13)

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

如果您编写了自己的__import__函数并隐藏了内置函数,那么您可能会达到令人生畏的目标,但我怀疑时间和测试的成本是值得的还是完全有效的。

您可以做的是使用前导下划线导入依赖模块,这是用于沟通&#34; 实现细节的标准Python习惯用法,使用风险自负&#34;:< / p>

import re as _re
import sys as _sys

def hello():
    pass

注意

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

import re
import sys

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

del re
del sys

然后导入并使用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

答案 1 :(得分:4)

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

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

sys.copyright # Throws NameError

整洁而简单。

实际上,我认为使用本地进口应该是一种很好的做法,而全球性进口只是对C或其遗产的致敬。

UPD :明显的缺点是每次调用此函数时都会执行import sys语句,这可能是无法承受的。 但您可以改为创建一个可调用对象:

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

foo = Callable()
foo()

虽然我个人不喜欢这种方法,但它可以更好地使用泛型类。

答案 2 :(得分:3)

有几种选择:

  1. None放入sys.modules模块中:

    >>> import sys
    >>> import re
    >>> del re
    >>> sys.modules['re'] = None
    >>> import re
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      ImportError: No module named re
    
  2. 使用RestrictedPython packagepysandbox package

  3. 请务必查看this article on Sandboxed Python

答案 3 :(得分:2)

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

进行比较:

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

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)
    

2。拥抱__all__

进一步阅读后,我发现 pythonic方法是依靠__all__。它不仅控制from MODULE import *上导出的内容,还控制help(MODULE)中显示的内容,根据“我们都是这里的成年人”的口号,如果用户不使用任何内容,则是用户自己的错记录为公开。

2.1。优点

工具对此方法提供了最佳支持(例如,通过 importmagic 库通过编辑器支持自动导入)。

2.2。缺点

就我个人而言,我发现整个“我们都是成年人”的口头禅很天真,但它的pythonic方式。

答案 4 :(得分:2)

解决方案可能是重新考虑您的文件结构并创建一个子模块。 使用__init__.py之类的文件来显示所需的变量。

package/
    __init__.py
    import_me/
         __init__.py
         code.py
'''
import_me/code.py
'''

# imports that you do not want to expose
import re
import sys


def hello():
    re.doSomething()
    sys.doSomething()
    print('hello')
'''
import_me/__init__.py
'''

from code import hello

答案 5 :(得分:0)

我知道这是一个已有4年历史的问题,但是我发现自己最近不得不处理同样的问题,并提出了一个似乎可行的解决方案。

这是我们将要使用的文件结构:

package/
    __init__.py
    import_me.py
    import_me_no_re_export.py

package/import_me.py的内容(仅用于比较)根据问题:

import re
import sys

def hello():
    pass

package/import_me_no_re_export.py的那些是:

def _():
    global hello

    import re
    import sys

    def hello():
        pass

_()
del _

以下python CLI会话(Python 3.8.5)显示了此方法的结果:

>>> import package.import_me
>>> import package.import_me_no_re_export
>>> dir(package.import_me)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'hello', 're', 'sys']
>>> dir(package.import_me_no_re_export)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'hello']
>>> set(dir(package.import_me)) - set(dir(package.import_me_no_re_export))
{'sys', 're'}

如您所见,import_me_no_re_export模块不会重新导出sysre模块。

优点:

  • 它看起来像是一种魅力,没有任何副作用。
  • mypyflake8似乎都喜欢这种方法。

缺点:

  • 至少Visual Studio Code似乎不太喜欢它(IntelliSense根本无法工作,并且仅建议已删除的_函数用于如此更改的模块)。
  • 您需要牺牲缩进级别。
  • 样板

正如其他答案所指出的那样,拥抱__all__可能是最好的选择,尽管实际上并不能解决问题。除此之外,可能处理这些事情一定会发生的事实。

答案 6 :(得分:-2)

编辑:在大多数情况下,这不起作用。请参阅其他答案。

我知道这个问题很老,但如果你只是把

select * from (select rownum as rn, t.* from test_table t) where rn > 3

那么您不能从import re import sys def hello(): pass del re del sys 导入resys,这样您就不必从一开始就移动您的导入文件。