Module name binding by relative import in __init__.py

huangapple go评论74阅读模式
英文:

Module name binding by relative import in __init__.py

问题

我编写了一个包,其中包含两个子模块:

pkg/init.py
pkg/foo.py
pkg/bar.py

我将以下代码放在__init__.pybar.py中。

from . import foo as f
foo
print("Hello!")

虽然导入pkg成功,但导入pkg.bar不成功:

>>> import pkg
Hello!

>>> import pkg.bar
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\fumito\Documents\pkg\bar.py", line 2, in <module>
    foo
NameError: name 'foo' is not defined

为什么foopkg命名空间中定义了,但在pkg.bar中没有定义?

英文:

I wrote a package containing two submodules:

pkg/__init__.py
pkg/foo.py
pkg/bar.py

I put the following code in __init__.py and also in bar.py.

from . import foo as f
foo
print(&quot;Hello!&quot;)

While importing pkg suceeds, pkg.bar doesn't:

&gt;&gt;&gt; import pkg
Hello!

&gt;&gt;&gt; import pkg.bar
Traceback (most recent call last):
  File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt;
  File &quot;C:\Users\fumito\Documents\pkg\bar.py&quot;, line 2, in &lt;module&gt;
    foo
NameError: name &#39;foo&#39; is not defined

Why is foo defined in the pkg namespace but not in the pkg.bar?

答案1

得分: 0

通过导入foo模块,它隐式地成为pkg的属性,因为它的完全限定位置是pkg.foo。这使得名称foo__init__.py中可用,因为它变成了pkg/__init__.py的属性。

但显然,pkg.bar.foo是没有意义的,因为那不是foo模块的完全限定名称,所以在bar中导入foo不会在bar内创建一个名为foo的名称。

(这是一个相当模糊的解释,但总结了这里发挥作用的核心机制。)

英文:

By importing the foo module, it implicitly becomes an attribute of pkg, because its fully qualified location is pkg.foo. This makes the name foo available in __init__.py, because it became an attribute of pkg/__init__.py.

But obviously, pkg.bar.foo would make no sense, since that's not the fully qualified name of the foo module, so importing foo in bar does not create a name foo inside bar.

(This is a rather handwavey explanation, but summarises the core mechanism at play here.)

答案2

得分: -3

以下是您要翻译的内容:

EDIT : This answer is incorrect. Please read the explanation in the next section.

这是因为当您尝试在与 pkg 相同的目录中导入模块时,相对导入发生在 pkg 所在的位置。这意味着 bar.py 中的以下语句

from . import foo as f

试图在 pkg 的父目录中查找 foo

您可以选择在 pkg/bar.py 中写入以下导入语句(我个人更喜欢这种方法,因为它不修改路径)

import pkg.foo as f

或者您可以选择在 __init__.py 文件中修改路径变量

import os, sys

script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(script_dir)

然后,在 bar.py 中添加以下导入语句

import foo as f

===============================================================

ATTEMPT 2

因此,deceze 提供了一个非常好的答案,我将尝试进行扩展。

您所做的事情与 Python 文档中的 5.4.2. 子模块 中所述非常相似:

> 当使用任何机制(例如 importlib API、importimport-from 语句或内置的 __import__())加载子模块时,将在父模块的命名空间中放置一个与子模块对象绑定的绑定。例如,如果包 spam 有一个子模块 foo,在导入 spam.foo 后,spam 将有一个属性 foo,该属性绑定到子模块。假设您有以下目录结构:

spam/
    __init__.py
    foo.py

> 并且 spam/__init__.py 中有以下行:

from .foo import Foo

> 那么执行以下操作会将 fooFoo 的名称绑定放入 spam 模块中:

&gt;&gt;&gt;import spam
&gt;&gt;&gt;spam.foo
&lt;module &#39;spam.foo&#39; from &#39;/tmp/imports/spam/foo.py&#39;&gt;
&gt;&gt;&gt;spam.Foo
&lt;class &#39;spam.foo.Foo&#39;&gt;

