关于python:第十亿次相对导入

Relative imports for the billionth time

我来过这里:

  • http://www.python.org/dev/peps/pep-0328/
  • http://docs.python.org/2/tutorial/modules.html包
  • python包:相对导入
  • python相对导入示例代码不起作用
  • 相对python导入的最终答案
  • python中的相对导入
  • python:禁用相对导入

还有很多我没有复制的网址,一些在上面,一些在其他网站上,当我认为我可以很快得到解决方案的时候。

永远重复出现的问题是:对于Windows7,32位python 2.7.3,如何解决这个"尝试在非包中相对导入"的消息?我在PEP-0328上构建了一个包的精确副本:

1
2
3
4
5
6
7
8
9
10
package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

我确实在相应的模块中创建了名为spam和eggs的函数。当然,它不起作用。答案显然在我列出的第四个网址上,但都是我的校友。我访问的其中一个URL上有此响应:

Relative imports use a module's name attribute to determine that module's position in the package hierarchy. If the module's name does not contain any package information (e.g. it is set to 'main') then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.

上面的回答看起来很有希望,但对我来说都是象形文字。所以我的问题是,如何使python不返回到我的"尝试在非包中进行相对导入"?有一个答案,可能涉及-m。

有人能告诉我为什么python会给出这个错误消息,非包意味着什么吗?,为什么和如何定义一个"包裹",以及准确的答案,用足够简单的术语来表达,让幼儿园学生能够理解。

编辑:从控制台导入。


脚本与模块好的。

这是一个解释。简短的版本是,直接运行一个python文件和从其他地方导入该文件有很大的区别。仅仅知道文件在哪个目录中并不能确定python认为它在哪个包中。另外,这取决于如何将文件加载到Python中(通过运行或导入)。好的。

加载python文件有两种方法:作为顶级脚本,或作为模块。如果直接执行文件,例如通过在命令行上键入python myfile.py,文件将作为顶级脚本加载。如果您执行python -m myfile操作,或者当在其他文件中遇到import语句时,它作为模块加载。一次只能有一个顶级脚本;顶级脚本是您运行以开始工作的python文件。好的。

命名好的。

加载文件时,会为其指定一个名称(存储在其__name__属性中)。如果它是作为顶级脚本加载的,那么它的名称是__main__。如果它是作为模块加载的,那么它的名称是文件名,前面是它所属的任何包/子包的名称,用点分隔。好的。

例如,在您的示例中:好的。

1
2
3
4
5
6
package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
    moduleA.py

如果您导入moduleX(注:导入,不直接执行),其名称为package.subpackage1.moduleX。如果你进口了moduleA,它的名字会是package.moduleA。但是,如果您直接从命令行运行moduleX,它的名称将改为__main__,如果您直接从命令行运行moduleA,它的名称将是__main__。当一个模块作为顶级脚本运行时,它会丢失它的正常名称,而它的名称是__main__。好的。

不通过模块的包含包访问模块好的。

还有一个额外的问题:模块的名称取决于它是从它所在的目录"直接"导入的,还是通过包导入的。如果您在一个目录中运行python,并尝试在同一个目录(或它的子目录)中导入一个文件,那么这只会有所不同。例如,如果启动目录package/subpackage1中的python解释器,然后执行import moduleX,那么moduleX的名称将只是moduleX,而不是package.subpackage1.moduleX。这是因为python在启动时将当前目录添加到其搜索路径中;如果在当前目录中找到要导入的模块,则不会知道该目录是包的一部分,并且包信息也不会成为模块名称的一部分。好的。

一个特殊的情况是,如果您以交互方式运行解释器(例如,只需键入python并开始动态输入python代码)。在这种情况下,交互式会话的名称是__main__。好的。

现在,对于您的错误消息来说,关键是:如果一个模块的名称没有点,那么它就不被认为是包的一部分。文件在磁盘上的实际位置并不重要。重要的是它的名称是什么,它的名称取决于您如何加载它。好的。

现在看看你的问题中包含的报价:好的。

Relative imports use a module's name attribute to determine that module's position in the package hierarchy. If the module's name does not contain any package information (e.g. it is set to 'main') then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.

Ok.

相对导入…好的。

相对导入使用模块的名称来确定它在包中的位置。当您使用相对导入(如from .. import foo)时,点表示在包层次结构中增加了一些级别。例如,如果当前模块的名称是package.subpackage1.moduleX,那么..moduleA表示package.moduleA。为了使from .. import工作,模块名必须至少有import语句中的点。好的。

