本文介绍了在 __init__.py 和 `import as` 语句中导入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了在 __init__.py 中导入并在包的模块中使用 import as 和绝对导入的问题.

I've run into a problem with having imports in __init__.py and using import as with absolute imports in modules of the package.

我的项目有一个子包,其中的 __init__.py 我使用 from import as 语句将其中一个类从模块提升"到子包级别.该模块使用绝对导入从该子包中导入其他模块.我收到此错误 AttributeError: 'module' object has no attribute 'subpkg'.

My project has a subpackage and in it's __init__.py I "lift" one of the classes from a module to the subpackage level with from import as statement. The module imports other modules from that subpackage with absolute imports. I get this error AttributeError: 'module' object has no attribute 'subpkg'.

结构:

pkg/
├── __init__.py
├── subpkg
│   ├── __init__.py
│   ├── one.py
│   └── two_longname.py
└── tst.py

pkg/__init__.py 为空.

pkg/subpkg/__init__.py:

from pkg.subpkg.one import One

pkg/subpkg/one.py:

import pkg.subpkg.two_longname as two

class One(two.Two):
    pass

pkg/subpkg/two_longname.py:

class Two:
    pass

pkg/tst.py:

from pkg.subpkg import One

print(One)

输出:

$ python3.4 -m pkg.tst
Traceback (most recent call last):
  File "/usr/lib/python3.4/runpy.py", line 170, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.4/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/and/dev/test/python/imptest2/pkg/tst.py", line 1, in <module>
    from pkg.subpkg import One
  File "/home/and/dev/test/python/imptest2/pkg/subpkg/__init__.py", line 1, in <module>
    from pkg.subpkg.one import One
  File "/home/and/dev/test/python/imptest2/pkg/subpkg/one.py", line 1, in <module>
    import pkg.subpkg.two_longname as two
AttributeError: 'module' object has no attribute 'subpkg'

解决方法

有一些改变使它起作用:

Workarounds

There are changes that make it work:

  1. 清空 pkg/subpkg/__init__.py 并直接从 pkg.subpkg.one 导入.

我不认为这是一种选择,因为 AFAIK 将事物提升"到包级别是可以的.以下引用自一篇文章:

I don't consider this as an option because AFAIK "lifting" things to the package level is ok. Here is quote from an article:

