Python: 为什么导入包有时会授予对其下模块的访问权限,有时却不会?

Python: why does importing a package sometimes grant access to modules underneath it but sometimes not?

Python 导入机制对我来说一直是一个神话。有时导入一个包可以授予对其下面模块的访问权限。例如,

import urllib
urllib.parse.unquote

给予

<function urllib.parse.unquote>

这表明即使只导入了包(即本例中的 urllib)也可以访问函数,但不能访问模块文件。这是在 Jupyter Notebook 中完成的。

但是当我在终端中做同样的事情时

>>> import urllib
>>> urllib.parse.unquote
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'urllib' has no attribute 'parse'

两个Python版本都是3.6.1.

有什么不同,好的做法是什么?

编辑以合并@user2357112 和@Tomoki 的答案。

直接来自@user2357112

For an access to urllib.parse to work, the following two conditions must be true:

The urllib module object must be bound to the urllib name, whether in the local, global, or some enclosing scope. The urllib.parse submodule must have been initialized and bound to the parse attribute of the urllib module object. An import urllib in the current local or global scope (or any enclosing scope) satisfies the first condition.

An import urllib.parse executed anywhere in the program satisfies the second condition, since it loads the submodule and binds it to the parse attribute on the urllib module object, and there's only one urllib module object for the whole program.

In the environments where urllib.parse was accessible after a simple import urllib, some other code must have loaded urllib.parse, causing you to see it.

证据由@Tomoki提供

Test: "import IPython"
 └─IPython:┐
      ┌────┘
      ├──"from core.application import Application"
      │   └──IPython.core.application: "from IPython.core import release, crashhandler"
      │      └──IPython.core.crashhandler: "from IPython.core import ultratb"
      │         └──IPython.core.ultratb: "import pydoc"
      │            └──pydoc: "import urllib.parse"
      └──"from terminal.embed import embed"
          └──IPython.terminal.embed:┐
                        ┌───────────┘
                        ├──"from IPython.core import magic_arguments"
                        │   └──IPython.core.magic_arguments: "from IPython.utils.text import dedent"
                        │      └──IPython.utils.text: "from pathlib import Path"
                        │         └──pathlib: "from urllib.parse import quote_from_bytes"
                        ├──"from IPython.core.magic import Magics, magics_class, line_magic"
                        │   └──IPython.core.magic: "from IPython.core import oinspect"
                        │      └──IPython.core.oinspect: "from IPython.core import page"
                        │         └──IPython.core.page: "from IPython.core.display import display"
                        │            └──IPython.core.display: "import mimetypes"
                        │               └──mimetypes: "import urllib.parse"
                        └──"from IPython.terminal.interactiveshell import TerminalInteractiveShell"
                            └──pygments.plugin: "import pkg_resources"
                               └──pkg_resources: "import email.parser"
                                  └──email.parser: "from email.feedparser import FeedParser, BytesFeedParser"
                                     └──email.feedparser: "from email._policybase import compat32"
                                        └──email._policybase: "from email.utils import _has_surrogates"
                                           └──email.utils: "import urllib.parse"

最后一行确实涉及urllib.parse

另一个证据

import scipy 不提供在终端或 Jupyter notebook 中对 scipy.stats.norm 的访问,因为环境的 none 根本触及 scipy.stats

什么是好的做法?

从上面我们可以得出结论,##import 整个模块级别## 不仅是一个好的做法,而且实际上是一个要求。

"Always import down to file (module) level to guarantee the access"

感谢大家的回答!

要访问 urllib.parse,必须满足以下两个条件:

  1. urllib 模块对象必须绑定到 urllib 名称,无论是在本地、全局还是某些封闭范围内。
  2. urllib.parse 子模块必须已经初始化并绑定到 urllib 模块对象的 parse 属性。

当前局部或全局范围(或​​任何封闭范围)中的 import urllib 满足第一个条件。

在程序的任何地方执行的import urllib.parse满足第二个条件,因为它加载子模块并将其绑定到parse属性urllib模块对象,整个程序只有一个urllib模块对象。