…仅在包中是相对的好的。

但是,如果您的模块名是__main__,则不认为它在包中。它的名称没有点,因此不能在其中使用from .. import语句。如果您尝试这样做,您将得到"相对导入非包"错误。好的。

脚本无法导入相对好的。

您可能会尝试从命令行运行moduleX或类似命令。当您这样做时,它的名称被设置为__main__,这意味着它内部的相对导入将失败,因为它的名称不会显示它在包中。请注意,如果从模块所在的同一目录运行python,然后尝试导入该模块,也会发生这种情况,因为如上所述,python会在当前目录中"太早"找到该模块,而不会意识到它是包的一部分。好的。

还要记住,当您运行交互式解释器时,该交互式会话的"名称"始终是__main__。因此,不能直接从交互会话进行相对导入。相对导入仅在模块文件中使用。好的。

两种解决方案:好的。

  • 如果您确实想直接运行moduleX,但仍然希望它被视为包的一部分,那么可以执行python -m package.subpackage1.moduleX-m告诉python将其作为模块加载,而不是作为顶级脚本加载。好的。

  • 或者你可能不想运行moduleX,你只想运行一些其他的脚本,比如myfile.py,它使用moduleX中的函数。如果是这样,请将myfile.py放在不在package目录中的其他位置,然后运行它。如果在myfile.py内做from package.moduleA import spam之类的事情,它会很好地工作。好的。

  • 笔记好的。

    • 对于这些解决方案中的任何一个,包目录(示例中的package)都必须可以从python模块搜索路径(sys.path访问。否则,您将无法可靠地使用包中的任何内容。好的。

    • 由于python 2.6,用于包解析的模块的"名称"不仅由其__name__属性决定,而且由__package__属性决定。这就是为什么我避免使用显式符号__name__来引用模块的"名称"。因为python 2.6模块的"名称"实际上是__package__ + '.' + __name__,或者如果__package__None,则仅是__name__。好的。

    好啊。


    这确实是Python中的一个问题。困惑的根源在于人们错误地把相对进口视为路径相对,而不是路径相对。

    例如,在faa.py中编写时:

    1
    from .. import foo

    只有当faa.py在执行期间被python标识和加载为包的一部分时,这才有意义。在这种情况下,模块的名称例如,对于faa.py,可能是一些_PackageName.faa。如果只因为文件在当前目录中而加载了该文件,那么在运行python时,它的名称将不会引用任何包,最终相对导入将失败。

    在当前目录中引用模块的一个简单解决方案是使用:

    1
    2
    3
    4
    5
    6
    if __package__ is None or __package__ == '':
        # uses current directory visibility
        import foo
    else:
        # uses current package visibility
        from . import foo


    下面是一个通用的方法,作为一个例子进行了修改,我现在使用它来处理作为包编写的Python库,这些库包含相互依赖的文件,我希望能够在其中逐段测试其中的某些部分。我们称之为lib.foo,并说它需要访问lib.fileA以获取f1f2的函数,lib.fileB以获取Class3的类。

    我打了几通print电话来帮助说明这是如何工作的。实际上,您希望删除它们(也可能删除from __future__ import print_function行)。

    这个特殊的例子太简单了,无法显示何时真正需要在sys.path中插入一个条目。(当我们有两个或更多级别的包目录,然后我们使用os.path.dirname(os.path.dirname(__file__))时,请参阅lars的答案,但在这里也没有真正的伤害。)在没有if _i in sys.path测试的情况下,这样做也足够安全。但是,如果每个导入的文件都插入相同的路径,例如,如果fileAfileB都想从包中导入实用程序,那么多次将sys.path与相同的路径混在一起,所以最好将if _i not in sys.path放在样板中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    from __future__ import print_function # only when showing how this works

    if __package__:
        print('Package named {!r}; __name__ is {!r}'.format(__package__, __name__))
        from .fileA import f1, f2
        from .fileB import Class3
    else:
        print('Not a package; __name__ is {!r}'.format(__name__))
        # these next steps should be used only with care and if needed
        # (remove the sys.path manipulation for simple cases!)
        import os, sys
        _i = os.path.dirname(os.path.abspath(__file__))
        if _i not in sys.path:
            print('inserting {!r} into sys.path'.format(_i))
            sys.path.insert(0, _i)
        else:
            print('{!r} is already in sys.path'.format(_i))
        del _i # clean up global name space

        from fileA import f1, f2
        from fileB import Class3

    ... all the code as usual ...

    if __name__ == '__main__':
        import doctest, sys
        ret = doctest.testmod()
        sys.exit(0 if ret.failed == 0 else 1)

    这里的想法是这样的(请注意,这些功能在python2.7和python 3.x中都是相同的):

  • 如果作为从普通代码导入的常规包运行import libfrom lib import foo__packagelib__name__lib.foo。我们采用第一个代码路径,从.fileA导入,等等。

  • 如果按python lib/foo.py运行,__package__将为无,__name__将为__main__

    我们采用第二条代码路径。lib目录将已经在sys.path中,因此不需要添加它。我们从江户十一〔11〕等地进口。

  • 如果在lib目录中运行python foo.py,则行为与案例2相同。

  • 如果在lib目录中运行,作为python -m foo目录,则行为与案例2和3类似。但是,到lib目录的路径不在sys.path中,所以我们在导入之前添加它。如果我们运行python,然后运行import foo,这同样适用。

    (由于.sys.path中,我们不需要在这里添加路径的绝对版本。在这里,更深层的包嵌套结构(我们希望在其中执行from ..otherlib.fileC import ...)起到作用。如果你不这样做,你可以完全忽略所有的sys.path操作。)

  • 笔记

    还有一个怪癖。如果你从外面把这整件事处理掉:

    1
    $ python2 lib.foo

    或:

    1
    $ python3 lib.foo

    行为取决于lib/__init__.py的内容。如果它存在并且是空的,那么一切都很好:

    1
    Package named 'lib'; __name__ is '__main__'

    但是如果lib/__init__.py本身进口routine以便直接出口routine.name作为lib.name的话,你会得到:

    1
    2
    3
    $ python2 lib.foo
    Package named 'lib'; __name__ is 'lib.foo'
    Package named 'lib'; __name__ is '__main__'

    也就是说,模块被导入两次,一次通过包,然后再次作为__main__导入,以便运行main代码。python 3.6及更高版本警告:

    1
    2
    3
    4
    5
    6
    7
    $ python3 lib.routine
    Package named 'lib'; __name__ is 'lib.foo'
    [...]/runpy.py:125: RuntimeWarning: 'lib.foo' found in sys.modules
    after import of package 'lib', but prior to execution of 'lib.foo';
    this may result in unpredictable behaviour
      warn(RuntimeWarning(msg))
    Package named 'lib'; __name__ is '__main__'

    警告是新的,但关于行为的警告不是。它是一些人称之为双重导入陷阱的一部分。(更多细节见27487期)尼克·科格伦说:

    This next trap exists in all current versions of Python, including 3.3, and can be summed up in the following general guideline:"Never add a package directory, or any directory inside a package, directly to the Python path".

    请注意,虽然我们在这里违反了这个规则,但是只有当正在加载的文件不是作为包的一部分加载时,我们才这样做,并且我们的修改是专门为允许我们访问该包中的其他文件而设计的。(而且,正如我所指出的,我们可能根本不应该对单个级别的包执行此操作。)如果我们想要更加干净,我们可以将其重写为,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        import os, sys
        _i = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        if _i not in sys.path:
            sys.path.insert(0, _i)
        else:
            _i = None

        from sub.fileA import f1, f2
        from sub.fileB import Class3

        if _i:
            sys.path.remove(_i)
        del _i

    也就是说,我们修改sys.path足够长的时间来实现我们的进口,然后将其恢复原样(删除_i的一个副本,如果并且仅当我们添加_i的一个副本时)。


    因此,在与许多其他人讨论了这一点之后,我看到了Dorian B在本文中发布的一条注释,它解决了我在开发用于Web服务的模块和类时遇到的特定问题,但是我也希望能够在编码时使用pycharm中的调试器工具对它们进行测试。要在独立类中运行测试,我将在类文件的末尾包含以下内容:

    1
    2
    if __name__ == '__main__':
       # run test code here...

    但是,如果我想导入同一文件夹中的其他类或模块,那么我将不得不将所有导入语句从相对符号更改为本地引用(即删除点(.),但是在阅读了Dorian的建议之后,我尝试了他的"一行程序",结果成功了!我现在可以在pycharm中进行测试,当我在另一个正在测试的类中使用该类时,或者当我在Web服务中使用该类时,将测试代码保留在适当的位置!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # import any site-lib modules first, then...
    import sys
    parent_module = sys.modules['.'.join(__name__.split('.')[:-1]) or '__main__']
    if __name__ == '__main__' or parent_module.__name__ == '__main__':
        from codex import Codex # these are in same folder as module under test!
        from dblogger import DbLogger
    else:
        from .codex import Codex
        from .dblogger import DbLogger

    if语句检查我们是否将此模块作为主模块运行,或者它是否正在另一个作为主模块进行测试的模块中使用。也许这是显而易见的,但我在这里提供了这个注释,以防其他任何人对上面的相关进口问题感到沮丧,可以利用它。


    这里是一个解决方案,我将不推荐,但可能是有用的在一些情况下是简单的:在不生成模块

    1
    2
    3
    4
    5
    6
    import os
    import sys
    parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
    sys.path.append(parent_dir_name +"/your_dir")
    import your_script
    your_script.a_function()

    __name__变化取决于是否代码中的问题在全局命名空间或运行的输入模块的一部分。

    如果代码没有运行在全球名称空间,将__name__的模块。如果它正在运行在全球命名空间-例如,如果你的类型,它将运行在控制台,或模块的使用python.exe yourscriptnamehere.py__name__成为当时"__main__"脚本。

    你会看到很多Python代码是用来测试是否与if __name__ == '__main__'被运行的代码是从全局命名空间,这让你有一个模块是双打的脚本。

    你试着从这些进口的控制台?


    我有一个类似的问题Where I didn’t want to change the Python模块搜索所需的路径和负载模块的比较(尽管从脚本"脚本中的所有不相关的进出brenbarn解释说,"nicely以上)。

    我使用下面的破解。不幸的是,它是在imprelies模块对取消的3.4版是从支持importlib下跌。这是可能的(与importlib,太?我不知道。。。。。。。)好,现在的黑客的作品。

    的例子是访问的成员从外moduleXsubpackage1residing subpackage2脚本文件夹中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #!/usr/bin/env python3

    import inspect
    import imp
    import os

    def get_script_dir(follow_symlinks=True):
       """
        Return directory of code defining this very function.
        Should work from a module as well as from a script.
       """

        script_path = inspect.getabsfile(get_script_dir)
        if follow_symlinks:
            script_path = os.path.realpath(script_path)
        return os.path.dirname(script_path)

    # loading the module (hack, relying on deprecated imp-module)
    PARENT_PATH = os.path.dirname(get_script_dir())
    (x_file, x_path, x_desc) = imp.find_module('moduleX', [PARENT_PATH+'/'+'subpackage1'])
    module_x = imp.load_module('subpackage1.moduleX', x_file, x_path, x_desc)

    # importing a function and a value
    function = module_x.my_function
    VALUE = module_x.MY_CONST

    清洁方法似乎被修改用于加载的模块包含在上述的对他。

    1
    2
    3
    4
    5
    6
    7
    8
    #!/usr/bin/env python3

    if __name__ == '__main__' and __package__ is None:
        from os import sys, path
        # __file__ should be defined in this case
        PARENT_DIR = path.dirname(path.dirname(path.abspath(__file__)))
       sys.path.append(PARENT_DIR)
    from subpackage1.moduleX import *


    使python不返回"尝试在非包中进行相对导入"。包装/

    英利子包1号/英利模块化模块化子包2/英利模块化模块化

    只有在将相对导入应用于父文件时才会出现此错误。例如,在modulea.py中对"print(name)"进行编码之后,父文件已经返回了main。因此,该文件已经是main文件了,它不能再返回任何父包。包子包1和子包2的文件中需要相对导入您可以使用".."来引用父目录或模块。但是父目录如果已经是顶级包,就不能再高于父目录(包)。您将相对导入应用于父级的此类文件只能与绝对导入应用程序一起使用。如果在父包中使用绝对导入,那么不会出现任何错误,因为python知道谁在包的顶层,即使您的文件在子包中,因为python path的概念定义了项目的顶层。


    Relative imports use a module's name attribute to determine that module's position in the package hierarchy. If the module's name does not contain any package information (e.g. it is set to 'main') then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.

    写了一个小的python包给pypi,它可以帮助查看这个问题。如果希望能够从包/项目中运行包含包含上层包的导入的python文件,而不直接在导入文件的目录中,那么包将充当解决方案。https://pypi.org/project/import-anywhere网站/