首页 文章

如何在Python中进行相对导入?

提问于
浏览
456

想象一下这个目录结构:

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

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

我试过 from ..sub2 import mod2 但是我得到了"Attempted relative import in non-package" .

我google了一下,但发现只有“ sys.path 操纵”黑客 . 有没有干净的方式?


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

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

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

16 回答

  • 8

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

    问题是你通过将mod1.py作为参数传递给解释器来运行模块'main' .

    来自PEP 328

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

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

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

  • 24
    main.py
    setup.py
    app/ ->
        __init__.py
        package_a/ ->
           __init__.py
           module_a.py
        package_b/ ->
           __init__.py
           module_b.py
    
    • 你运行 python main.py .

    • main.pyimport app.package_a.module_a

    • module_a.py 确实 import app.package_b.module_b

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

    只要你的PYTHONPATH中有 app ,那就行了 . main.py 可能在任何地方 .

    因此,您编写 setup.py 将整个应用程序包和子包复制(安装)到目标系统的python文件夹,并将 main.py 复制(安装)到目标系统的脚本文件夹 .

  • 1

    这是适用于我的解决方案:

    我做相对导入为 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提供包结构信息,通过它可以成功解析相对导入 .

    在进行相对导入时,我多次遇到过这个问题 . 并且,在阅读了之前的所有答案之后,我仍然无法以干净的方式弄清楚如何解决它,而无需在所有文件中放置样板代码 . (虽然有些评论非常有用,感谢@ncoghlan和@XiongChiamiov)

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

  • 24

    "Guido views running scripts within a package as an anti-pattern"(拒绝PEP-3122

    我花了很多时间试图找到解决方案,在Stack Overflow上阅读相关帖子并对自己说“必须有更好的方法!” . 看起来没有 .

  • 9
    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
    

    我正在使用此代码段从路径导入模块,希望有所帮助

  • 109

    这是100%解决的:

    • app /

    • main.py

    • 设置/

    • local_setings.py

    在app / main.py中导入设置/ local_setting.py:

    main.py:

    import sys
    sys.path.insert(0, "../settings")
    
    
    try:
        from local_settings import *
    except ImportError:
        print('No Import')
    
  • -5

    用例子解释 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 中的文件使用了文件夹 package_a 中的文件,这就是你想要的 . 对??

  • -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, '../..')
    

    这样的黑客,但让一切工作!所以请记住,如果你想要你的决定 override other paths 那么你需要使用sys.path.insert(0,pathname)来使它工作!这对我来说是一个非常令人沮丧的问题,很多人都说_747126_函数用于sys.path,但是如果你已经定义了一个模块(我发现它很奇怪的行为),这不起作用

  • 19

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

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

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

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

    这取自this SO answer .

  • 104
  • 1

    来自Python doc

    在Python 2.5中,您可以使用from future import absolute_import指令将导入行为切换为绝对导入 . 这种绝对导入行为将成为未来版本(可能是Python 2.7)的默认行为 . 一旦绝对导入是默认导入,导入字符串将始终找到标准库的版本 . 建议用户应尽可能多地开始使用绝对导入,因此最好从代码中的pkg import string开始编写

  • 11

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

    bash$ export PYTHONPATH=/PATH/TO/APP
    

    然后:

    import sub1.func1
    #...more import
    

    当然,PYTHONPATH是“全球性的”,但它并没有给我带来麻烦 .

  • 42

    除了John B所说的,似乎设置 __package__ 变量应该有所帮助,而不是改变 __main__ ,这可能搞砸其他事情 . 但据我测试,它并没有完全正常工作 .

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

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

  • 2

    假设您在顶级运行,然后在 mod1 中使用:

    import sub2.mod2
    

    代替

    from ..sub2 import mod2
    
  • 284

    我认为你要问自己的是:

    • 为什么我需要这样做?

    • 我的包裹分离工作做得好吗?

    我不知道你为什么要这样做的背景 . 但对我来说,更清洁的设计是拥有以下包结构:

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

    那你只需要做:

    from sub12 import mod2
    

相关问题