>根据 Python 的熟悉名称绑定规则,这可能看起来令人惊讶,但实际上这是导入系统的一个基本特性。保持不变的是,如果您有 sys.modules[&#39;spam&#39;]sys.modules[&#39;spam.foo&#39;](如上述导入后所示),则后者必须出现为前者的 foo 属性。

与文档中提到的类似,当您在 __init__.py 中添加语句

# pkg/__init__.py

from . import foo as f
foo
print(&quot;Hello!&quot;)

然后导入 pkg 时,由于导入语句的存在,pkg.foo 存在,这就是为什么不会引发 NameError。因此,导入 pkg 将成功。您可以检查终端中的 pkg.foo 的值,您将看到与以下类似的返回值:

&lt;module &#39;pkg.foo&#39; from &#39;absolute/path/to/pkg/foo.py&#39;&gt;

现在,在 bar.py 中添加以下代码

# pkg/bar.py

from . import foo as f
foo
print(&quot;Hello!&quot;)

并尝试执行

import pkg.bar

会引发 NameError 异常,因为在 bar.py 中不存在 foo 对象;foo 不存在于 bar.py 的命名空间中。

当您在 __init__.py 中使用别名导入 foo 时,还发生了一些有趣的事情。实际上,您可以在仍然可以访问 foo.py 内容的情况下重写引用 pkg.foo。如果您在 __init__.py 文件之后添加以下块

class foo:
    pass

然后检查引用 pkg.foo 的值将返回

&lt;class &#39;pkg.foo&#39;&gt;

但仍然可以通过使用 pkg.f 访问 foo.py 的内容。如果您检查 pkg.f 的值,将看到类似以下内容的内容:

&lt;module &#39;pkg.foo&#39; from &#39;absolute/path/to/pkg/foo.py&#39;&gt;
英文:

EDIT : This answer is incorrect. Please read the explanation in the next section.

It is because when you try to import the modules when you are in the same directory as pkg, the relative import happens from where pkg is. That means that the following statement in bar.py

from . import foo as f

attempts to find foo in the parent directory of pkg.

You could choose to write the following import statement in pkg/bar.py instead (I personally prefer this method because it doesn't modify the path)

import pkg.foo as f

Or you could choose to modify the path variable within your __init__.py file

import os, sys

script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(script_dir)

And then, add the following import statement in bar.py

import foo as f

===============================================================
ATTEMPT 2

So deceze has provided a really nice answer which I will try to expand upon.

What you are doing is quite similar to what is stated in the Python docs in 5.4.2. Submodules:

> When a submodule is loaded using any mechanism (e.g. 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. 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. Let’s say you have the following directory structure:

spam/
    __init__.py
    foo.py

> and spam/__init__.py has the following line in it:

from .foo import Foo

>then executing the following puts name bindings for foo and Foo in the spam module:

&gt;&gt;&gt;import spam
&gt;&gt;&gt;spam.foo
&lt;module &#39;spam.foo&#39; from &#39;/tmp/imports/spam/foo.py&#39;&gt;
&gt;&gt;&gt;spam.Foo
&lt;class &#39;spam.foo.Foo&#39;&gt;

>Given Python’s familiar name binding rules this might seem surprising, but it’s actually a fundamental feature of the import system. The invariant holding is that if you have sys.modules[&#39;spam&#39;] and sys.modules[&#39;spam.foo&#39;] (as you would after the above import), the latter must appear as the foo attribute of the former.

Similar to what is mentioned in the docs, when you add the statement

# pkg/__init__.py

from . import foo as f
foo
print(&quot;Hello!&quot;)

in __init__.py and then import pkg, pkg.foo exists because of the import statement, which is why NameError is not raised. Therefore, importing pkg will be successful. You can check that pkg.foo is referring to foo.py if you check the value of pkg.foo within the terminal - you will see a similar return value to

&lt;module &#39;pkg.foo&#39; from &#39;absolute/path/to/pkg/foo.py&#39;&gt;

Now adding the following code in bar.py

# pkg/bar.py

from . import foo as f
foo
print(&quot;Hello!&quot;)

and attempting to execute

import pkg.bar

does raise the NameError exception because an object foo does not exist within bar.py; foo does not exist within the namespace of bar.py.

There is something interesting that is also happening when you used an alias to import foo within __init__.py. It is actually possible to overwrite the reference pkg.foo while still maintaining access to the contents of foo.py. If you added the following block within your __init__.py file after foo

class foo:
    pass

then checking the reference pkg.foo will return

&lt;class &#39;pkg.foo&#39;&gt;

but contents of foo.py can still be accessed by using pkg.f. If you check the value of pkg.f, you will see someting similar to

&lt;module &#39;pkg.foo&#39; from &#39;absolute/path/to/pkg/foo.py&#39;&gt;

huangapple
  • 本文由 发表于 2023年6月27日 19:23:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76564355.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定