在简单 import urllib 后可以访问 urllib.parse 的环境中,一定是加载了一些其他代码 urllib.parse,导致您看到它。

Python 3 不自动加载 urllib 的辅助模块。 ( https://docs.python.org/2/library/urllib.html )

"Note The urllib module has been split into parts and renamed in Python 3 to urllib.request, urllib.parse, and urllib.error. The 2to3 tool will automatically adapt imports when converting your sources to Python 3."

"Note urllib also exposes certain utility functions like splittype, splithost and others parsing URL into various components. But it is recommended to use urlparse for parsing URLs rather than using these functions directly. Python 3 does not expose these helper functions from urllib.parse module."


如果您尝试在导入后查询 urllib 命名空间 dir(urllib),则没有子模块。在您键入 urllib.parse.unquote 并收到错误后,urllib 帮助程序模块已加载。 (我是认真的,这听起来很疯狂而且是错误的,所有事情都不是 Python、"he's a n00b",只是尝试一下。)您可以通过 dir(urllib) 在名称空间中看到它们,并且可以查询使用它们作为如果它们最初都已加载。然后您将获得函数对象return。

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

>>> urllib.parse.unquote

Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: module 'urllib' has no attribute 'parse'

>>> urllib.parse.unquote

<function unquote at 0x7f559e4768c8>


六个模块中有builtins.module(builtins.object)

    Module_six_moves_urllib
_LazyDescr(builtins.object)
    MovedAttribute
    MovedModule
_LazyModule(builtins.module)
    Module_six_moves_urllib_error
    Module_six_moves_urllib_parse
    Module_six_moves_urllib_request
    Module_six_moves_urllib_response
    Module_six_moves_urllib_robotparser

还有其他文档(当然),例如

class Module_six_moves_urllib(builtins.module) " | 创建一个类似于 Python 3 命名空间的 six.moves.urllib 命名空间"

我怀疑终端不会像 Jupyter 那样自动调用内置函数来加载辅助模块,但我真的不知道。

编辑添加:导入 urllib,导入 6 并调用它 [even help("six")] 会将解析、请求、响应、robotparser 模块加载到 urllib 命名空间。此外,导入 urllib 并在其上调用 help 会将 parse 而不是其他模块加载到命名空间中。 Jupyter 可能会主动加载帮助,导致它只加载解析模块。我没有安装 IPython/conda/Jupyter 所以无法帮助测试。

如 user2357112 所说,它正在导入;我相信这些是特定的模块和语句。

Test: "import IPython"
 └─IPython:┐
      ┌────┘
      ├──"from core.application import Application"
      │   └──IPython.core.application: "from IPython.core import release, crashhandler"
      │      └──IPython.core.crashhandler: "from IPython.core import ultratb"
      │         └──IPython.core.ultratb: "import pydoc"
      │            └──pydoc: "import urllib.parse"
      └──"from terminal.embed import embed"
          └──IPython.terminal.embed:┐
                        ┌───────────┘
                        ├──"from IPython.core import magic_arguments"
                        │   └──IPython.core.magic_arguments: "from IPython.utils.text import dedent"
                        │      └──IPython.utils.text: "from pathlib import Path"
                        │         └──pathlib: "from urllib.parse import quote_from_bytes"
                        ├──"from IPython.core.magic import Magics, magics_class, line_magic"
                        │   └──IPython.core.magic: "from IPython.core import oinspect"
                        │      └──IPython.core.oinspect: "from IPython.core import page"
                        │         └──IPython.core.page: "from IPython.core.display import display"
                        │            └──IPython.core.display: "import mimetypes"
                        │               └──mimetypes: "import urllib.parse"
                        └──"from IPython.terminal.interactiveshell import TerminalInteractiveShell"
                            └──pygments.plugin: "import pkg_resources"
                               └──pkg_resources: "import email.parser"
                                  └──email.parser: "from email.feedparser import FeedParser, BytesFeedParser"
                                     └──email.feedparser: "from email._policybase import compat32"
                                        └──email._policybase: "from email.utils import _has_surrogates"
                                           └──email.utils: "import urllib.parse"