在 python 3.5.x 中使用 `urllib` 意外加载模块

Unexpected module loading using `urllib` in python 3.5.x

在某些系统上使用 urllib 加载幻像模块时似乎出现了一些意外行为。行为如下:

Python 3.5.2 (default, Aug 18 2017, 17:48:00) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import urllib
>>> dir(urllib)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__']
>>> urllib.foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'urllib' has no attribute 'foo'
>>> dir(urllib)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'error', 'parse', 'request', 'response']

意外的行为是仅在尝试访问错误属性并获得 AttributeError 后加载其他名称。在另一个具有相同 python 解释器 (Ubuntu 16.04 apt python3) 的系统上,这不会发生:

Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
>>> import urllib
>>> dir(urllib)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__']
>>> urllib.foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'urllib' has no attribute 'foo'
>>> dir(urllib)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__']

我们已经在多个操作系统和解释器上进行了测试,唯一表现出意外行为的是从 apt 获得的 Ubuntu 14.04 Python 3.4.0。在这个系统上,我们还验证了它似乎在引发完全不相关的异常时发生...

Python 3.4.0 (default, Apr 11 2014, 13:05:18) 
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> raise Exception('uh oh')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Exception: uh oh
>>> import urllib
>>> dir(urllib)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'error', 'parse', 'request', 'response']

这是怎么回事?

您安装了 Apport,Ubuntu 用来捕获软件崩溃的软件包。

该软件包包括一个 Python 安装 sys.excepthook function 的软件包;每当在 Python 程序中引发 unhandled 异常时,都会调用此挂钩。该挂钩的实现间接加载 urllib.* 个模块。

通过在交互式解释器中触发异常,您触发了挂钩,导致额外的 Python 代码添加到 运行,从而添加了导入。

查看导入 urllib 模块的 apport_python_hook.py source code; when the hook is called, various apport modules are loaded, including apport.report

您可以通过在 /etc/default/apport 中设置 enabled = 0 来禁用挂钩。

将来,如果你想看到导入发生,你可以 运行 Python 和 -v command-line switch or setting the PYTHONVERBOSE environment variable;那会告诉您,当您打开 Python 时加载了 apport_python_hook 模块,并且在引发异常时加载了更多模块。