在您的 __init__.py 中要做的一件常见事情是导入选定的类、函数等进入包级别,以便它们可以方便地从包中导入.

  • one.py中的import as改为from import:

    from pkg.subpkg import two_longname
    
    class One(two_longname.Two):
        pass
    

    这里唯一的缺点是我无法为模块创建短别名.我从@begueradj 的回答中得到了这个想法.

    The only con here is that I can't create a short alias for module. I got that idea from @begueradj's answer.

    也可以在 one.py 中使用相对导入来解决问题.但我认为这只是解决方法 #2 的一种变体.

    It is also possible to use a relative import in one.py to fix the problem. But I think it's just a variation of workaround #2.

    1. 有人能解释一下这里到底发生了什么吗?为什么 __init__.py 中的导入和 import as 的使用组合会导致这些问题?

    1. Can someone explain what is actually going on here? Why a combination of imports in __init__.py and usage of import as leads to such problems?

    有没有更好的解决方法?

    Are there any better workarounds?

    原始示例

    这是我的原始示例.这不是很现实,但我不会删除它,所以@begueradj 的回答仍然有意义.


    Original example

    This is my original example. It's not very realistic but I'm not deleting it so @begueradj's answer still makes sense.

    pkg/__init__.py 为空.

    pkg/subpkg/__init__.py:

    from pkg.subpkg.one import ONE
    

    pkg/subpkg/one.py:

    import pkg.subpkg.two
    ONE = pkg.subpkg.two.TWO
    

    pkg/subpkg/two.py:

    TWO = 2
    

    pkg/tst.py:

    from pkg.subpkg import ONE
    

    输出:

    $ python3.4 -m pkg.tst
    Traceback (most recent call last):
      File "/usr/lib/python3.4/runpy.py", line 170, in _run_module_as_main
        "__main__", mod_spec)
      File "/usr/lib/python3.4/runpy.py", line 85, in _run_code
        exec(code, run_globals)
      File "/home/and/dev/test/python/imptest/pkg/tst.py", line 1, in <module>
        from pkg.subpkg import ONE
      File "/home/and/dev/test/python/imptest/pkg/subpkg/__init__.py", line 2, in <module>
        from pkg.subpkg.one import ONE
      File "/home/and/dev/test/python/imptest/pkg/subpkg/one.py", line 6, in <module>
        ONE = pkg.subpkg.two.TWO
    AttributeError: 'module' object has no attribute 'subpkg'
    

    最初我在 one.py 中有这个:

    Initially I had this in one.py:

    import pkg.subpkg.two as two
    ONE = two.TWO
    

    在这种情况下,我在导入时出错(就像在我的原始项目中使用 import as 一样).

    In that case I get error on import (just like in my original project which uses import as too).

    推荐答案

    你错误地假设一个别名不能使用 from ... import ,如 from ... import ... as 自 Python 2.0 以来一直存在.import ... as 是一种晦涩的语法,很少有人知道,但您在代码中偶然使用了它.

    You incorrectly assume that one cannot have an alias with from ... import, as from ... import ... as has been there since Python 2.0. The import ... as is the obscure syntax that not many know about, but which you use by accident in your code.

    PEP 0221 声称以下两个是有效"的一样:

    PEP 0221 claims that the following 2 are "effectively" the same:

    1. 将 foo.bar.bazaar 导入为 baz
    2. from foo.bar import bazaar as baz

    该声明在 Python 版本直到(包括 3.6.x) 中并不完全正确,正如您遇到的极端情况所证明的那样,即如果 sys.modules 但尚未初始化.import ... as 要求模块 foo.bar 作为属性 bar 注入到 foo 命名空间中,除了在 sys.modules 中,而 from ... import ... as foo.bar 中查找 >sys.modules.

    The statement is not quite true in Python versions up to and including 3.6.x as evidenced by the corner case you met, namely if the required modules already exist in sys.modules but are yet uninitialized. The import ... as requires that the module foo.bar is injected in foo namespace as the attribute bar, in addition to being in sys.modules, whereas the from ... import ... as looks for foo.bar in sys.modules.

    (还要注意 import foo.bar 仅确保模块 foo.barsys.modules 中并且可以作为 foo.bar,但可能尚未完全初始化.)

    (Do note also that import foo.bar only ensures that the module foo.bar is in sys.modules and accessible as foo.bar, but might not be fully initialized yet.)

    如下更改代码对我有用:

    Changing the code as follows did the trick for me:

    # import pkg.subpkg.two_longname as two
    from pkg.subpkg import two_longname as two
    

    并且代码在 Python 2 和 Python 3 上都能完美运行.

    And code runs perfectly on both Python 2 and Python 3.

    此外,在 one.py 中,出于同样的原因,您不能执行 from pkg import subpkg.

    Also, in one.py you cannot do from pkg import subpkg, for the same reason.

    为了进一步演示这个错误,修复你的 one.py 如上所述,并在 tst.py 中添加以下代码:

    To demonstrate this bug further, fix your one.py as above, and add the following code in tst.py:

    import pkg
    import pkg.subpkg.two_longname as two
    
    del pkg.subpkg
    
    from pkg.subpkg import two_longname as two
    import pkg.subpkg.two_longname as two
    

    只有最后一行崩溃,因为 from ... import 查询 sys.modulespkg.subpkg 并在那里找到它,而 import ... aspkg 查询 sys.modules 并尝试在pkg 模块.因为我们刚刚删除了那个属性,最后一行失败了 AttributeError: 'module' object has no attribute 'subpkg'.

    Only the last line crashes, because from ... import consults the sys.modules for pkg.subpkg and finds it there, whereas import ... as consults sys.modules for pkg and tries to find subpkg as an attribute in the pkg module. As we just had deleted that attribute, the last line fails with AttributeError: 'module' object has no attribute 'subpkg'.

    由于 import foo.bar as baz 语法有点晦涩并且增加了更多的极端情况,而且我很少看到它被使用,我建议完全避免它并支持 from .. import ... as.

    As the import foo.bar as baz syntax is a bit obscure and adds more corner cases, and I have rarely if ever seen it being used, I would recommend avoiding it completely and favouring from .. import ... as.

    这篇关于在 __init__.py 和 `import as` 语句中导入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

  • 08-22 13:06