导入工具 VS 从工具导入安全

import werkzeug VS from werkzeug import security

我目前对 Python 中的导入如何工作的理解(基于这些答案:one, two, ; and Python documentation)是 (以防万一:所有代码片段都经过测试Python 3.6.1):

假设我们有一个模块 mod,它有子模块 subsub1sub,反过来,有一个函数func;那么我们可以(当然,假设 mod 安装在当前环境中):

import mod

mod.sub.func()
mod.sub1

# or
import mod.sub

mod.sub.func()
mod.sub1 # will result in "NameError: name 'mod' is not defined"

# or
from mod.sub import func

func()
mod.sub.func() # will result in "NameError: name 'mod' is not defined"
mod.sub1 # will result in "NameError: name 'mod' is not defined"

最近,在玩 werkzeug.security.generate_password_hashwerkzeug.security.check_password_hash 时,在 Python 控制台中,我注意到:

import werkzeug

werkzeug.security.generate_password_hash('some_password', method='pbkdf2:sha512', salt_length=25)

结果 AttributeError: module 'werkzeug' has no attribute 'security'

尽管如此,以下工作正常:

from werkzeug import security

security.generate_password_hash('some_password', method='pbkdf2:sha512', salt_length=25)

这个(当然)也是:

import werkzeug.security

werkzeug.security.generate_password_hash('some_password', method='pbkdf2:sha512', salt_length=25)

还有这个:

from werkzeug.security import generate_password_hash

generate_password_hash('some_password', method='pbkdf2:sha512', salt_length=25)

而且,有点令人惊讶(至少对我而言),这个:

import werkzeug
from werkzeug import security

werkzeug.security.generate_password_hash('some_password', method='pbkdf2:sha512', salt_length=25)

