首页 文章

相对进口数十亿次

提问于
浏览
338

我来过这里:

还有很多我没有复制的网址,有些在SO上,有些在其他网站上,当我认为我很快就有解决方案时 .

永远反复出现的问题是:使用Windows 7,32位Python 2.7.3,如何解决这个“非包装中尝试相对导入”的消息?我在pep-0328上构建了一个包的精确复制品:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

我确实在适当的模块中创建了名为spam和eggs的函数 . 当然,它没有用 . 答案显然是我列出的第4个网址,但这对我来说都是校友 . 我访问过的其中一个网址上有此回复:

相对导入使用模块的name属性来确定模块在包层次结构中的位置 . 如果模块的名称不包含任何包信息(例如,它设置为'main'),则解析相对导入,就像模块是顶级模块一样,无论模块实际位于文件系统的哪个位置 .

上面的反应看起来很有希望,但它对我来说都是象形文字 . 所以我的问题是,我怎么让Python不回到我身边“试图在非包装中进行相对导入”?有一个答案涉及-m,据说 .

有人可以告诉我为什么Python会给出错误消息,非包装意味着什么!为什么以及如何定义'package'和 the precise answer put in terms easy enough for a kindergartener to understand .

编辑:导入是从控制台完成的 .

8 回答

  • 2

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

    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()
    
  • 1

    相对导入使用模块的name属性来确定模块在包层次结构中的位置 . 如果模块的名称不包含任何包信息(例如,它设置为'main'),则解析相对导入,就像模块是顶级模块一样,无论模块实际位于文件系统的哪个位置 .

    给PyPi写了一个小python包,可能会帮助这个问题的 Spectator . 如果希望能够在包/项目中运行包含包含上层包的导入的python文件而不直接在导入文件的目录中,则该包用作解决方法 . https://pypi.org/project/import-anywhere/

  • 1

    我有一个类似的问题,我不想改变Python模块搜索路径,并需要从脚本相对加载一个模块(尽管"scripts can't import relative with all"正如上面很好地解释了BrenBarn) .

    所以我使用了以下hack . 不幸的是,它依赖于自版本3.4以来被弃用的 imp 模块,而不是 importlib . (这也可以用 importlib 吗?我不知道 . )不过,黑客现在还可以使用 .

    subpackage2 文件夹中的脚本访问 subpackage1 中的 moduleX 成员的示例:

    #!/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
    

    更简洁的方法似乎是修改Federico提到的用于加载模块的sys.path .

    #!/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 *
    
  • 2

    这在python中确实是一个问题 . The origin of confusion is that people mistakenly takes the relative import as path relative which is not.

    例如,当你用faa.py写时:

    from .. import foo
    

    只有在执行期间由python识别并加载faa.y作为包的一部分时,这才有意义 . 在这种情况下,faa.py的 module's name 将是例如some_packagename.faa . 如果文件是因为它在当前目录中而加载的,那么当运行python时,它的名称将不会引用任何包,最终相对导入将失败 .

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

    if __package__ is None or __package__ == '':
    #uses current directory visibility
      import foo
    else:
    #uses current package visibility
      from . import foo
    
  • 0

    这里's a general recipe, modified to fit as an example, that I am using right now for dealing with Python libraries written as packages, that contain interdependent files, where I want to be able to test parts of them piecemeal. Let' s调用 lib.foo 并说它需要访问 lib.fileA 用于函数 f1f2 ,并且 lib.fileB 用于类 Class3 .

    我已经包含了几个 print 调用来帮助说明这是如何工作的 . 在实践中,你会想要删除它们(也可能是 from __future__ import print_function 行) .

    当我们确实需要在 sys.path 中插入一个条目时,这个特殊的例子太简单了 . (有关我们确实需要它的情况,请参阅Lars' answer,当我们有两个或更多级别的包目录时,然后我们使用 os.path.dirname(os.path.dirname(__file__)) - 但它在这里也没有真正受到伤害 . )如果没有 if _i in sys.path 这样做也很安全测试 . 但是,如果每个导入的文件都插入相同的路径 - 例如,如果 fileAfileB 都想从包中导入实用程序 - 这会多次使用相同的路径混淆 sys.path ,那么在样板文件中使用 if _i not in sys.path 会很不错 .

    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 所以没有必要添加它 . 我们从 fileA 等进口

    • 如果在 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 操作 . )

    注意事项

    还有一个怪癖 . 如果你从外面运行这整件事:

    $ python2 lib.foo
    

    要么:

    $ python3 lib.foo
    

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

    Package named 'lib'; __name__ is '__main__'
    

    但是如果 lib/__init__.py 本身导入 routine 以便它可以直接导出 routine.namelib.name ,那么你得到:

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

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

    $ 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__'
    

    警告是新的,但警告的行为不是 . 这是有些人称之为the double import trap的一部分 . (有关其他详细信息,请参阅issue 27487 . )Nick Coghlan说:

    下一个陷阱存在于所有当前版本的Python中,包括3.3,并且可以在以下一般准则中总结:“永远不要将包目录或包内的任何目录直接添加到Python路径” .

    请注意,虽然我们在此处违反了该规则,但只有当正在加载的文件未作为程序包的一部分加载时才会执行此操作,并且我们的修改专门用于允许我们访问该程序包中的其他文件 . (并且,正如我所指出的,我们可能根本不应该对单级包进行此操作 . )如果我们想要更加清洁,我们可能会将其重写为例如:

    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 的一个副本) .

  • 586

    Script vs. Module

    这是一个解释 . 简短的版本是直接运行Python文件和从其他地方导入该文件之间存在很大差异 . Just knowing what directory a file is in does not determine what package Python thinks it is in. 另外,这还取决于如何将文件加载到Python中(通过运行或导入) .

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

    Naming

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

    例如,在您的示例中:

    package/
        __init__.py
        subpackage1/
            __init__.py
            moduleX.py
        moduleA.py
    

    如果导入 moduleX (注意:导入,不直接执行),其名称将为 package.subpackage1.moduleX . 如果导入 moduleA ,则其名称为 package.moduleA . 但是,如果直接从命令行运行 moduleX ,则其名称将改为 __main__ ,如果直接从命令行运行 moduleA ,则其名称将为 __main__ . 当模块作为顶级脚本运行时,它将丢失其正常名称,而其名称将改为 __main__ .

    Accessing a module NOT through its containing package

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

    一个特例是如果您以交互方式运行解释器(例如,只需输入 python 并开始即时输入Python代码) . 在这种情况下,该交互式会话的名称是 __main__ .

    现在,这是您的错误消息的关键所在: if a module's name has no dots, it is not considered to be part of a package . 文件在磁盘上的实际位置并不重要 . 重要的是它的名称,它的名称取决于你如何加载它 .

    现在看看你在问题中包含的引用:

    相对导入使用模块的name属性确定模块在包层次结构中的位置 . 如果模块的名称不包含任何包信息(例如,它设置为'main'),则解析相对导入,就像模块是顶级模块一样,无论模块实际位于文件系统的哪个位置 .

    Relative imports...

    相对导入使用模块的名称来确定它在包中的位置 . 当您使用 from .. import foo 之类的相对导入时,这些点表示在包层次结构中增加一些级别 . 例如,如果您当前模块的名称是 package.subpackage1.moduleX ,则 ..moduleA 将表示 package.moduleA . 要使 from .. import 工作,模块的名称必须至少与 import 语句中的点一样多 .

    ... are only relative in a package

    但是,如果您的模块名称为 __main__ ,则不会将其视为包中 . 它的名字没有点,因此你不能在里面使用 from .. import 语句 . 如果您尝试这样做,您将收到"relative-import in non-package"错误 .

    Scripts can't import relative

    你可能做的是你试图从命令行运行 moduleX 或类似的东西 . 执行此操作时,其名称设置为 __main__ ,这意味着其中的相对导入将失败,因为其名称不会显示它在包中 . 请注意,如果您从模块所在的同一目录运行Python,然后尝试导入该模块,也会发生这种情况,因为如上所述,Python会在当前目录_382689中找到该模块而不会意识到它是一个模块的一部分包 .

    还要记住,当您运行交互式解释器时,该交互式会话的"name"始终为 __main__ . 因此 you cannot do relative imports directly from an interactive session . 相对导入仅用于模块文件中 .

    Two solutions:

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

    • 或许你实际上并不想运行 moduleX ,你只想运行一些其他脚本,例如 myfile.py ,它使用 moduleX 中的函数 . 如果是这种情况,请将 myfile.py 放在其他地方 - 不在 package 目录中 - 并运行它 . 如果在 myfile.py 内你做 from package.moduleA import spam 这样的事情,它会正常工作 .

    Notes

    • 对于这些解决方案中的任何一个,必须可以从Python模块搜索路径( sys.path )访问包目录(在您的示例中为 package ) . 如果不是,您根本无法可靠地使用包装中的任何东西 .

    • 从Python 2.6开始,用于包解析目的的模块"name"不仅取决于 __name__ 属性,还取决于 __package__ 属性 . 's why I' m避免使用显式符号 __name__ 来引用模块's 382712 . Since Python 2.6 a module' s "name"实际上是 __package__ + '.' + __name__ ,如果 __package__None 则只是 __name__ . )

  • -1

    所以在与其他许多人一起解决这个问题之后,我在article中发现了Dorian B发布的一条说明,解决了我在开发用于Web服务的模块和类的具体问题,但我也希望能够使用PyCharm中的调试工具在我编码时测试它们 . 要在自包含类中运行测试,我将在类文件的末尾包含以下内容:

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

    但是如果我想在同一个文件夹中导入其他类或模块,那么我必须将所有的import语句从相对符号更改为本地引用(即删除点( . ))但是在阅读了Dorian的建议之后,我尝试了他的'单线'并且它有效!我现在可以在PyCharm中进行测试,并在我在另一个被测试的类中使用该类时,或者当我在我的Web服务中使用它时,保留我的测试代码!

    # 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语句检查我们是否将此模块作为 main 运行,或者它是否's being used in another module that'被测试为 main . 也许这是显而易见的,但我在这里提供这个注释,以防其他人因上述相对导入问题而感到沮丧,可以利用它 .

  • 11

    __name__ 的更改取决于相关代码是在全局命名空间中运行还是作为导入模块的一部分运行 .

    如果代码未在全局空间中运行,则 __name__ 将是模块的名称 . 如果它在全局命名空间中运行 - 例如,如果您将其键入控制台,或使用 python.exe yourscriptnamehere.py 将该模块作为脚本运行,则 __name__ 将变为 "__main__" .

    您将看到许多带有 if __name__ == '__main__' 的python代码用于测试代码是否从全局命名空间运行 - 这允许您拥有一个兼作脚本的模块 .

    您是否尝试从控制台进行这些导入?

相关问题