【问题标题】:How to do relative imports in Python?如何在 Python 中进行相对导入?
【发布时间】:2021-10-19 11:15:39
【问题描述】:

想象一下这个目录结构:

app/
   __init__.py
   sub1/
      __init__.py
      mod1.py
   sub2/
      __init__.py
      mod2.py

我正在编码mod1,我需要从mod2 导入一些东西。我该怎么做?

我尝试了from ..sub2 import mod2,但我得到了“尝试在非包中进行相对导入”。

我四处搜索,但只发现“sys.path 操纵”黑客。没有干净的方法吗?


编辑:我所有的__init__.py 目前都是空的

Edit2:我正在尝试这样做,因为 sub2 包含跨子包共享的类(sub1subX 等)。

Edit3:我正在寻找的行为与PEP 366 中描述的行为相同(感谢 John B)

【问题讨论】:

  • 我建议更新您的问题,以更清楚地说明您描述的是 PEP 366 中解决的问题。
  • 这是一个冗长的解释,但请看这里:stackoverflow.com/a/10713254/1267156 我回答了一个非常相似的问题。直到昨晚我都遇到了同样的问题。
  • 对于那些希望加载位于任意路径的模块的人,请参阅:stackoverflow.com/questions/67631/…
  • 在相关说明中,Python 3 将默认情况下将导入的默认处理更改为绝对;必须明确指定相对导入。

标签: python python-import python-module


【解决方案1】:

每个人似乎都想告诉你应该做什么,而不仅仅是回答问题。

问题是您通过将 mod1.py 作为参数传递给解释器来将模块作为“__main__”运行。

来自PEP 328

相对导入使用模块的 __name__ 属性来确定该模块在包层次结构中的位置。如果模块的名称不包含任何包信息(例如,它设置为 '__main__'),则相对导入将被解析为好像模块是顶级模块,而不管模块实际位于文件系统上的什么位置。

在 Python 2.6 中,他们添加了相对于主模块引用模块的功能。 PEP 366 描述了变化。

更新:根据 Nick Coghlan 的说法,推荐的替代方法是使用 -m 开关在包内运行模块。

【讨论】:

  • 这里的答案涉及到在程序的每个入口点都弄乱 sys.path。我想这是唯一的方法。
  • 推荐的替代方法是使用-m 开关在包内运行模块,而不是直接指定它们的文件名。
  • 我不明白:这里的答案在哪里?如何在这样的目录结构中导入模块?
  • @Tom:在这种情况下,mod1 将是from sub2 import mod2。然后,要在应用程序中运行 mod1,请执行 python -m sub1.mod1
  • @XiongChiamiov:这是否意味着如果你的 python 嵌入在应用程序中,你就不能这样做,所以你无法访问 python 命令行开关?
【解决方案2】:

这是适合我的解决方案:

我以from ..sub2 import mod2 进行相对导入 然后,如果我想运行mod1.py,那么我转到app 的父目录并使用python -m 开关作为python -m app.sub1.mod1 运行模块。

相对导入出现此问题的真正原因是相对导入通过获取模块的__name__ 属性来工作。如果模块正在直接运行,则__name__ 设置为__main__,并且它不包含任何有关包结构的信息。而且,这就是 python 抱怨 relative import in non-package 错误的原因。

因此,通过使用 -m 开关,您可以向 python 提供包结构信息,通过它可以成功解析相关导入。

我在做相对导入时多次遇到这个问题。而且,在阅读了所有先前的答案之后,我仍然无法弄清楚如何以一种干净的方式解决它,而无需将样板代码放入所有文件中。 (虽然有些 cmets 真的很有帮助,感谢 @ncoghlan 和 @XiongChiamiov)

希望这可以帮助那些正在与相关进口问题作斗争的人,因为通过 PEP 真的不好玩。

【讨论】:

  • 最佳答案恕我直言:不仅解释了 OP 出现问题的原因,而且还找到了解决问题的方法而不改变他的模块导入方式。毕竟,OP的相对进口很好。罪魁祸首是直接作为脚本运行时无法访问外部包,-m 旨在解决此问题。
  • 另请注意:这个答案是在问题发生 5 年后。这些功能当时不可用。
  • 如果你想从同一个目录导入一个模块,你可以from . import some_module
  • 这是帮助我的答案,它也帮助我将我的想法浓缩为:为了运行包含相对导入的 Python 脚本,我必须将脚本作为模块运行而$ PWD 是它的父目录,如$ python -m app.main。为清楚起见,$ python -m <main_directory>.<script_with_relative_imports>
【解决方案3】:
main.py
setup.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       module_a.py
    package_b/ ->
       __init__.py
       module_b.py
  1. 你运行python main.py
  2. main.py 确实:import app.package_a.module_a
  3. module_a.pyimport app.package_b.module_b

