简体   繁体   English

Python 导入模块与 __init__.py 中的函数共享名称

[英]Python import module sharing name with a function in __init__.py

My tree looks like我的树看起来像

parent/
|--__init__.py
\--a.py

And the content of __init__.py is__init__.py的内容是

import parent.a as _a
a = 'some string'

When I open up a Python at the top level and import parent.a , I would get the string instead of module.当我在顶层打开 Python 并import parent.a ,我会得到字符串而不是模块。 For example import parent.a as the_a; type(the_a) == str例如import parent.a as the_a; type(the_a) == str import parent.a as the_a; type(the_a) == str . import parent.a as the_a; type(the_a) == str

So I think OK probably import is importing the name from the parent namespace, and it's now overridden.所以我认为 OK 可能import是从parent命名空间导入名称,现在它被覆盖了。 So I figure I can go import parent._a as a_module .所以我想我可以import parent._a as a_module But this doesn't work as there is "No module named _a".但这不起作用,因为“没有名为 _a 的模块”。

This is very confusing.这是非常令人困惑的。 A function can override a module with the same name, but a module cannot take on a new name and "reexport".函数可以覆盖具有相同名称的模块,但模块不能采用新名称和“重新导出”。

Is there any explanation I'm not aware of?有什么我不知道的解释吗? Or is this documented feature?或者这是记录在案的功能?

Even more confusing, if I remove the import statement in __init__.py , everything is back normal again ( import parent.a; type(parent.a) is module ).更令人困惑的是,如果我删除__init__.py中的import语句,一切import parent.a; type(parent.a) is module恢复正常( import parent.a; type(parent.a) is module )。 But why is this different?但为什么这不同呢? The a name in parent namespace is still a string. parent命名空间中的a名称仍然是一个字符串。

(I ran on Python 3.5.3 and 2.7.13 with the same results) (我在 Python 3.5.3 和 2.7.13 上运行,结果相同)

In an import statement, the module reference never uses attribute lookups.import语句中,模块引用从不使用属性查找。 The statements声明

import parent.a  # as ...

and

from parent.a import ...  # as ...

will always look for parent.a in the sys.modules namespace before trying to further initiate module loading from disk.在尝试从磁盘进一步启动模块加载之前,将始终在sys.modules命名空间中查找parent.a

However, for from ... import name statements, Python does look at attributes of the resolved module to find name , before looking for submodules.但是,对于from ... import name语句,Python 在查找子模块之前确实会查看解析模块的属性以查找name

Module globals and the attributes on a module object are the same thing.模块全局变量和模块对象上的属性是一回事。 On import, Python adds submodules as attributes (so globals) to the parent module, but you are free to overwrite those attributes, as you did in your code.在导入时,Python 将子模块作为属性(全局变量)添加到父模块,但您可以像在代码中一样自由地覆盖这些属性。 However, when you then use an import with the parent.a module path, attributes do not come into play.但是,当您使用带有parent.a模块路径的导入时,属性不会起作用。

From the Submodules section of the Python import system reference documentation :Python 导入系统参考文档子模块部分

When a submodule is loaded using any mechanism [...] a binding is placed in the parent module's namespace to the submodule object.当使用任何机制加载子模块时 [...] 将绑定放置在父模块的命名空间中到子模块对象。 For example, if package spam has a submodule foo , after importing spam.foo , spam will have an attribute foo which is bound to the submodule.例如,如果包spam有一个子模块foo ,在导入spam.foospam将有一个属性foo绑定到子模块。

Your import parent.a as _a statement adds two names to the parent namespace;您的import parent.a as _a语句将两个名称添加到parent名称空间; first a is added pointing to the parent.a submodule, and then _a is also set, pointing to the same object.第一a被加到指向parent.a子模块,然后_a也被设定,指向同一个对象。

Your next line replaces the name a with a binding to the 'some string' object.您的下一行将名称a替换为'some string'对象的绑定。

The Searching section of the same details how Python goes about finding a module when you import:相同搜索部分详细介绍了 Python 在导入时如何查找模块:

To begin the search, Python needs the fully qualified name of the module [...] being imported.要开始搜索,Python 需要导入的模块 [...] 的完全限定名称。

[...] [...]

This name will be used in various phases of the import search, and it may be the dotted path to a submodule, eg foo.bar.baz .此名称将用于导入搜索的各个阶段,它可能是子模块的虚线路径,例如foo.bar.baz In this case, Python first tries to import foo , then foo.bar , and finally foo.bar.baz .在这种情况下,Python 首先尝试导入foo ,然后是foo.bar ,最后是foo.bar.baz If any of the intermediate imports fail, a ModuleNotFoundError is raised.如果任何中间导入失败, ModuleNotFoundError引发ModuleNotFoundError

then further on然后进一步

The first place checked during import search is sys.modules .在导入搜索期间检查的第一个位置是sys.modules This mapping serves as a cache of all modules that have been previously imported, including the intermediate paths.此映射用作之前导入的所有模块的缓存,包括中间路径。 So if foo.bar.baz was previously imported, sys.modules will contain entries for foo , foo.bar , and foo.bar.baz .因此,如果之前导入了foo.bar.baz ,则sys.modules将包含foofoo.barfoo.bar.baz条目。 Each key will have as its value the corresponding module object.每个键都有相应的模块对象作为其值。

During import, the module name is looked up in sys.modules and if present, the associated value is the module satisfying the import, and the process completes.在导入期间,在sys.modules查找模块名称,如果存在,则关联的值是满足导入的模块,过程完成。 [...] If the module name is missing, Python will continue searching for the module. [...] 如果缺少模块名称,Python 将继续搜索该模块。