我的问题是:

  1. 关于 import 如何在 Python 中工作,我的一些想法是否错误(或缺乏细节)?
  2. 为什么 import werkzeug 不允许我访问 werkzeug.security?我的理解是 — 它应该导入 werkzeug,连同所有 submodules/attributes.
  3. 为什么 import werkzeug + from werkzeug import security 允许访问 werkzeug.security?我的理解:它应该绑定两个单独的名称(它们之间没有连接),如下:werkzeugimport werkzeug(即werkzeug模块) securityfrom werkzeug import security(即 werkzeug 模块的 security 子模块。

我不确定我是否能够很好地回答你的所有问题,但我发现它很有趣并看了一下,这是我的结果。

通常,import mod.subfrom mod import sub 假定 submod 包中的子模块。但是,这也可能意味着 sub 是在 mod 模块中声明的 field/variable。

存在__init.py__-文件will denote一个文件夹是一个包:

The __init__.py files are required to make Python treat the directories as containing packages; this is done to prevent directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module search path. In the simplest case, __init__.py can just be an empty file, but it can also execute initialization code for the package (...).

我相信,from werkzeug import securityimport werkzeug.security 都导入了一个模块 security,因此 security.generate_password_hash 是一个已知且有效的属性。基本上,from werkzeug.security import generate_password_hash 通过有效的导入语句直接导入那个属性。

Werkzeug Quickstart 文档中,我发现了以下内容:

Make sure to import all objects from the places the documentation suggests. It is theoretically possible in some situations to import objects from different locations but this is not supported.

此外,Werkzeug transition to 1.0 指出:

Werkzeug originally had a magical import system hook that enabled everything to be imported from one module and still loading the actual implementations lazily as necessary. Unfortunately this turned out to be slow and also unreliable on alternative Python implementations and Google’s App Engine.

Starting with 0.7 we recommend against the short imports and strongly encourage starting importing from the actual implementation module. Werkzeug 1.0 will disable the magical import hook completely.

看来 Werkzeug modifies 模块是如何加载的。 (我推测这在具有 contrib-content 的大包​​中并不少见,例如 Flask、Django;受延迟加载、提高性能或管理分布在包中的贡献模块内容的能力的推动。)

如您所见,import werkzeug 不会werkzeug 模块导入 security,因为(据我了解),将作为属性导入的 子模块是在 __init__.py:

line 100 上定义的子模块
# modules that should be imported when accessed as attributes of werkzeug
attribute_modules = frozenset(['exceptions', 'routing'])

在同一文件中,当 looking at Werkzeug 的 module(ModuleType)-class 及其 __getattr__()-方法时:

class module(ModuleType):

    """Automatically import objects from the modules."""

    def __getattr__(self, name):
        if name in object_origins:
            module = __import__(object_origins[name], None, None, [name])
            for extra_name in all_by_module[module.__name__]:
                setattr(self, extra_name, getattr(module, extra_name))
            return getattr(module, name)
        elif name in attribute_modules:
            __import__('werkzeug.' + name)
        return ModuleType.__getattribute__(self, name)

看来object_origins字典中的模块名称,根据all_by_module中的定义,必须单独导入,而werkzeug.securityone of them

最后,我认为原因是:

import werkzeug     
from werkzeug import security  

组合有效,第一行 导入安全,但第二行 执行 ,并且 __getattr__()-方法将 return 显式导入的模块。

编辑: 最后一节不正确,由 Filipp 测试:

我希望只做 from werkzeug import security 仍然 werkzeug.security.generate_password_hash() 可以工作。 (我没有测试或证实这一点)

TL;DR:直接从werkzeug导入all_by_module dictionary中包含的任何属性,即from werkzeug import generate_password_hash.


受到/基于的启发,我会尝试总结一下我自己的问题的答案:

  1. Am I wrong (or lacking details) in some of my notions, concerning how import works in Python?

从我目前的立场来看,简短的回答是否定的。不过,最好记住 import rules/mechanics 可以通过 __init__.py 在包级别自定义。
关于主题的进一步阅读:Python import system, official docs on importlib, Importing Python Modules 文章。

  1. Why import werkzeug won't give me access to werkzeug.security? My understanding is — it should import werkzeug, along with all of it's submodules/attributes.

As Thomas Fauskanger, correctly pointed out in his : import werkzeug does not import security from the werkzeug module, because the only submodules that will be imported as attributes — are those defined on line 100 of the Werkzeug's __init__.py(即 exceptionsrouting)。这个假设可以通过以下方式验证:

import werkzeug

werkzeug.routing # will return path to routing.py module
werkzeug.exceptions # will return path to exceptions.py module

werkzeug.security # AttributeError: module 'werkzeug' has no attribute 'security'
  1. Why import werkzeug + from werkzeug import security allows access to werkzeug.security? My understanding: it should bind two separate names (with no connections between them), as follows: werkzeug to import werkzeug (i.e. werkzeug module) and security to from werkzeug import security (i.e. security submodule of werkzeug module.

这是一个棘手的问题。正如 Werkzeug 的 __init__.py 中所示,docstring for module's __dir__ function:

Just show what we want to show.

这(可能)是原因:

import werkzeug

dir1 = dir(werkzeug)
werkzeug.security # AttributeError: module 'werkzeug' has no attribute 'security'

from werkzeug import security

dir2 = dir(werkzeug)
werkzeug.security # will return path to security.py module
# BUT!
dir1 == dir2 # True

我想,Thomas 也在这里,并且:

...__getattr__() method will return modules that are explicitly imported.


结论(或者我的学习=)

docstring for Werkzeug's __init__.py 中所述:

...
The majority of the functions and classes provided by Werkzeug work on the HTTP and WSGI layer. There is no useful grouping for those which is why they are all importable from "werkzeug" instead of the modules where they are implemented.
...
The implementation of a lazy-loading module in this file replaces the werkzeug package when imported from within. Attribute access to the werkzeug module will then lazily import from the modules that implement the objects.

这意味着,而不是:

from werkzeug import security
security.generate_password_hash('some_password', method='pbkdf2:sha512', salt_length=25)
# OR
import werkzeug.security
werkzeug.security.generate_password_hash('some_password', method='pbkdf2:sha512', salt_length=25)
# OR
from werkzeug.security import generate_password_hash
generate_password_hash('some_password', method='pbkdf2:sha512', salt_length=25)
# OR
import werkzeug
from werkzeug import security
werkzeug.security.generate_password_hash('some_password', method='pbkdf2:sha512', salt_length=25)

你可以简单地做:

from werkzeug import generate_password_hash
generate_password_hash('some_password', method='pbkdf2:sha512', salt_length=25)

您可以以相同的方式导入和使用 all_by_module dictionary 中包含的任何属性。

如果您遵循此语法,它将工作正常。

import werkzeug as xyz

有了它,您还可以执行 xyz.generate_hash_password() 或调用 xyz 的任何其他功能。