或者 2 或 3 可以使用:from app.package_a import module_a

只要你的 PYTHONPATH 中有 app,它就可以工作。 main.py 可以在任何地方。

所以你写一个setup.py来复制(安装)整个应用程序包和子包到目标系统的python文件夹,main.py到目标系统的脚本文件夹。

【讨论】:

  • 优秀的答案。有没有办法在不安装 PYTHONPATH 的情况下以这种方式导入?
  • 那么,有一天,需要将应用名称更改为test_app。会发生什么?您将需要更改所有源代码,导入 app.package_b.module_b --> test_app.package_b.module_b。这绝对是不好的做法......我们应该尝试在包中使用相对导入。
【解决方案4】:

“Guido 将包中正在运行的脚本视为反模式”(被拒绝 PEP-3122)

我花了很多时间试图找到解决方案,阅读 Stack Overflow 上的相关帖子,并对自己说“一定有更好的方法!”。好像没有。

【讨论】:

  • 注意:已经提到pep-366(与pep-3122同时创建)提供相同的功能,但使用不同的向后兼容实现,即,如果您想在包中运行模块作为脚本在其中使用显式相对导入,然后您可以使用-m 开关运行它:python -m app.sub1.mod1 或从顶级脚本调用app.sub1.mod1.main()(例如,从 setuptools 的 entry_points 生成在 setup.py 中定义)。
  • +1 用于使用 setuptools 和入口点 - 这是设置将从外部运行的脚本的正确方法,在明确定义的位置,而不是无休止地破解 PYTHONPATH
  • 在 peps 上找不到“运行”的定义。对我来说,看起来“运行”并不是最好的定义(对于蚂蚁模式),因为最后“解释”将链接依赖关系,而不是在立即执行的意义上真正“运行”它。 Reference 1reference 2
【解决方案5】:

这个问题100%解决了:

  • 应用/
    • main.py
  • 设置/
    • local_settings.py

在 app/main.py 中导入 settings/local_setting.py:

main.py:

import sys
sys.path.insert(0, "../settings")


try:
    from local_settings import *
except ImportError:
    print('No Import')

【讨论】:

  • 谢谢!所有的人都强迫我以不同的方式运行我的脚本,而不是告诉我如何在脚本中解决它。但我不得不更改代码以使用sys.path.insert(0, "../settings"),然后使用from local_settings import *
【解决方案6】:

nosklo's 答案举例说明

注意:所有__init__.py 文件都是空的。

main.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       fun_a.py
    package_b/ ->
       __init__.py
       fun_b.py

app/package_a/fun_a.py

def print_a():
    print 'This is a function in dir package_a'

app/package_b/fun_b.py

from app.package_a.fun_a import print_a
def print_b():
    print 'This is a function in dir package_b'
    print 'going to call a function in dir package_a'
    print '-'*30
    print_a()

main.py

from app.package_b import fun_b
fun_b.print_b()

如果你运行$ python main.py,它会返回:

This is a function in dir package_b
going to call a function in dir package_a
------------------------------
This is a function in dir package_a
  • main.py 确实:from app.package_b import fun_b
  • fun_b.py 做from app.package_a.fun_a import print_a

package_b 文件夹中的so 文件使用了package_a 文件夹中的文件,这就是你想要的。对吧??

【讨论】:

    【解决方案7】:
    def import_path(fullpath):
        """ 
        Import a file with full path specification. Allows one to
        import from anywhere, something __import__ does not do. 
        """
        path, filename = os.path.split(fullpath)
        filename, ext = os.path.splitext(filename)
        sys.path.append(path)
        module = __import__(filename)
        reload(module) # Might be out of date
        del sys.path[-1]
        return module
    

    我正在使用这个 sn-p 从路径中导入模块,希望对您有所帮助

    【讨论】:

    • 我正在使用这个 sn-p,结合 imp 模块(如这里解释的 [1])效果很好。 [1]:stackoverflow.com/questions/1096216/…
    • 大概,sys.path.append(path)应该换成sys.path.insert(0, path),sys.path[-1]应该换成sys.path[0 ]。否则,如果搜索路径中已经存在同名模块,该函数将导入错误的模块。例如,如果当前目录中有“some.py”,import_path("/imports/some.py") 将导入错误的文件。
    • 我同意!有时其他相对进口会优先。使用 sys.path.insert
    • 如何复制 from x import y (or *) 的行为?
    • 不清楚,请指定完整使用该脚本解决OP问题。
    【解决方案8】:

    不幸的是,这是一个 sys.path hack,但效果很好。

    我在另一个层遇到了这个问题:我已经有一个指定名称的模块,但它是错误的模块。

    我想做的是以下(我正在使用的模块是module3):

    mymodule\
       __init__.py
       mymodule1\
          __init__.py
          mymodule1_1
       mymodule2\
          __init__.py
          mymodule2_1
    
    
    import mymodule.mymodule1.mymodule1_1  
    

    请注意,我已经安装了 mymodule,但在我的安装中我没有“mymodule1”

    我会得到一个 ImportError,因为它试图从我安装的模块中导入。

    我尝试执行 sys.path.append,但没有成功。起作用的是 sys.path.insert

    if __name__ == '__main__':
        sys.path.insert(0, '../..')
    

    有点像黑客,但一切正常! 所以请记住,如果您希望自己的决定覆盖其他路径,那么您需要使用 sys.path.insert(0, pathname) 让它工作!这对我来说是一个非常令人沮丧的症结所在,很多人说要对 sys.path 使用“附加”功能,但如果你已经定义了一个模块,这将不起作用(我觉得这是非常奇怪的行为)

    【讨论】:

    • sys.path.append('../') 适合我(Python 3.5.2)
    • 我认为这很好,因为它将黑客本地化到可执行文件并且不会影响可能依赖于您的包的其他模块。
    【解决方案9】:

    让我把它放在这里供我自己参考。我知道这不是好的 Python 代码,但我需要一个用于我正在处理的项目的脚本,我想将脚本放在 scripts 目录中。

    import os.path
    import sys
    sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
    

    【讨论】:

      【解决方案10】:

      正如@EvgeniSergeev 在对 OP 的 cmets 中所说,您可以从任意位置的 .py 文件中导入代码:

      import imp
      
      foo = imp.load_source('module.name', '/path/to/file.py')
      foo.MyClass()
      

      这取自this SO answer

      【讨论】:

        【解决方案11】:

        看看http://docs.python.org/whatsnew/2.5.html#pep-328-absolute-and-relative-imports。你可以这样做

        from .mod1 import stuff
        

        【讨论】:

        • John B. states 的回答是,除了不能从 'main' 模块进行相对导入
        【解决方案12】:

        来自Python doc

        在 Python 2.5 中,您可以使用 from __future__ import absolute_import 指令将 import 的行为切换为绝对导入。这种绝对导入行为将成为未来版本(可能是 Python 2.7)中的默认行为。一旦绝对导入成为默认值,import string 将始终找到标准库的版本。建议用户尽可能开始使用绝对导入,所以最好在代码中开始写from pkg import string

        【讨论】:

          【解决方案13】:

          我发现将“PYTHONPATH”环境变量设置到顶层文件夹更容易:

          bash$ export PYTHONPATH=/PATH/TO/APP
          

          然后:

          import sub1.func1
          #...more import
          

          当然,PYTHONPATH 是“全局的”,但它还没有给我带来麻烦。

          【讨论】:

          • 这基本上就是virtualenv 让您管理导入语句的方式。
          【解决方案14】:

          除了 John B 所说的之外,似乎设置 __package__ 变量应该会有所帮助,而不是更改 __main__ 可能会搞砸其他事情。但据我测试,它并不能完全发挥应有的作用。

          我有同样的问题,PEP 328 或 366 都没有完全解决问题,因为据我所知,到一天结束时,两者都需要将包的头部包含在 sys.path 中。

          我还应该提到,我没有找到如何格式化应该进入这些变量的字符串。是"package_head.subfolder.module_name" 还是什么?

          【讨论】:

            【解决方案15】:

            您必须将模块的路径附加到PYTHONPATH

            export PYTHONPATH="${PYTHONPATH}:/path/to/your/module/"
            

            【讨论】:

            • 这与操作sys.path 大致相同,因为sys.path 是从PYTHONPATH 初始化的
            • @Joril 没错,但sys.path 需要在源代码中硬编码,而PYTHONPATH 是一个环境变量,可以导出。
            【解决方案16】:

            类似于 Роман Арсеньев 的回答,但如果你想回到两个目录:

            两个目录返回:

            app/
               __init__.py
               sub1/
                  __init__.py
                  mod1.py          ------*
                  subsub1/               |
                     __init__.py   mod1 and mod4  
                     mod4.py       ------|
               sub2/                     |
                  __init__.py           into
                  mod2.py                |
                  subsub2/              mod3
                     __init__.py         |
                       mod3.py     <-----*
            
            

            将 mod1 导入 mod3:

            import sys
            sys.path.insert(1, '../../')
            
            from sub1 import mod1
            from sub1.subsub1 import mod
            

            【讨论】:

              【解决方案17】:

              此方法查询并自动填充路径:

              import os
              import inspect
              currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
              parentdir = os.path.dirname(currentdir)
              os.sys.path.insert(1, parentdir)
              # print("currentdir = ", currentdir)
              # print("parentdir=", parentdir)
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2023-03-20
                • 1970-01-01
                • 1970-01-01
                • 2019-11-24
                相关资源
                最近更新 更多