So when trying to import parent.a all that matters is that sys.modules['parent.a'] exists.因此,当尝试导入parent.a ,重要的是sys.modules['parent.a']存在。 sys.modules['parent'].a is not consulted. sys.modules['parent'].a没有被咨询。

Only from module import ... would ever look at attributes.只有from module import ...才会查看属性。 From the import statement documentation :import语句文档

The from form uses a slightly more complex process: from表单使用了一个稍微复杂的过程:

  1. find the module specified in the from clause, loading and initializing it if necessary;找到 from 子句中指定的模块,必要时加载和初始化它;
  2. for each of the identifiers specified in the import clauses:对于 import 子句中指定的每个标识符:
    1. check if the imported module has an attribute by that name检查导入的模块是否具有该名称的属性
    2. if not, attempt to import a submodule with that name and then check the imported module again for that attribute如果没有,请尝试导入具有该名称的子模块,然后再次检查导入的模块的该属性
    3. [...] [...]

So from parent import _a would work, as would from parent import a , and you'd get the parent.a submodule and the 'some string' object, respectively.所以from parent import _a会起作用,就像from parent import a ,你会分别得到parent.a子模块和'some string'对象。

Note that sys.modules is writable, if you must have import parent._a work, you can always just alter sys.modules directly:请注意sys.modules是可写的,如果你必须import parent._a工作,你总是可以直接改变sys.modules

sys.modules['parent._a'] = sys.modules['parent.a']  # make parent._a an alias for parent.a
import parent._a  # works now

I think I have a coherent understanding of this problem now, just documenting my findings in case others run into this.我想我现在对这个问题有一个连贯的理解,只是记录我的发现以防其他人遇到这个问题。

What Martijn said above is mostly true, expanding on that answer, import parent.a as _a is a two step process. Martijn 上面所说的大部分是正确的,扩展了那个答案, import parent.a as _a是一个两步过程。 The first step is module lookup of parent.a , which never goes through attribute lookup, and then it does a binding onto sys.modules , and then an attribute binding of the module to attribute a in parent .第一步是parent.a模块查找,它从不通过属性查找,然后它绑定到sys.modules ,然后将模块的属性绑定到parent属性a In fact this is all you get if you only use import parent.a .事实上,如果你只使用import parent.a这就是你所得到的。 This part is described thoroughly by the previous answer.这部分在前面的回答中有详细的描述。

The second part as _a does an attribute lookup of parent.a , and binds it onto the name _a . as _a的第二部分执行parent.a属性查找,并将其绑定到名称_a So to answer my original question, now if I go outside and start an interactive Python interpreter, now parent.a has been overwritten to the string in __init__.py , and import parent.a as the_a; the_a所以为了回答我最初的问题,现在如果我出去启动一个交互式 Python 解释器,现在parent.a已被覆盖为__init__.py的字符串,并将import parent.a as the_a; the_a import parent.a as the_a; the_a would get me the string. import parent.a as the_a; the_a会给我字符串。 In fact, this is the same as import parent.a; parent.a其实这和import parent.a; parent.a是一样的import parent.a; parent.a import parent.a; parent.a . import parent.a; parent.a Both the_a and parent.a are the results of attribute lookup. the_aparent.a都是属性查找的结果。 I could still get the submodule by parent._a or sys.modules["parent.a"] .我仍然可以通过parent._asys.modules["parent.a"]获取子模块。

To answer my follow up question:回答我的后续问题:

Even more confusing, if I remove the import statement in __init__.py , everything is back normal again ( import parent.a; type(parent.a) is module).更令人困惑的是,如果我删除__init__.py中的import语句,一切import parent.a; type(parent.a)恢复正常( import parent.a; type(parent.a) is module )。 But why is this different?但为什么这不同呢? The a name in parent namespace is still a string. parent命名空间中的a名称仍然是一个字符串。

This is when I import parent.a in the outside interactive Python interpreter, it first evaluates __init__.py , which does the overwriting of parent.a to a string.这是当我在外部交互式 Python 解释器中import parent.a时,它首先评估__init__.py ,它将parent.a覆盖为字符串。 But the import hasn't finished yet, it goes on importing the submodule parent.a , and since we are still in the importing part, we don't do attribute lookups, and so we find the correct submodule.但是导入还没有完成,它继续导入子模块parent.a ,由于我们还在导入部分,我们不做属性查找,所以我们找到了正确的子模块。 When all this is done, it binds the submodule to a of parent , thus overwriting the string that was overwriting the submodule, and making it all correct again.当这一切都完成后,它结合了子模块aparent ,从而覆盖这是覆盖辅助模块中的字符串,会再次转变为正确的。

This sounds very confusing, but remember ( https://docs.python.org/3/reference/import.html#submodules ):这听起来很混乱,但请记住( https://docs.python.org/3/reference/import.html#submodules ):

When a submodule is loaded using any mechanism (eg importlib APIs, the import or import-from statements, or built-in __import__() ) a binding is placed in the parent module's namespace to the submodule object.当使用任何机制(例如 importlib API、import 或 import-from 语句或内置__import__()__import__()子模块时,将在父模块的命名空间中放置一个绑定到子模块对象。 For example, if package spam has a submodule foo, after importing spam.foo, spam will have an attribute foo which is bound to the submodule.例如,如果包 spam 有一个子模块 foo,那么在导入 spam.foo 后,spam 将有一个属性 foo 绑定到子模块。 Let's say you have the following directory structure:假设您有以下目录结构:

An import parent.a first runs all the module set-up code, and then binds the name.一个import parent.a首先运行所有模块设置代码,然后绑定名称。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM