【问题标题】:How to import a Python class that is in a directory above?如何导入上面目录中的 Python 类?
【发布时间】:2010-11-06 10:59:50
【问题描述】:

我想从位于当前目录上方的文件中的类继承。

可以相对导入那个文件吗?

【问题讨论】:

    标签: python module directory python-import


    【解决方案1】:

    from ..subpkg2 import mod

    根据 Python 文档:在包层次结构中,使用两个点,正如 import statement 文档所说:

    在指定要导入的模块时,您不必指定模块的绝对名称。当一个模块或包包含在另一个包中时,可以在同一个顶级包中进行相对导入,而不必提及包名。通过在from 之后的指定模块或包中使用前导点,您可以指定在不指定确切名称的情况下向上遍历当前包层次结构的高度。一个前导点表示进行导入的模块所在的当前包。 两个点表示一个包级别。三个点向上两个级别,等等。因此,如果您从 pkg 包中的模块执行 from . import mod,那么您最终将导入 pkg.mod。如果您从pkg.subpkg1 中执行from ..subpkg2 import mod,您将导入pkg.subpkg2.mod。相对导入的规范包含在 PEP 328 中。

    PEP 328 处理绝对/相对导入。

    【讨论】:

    • up1 = os.path.abspath('..') sys.path.insert(0, up1)
    • Pep 328 仅显示 Python 版本:2.4、2,5、2.6。第 3 版留给知识渊博的人。
    • 这会触发错误ValueError:尝试相对导入超出顶级包
    • 导致 ImportError:尝试相对导入,但没有已知的父包
    【解决方案2】:
    import sys
    sys.path.append("..") # Adds higher directory to python modules path.
    

    【讨论】:

    • 这对我有用。加上这个后,我可以直接导入父模块,不需要使用“..”。
    • 只有在应用程序的 PWD 值(其当前的directeroy)是父级的子级时才有效。因此,即使它有效,多种系统性变化也会将其赶走。
    • 这对我来说也适用于导入更高级别的模块。我将它与 os.chdir("..") 一起使用来加载更高级别的其他文件。
    • 这似乎触发了与上述来自 gimel 的答案相同的错误。
    【解决方案3】:

    @gimel 的回答是正确的如果你可以保证他提到的包层次结构。如果你不能——如果你真正的需要是你表达的那样,只与目录相关并且与打包没有任何必要的关系——那么你需要在__file__上工作以找出父目录(几个@ 987654322@ 调用会做;-),然后(如果该目录尚未在 sys.path 上)在 sys.path__import__ 的开头临时插入所述目录,再次删除所述目录 - 确实是混乱的工作,但是,“当你必须,你必须”(并且 Pyhon 力求从不阻止程序员做 必须 做的事情——就像 ISO C 标准在“C 精神”一节中所说的那样它的前言!-)。

    这是一个可能适合您的示例:

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

    【讨论】:

    • 这可能会向sys.path 添加一个inside Python 包的目录,从而使相同的模块以不同的名称和所有相应的错误可用。 pypy 中的 autopath.py 或twisted 中的 _preamble.py 通过使用在向上遍历目录时识别顶级包的搜索条件来解决它。
    • 您可能希望在需要的导入后执行sys.path.remove(pathYouJustAdded) 之类的操作,以免保留这条新路径。
    【解决方案4】:

    从恰好比当前目录高一级的目录导入模块:

    from .. import module
    

    【讨论】:

    • 我得到:尝试相对导入超出顶级包:(
    • using from .. import module 我得到错误 ValueError: Attempted relative import in non-package 按照建议
    • ImportError: 在没有已知父包的情况下尝试相对导入 收到此错误
    【解决方案5】:

    如何加载一个目录向上的模块

    前言:我对之前的答案进行了大量重写,希望能帮助人们轻松进入 python 的生态系统,并希望通过 python 的导入系统为每个人带来成功的最佳改变。

    这将涵盖包中的相对导入,我认为这是 OP 问题最可能的情况。

    Python 是一个模块化系统

    这就是为什么我们写import foo 来从根命名空间加载一个模块“foo”,而不是写:

    foo = dict();  # please avoid doing this
    with open(os.path.join(os.path.dirname(__file__), '../foo.py') as foo_fh:  # please avoid doing this
        exec(compile(foo_fh.read(), 'foo.py', 'exec'), foo)  # please avoid doing this
    

    Python 没有与文件系统耦合

    这就是为什么我们可以在不提供虚拟文件系统(例如 Jython)的情况下将 python 嵌入到没有事实上的文件系统的环境中。

    与文件系统解耦后,导入变得灵活,这种设计允许从归档/zip 文件导入、导入单例、字节码缓存、cffi 扩展,甚至远程代码定义加载。

    如果导入不与文件系统耦合,“一个目录向上”是什么意思?我们必须选择一些启发式方法,但我们可以这样做,例如在package 中工作时,已经定义了一些启发式方法,使relative imports like .foo and ..foo 在同一个包中工作。酷!

    如果您真的想将源代码加载模式与文件系统结合起来,您可以这样做。您必须选择自己的启发式方法,并使用某种导入机制,我推荐importlib

    Python 的 importlib 示例如下所示:

    import importlib.util
    import sys
    
    # For illustrative purposes.
    file_path = os.path.join(os.path.dirname(__file__), '../foo.py')
    module_name = 'foo'
    
    foo_spec = importlib.util.spec_from_file_location(module_name, file_path)
    # foo_spec is a ModuleSpec specifying a SourceFileLoader
    foo_module = importlib.util.module_from_spec(foo_spec)
    sys.modules[module_name] = foo_module
    foo_spec.loader.exec_module(foo_module)
    
    foo = sys.modules[module_name]
    # foo is the sys.modules['foo'] singleton
    

    包装

    这里有一个很好的示例项目:https://github.com/pypa/sampleproject

    python 包是有关您的源代码的信息的集合,它可以告知其他工具如何将您的源代码复制到其他计算机,以及如何将您的源代码集成到该系统的路径中,以便import foo 为其他工具工作计算机(不考虑解释器、主机操作系统等)

    目录结构

    让我们在某个目录(最好是空目录)中有一个包名foo

    some_directory/
        foo.py  # `if __name__ == "__main__":`  lives here
    

    我的偏好是创建setup.py 作为foo.py 的兄弟,因为它使 setup.py 文件的编写更简单,但是如果你愿意,你可以编写配置来更改/重定向 setuptools 默认所做的一切;例如将foo.py 放在“src/”目录下比较流行,这里不做介绍。

    some_directory/
        foo.py
        setup.py
    

    .

    #!/usr/bin/env python3
    # setup.py
    
    import setuptools
    
    setuptools.setup(
        name="foo",
        ...
        py_modules=['foo'],
    )
    

    .

    python3 -m pip install --editable ./  # or path/to/some_directory/
    

    “editable”又名-e 将再次重定向导入机器以加载此目录中的源文件,而不是将当前确切的文件复制到安装环境的库中。这也可能导致开发人员机器上的行为差异,请务必测试您的代码! 除了 pip 之外,还有其他工具,但我建议将 pip 作为入门工具:)

    我还喜欢将foo 设为“包”(包含__init__.py 的目录)而不是模块(单个“.py”文件),“包”和“模块”都可以加载到根命名空间,模块允许嵌套命名空间,如果我们想要“相对一个目录向上”导入,这很有帮助。

    some_directory/
        foo/
            __init__.py
        setup.py
    

    .

    #!/usr/bin/env python3
    # setup.py
    
    import setuptools
    
    setuptools.setup(
        name="foo",
        ...
        packages=['foo'],
    )
    

    我也喜欢创建一个foo/__main__.py,这允许python将包作为一个模块执行,例如python3 -m foofoo/__main__.py作为__main__执行。

    some_directory/
        foo/
            __init__.py
            __main__.py  # `if __name__ == "__main__":`  lives here, `def main():` too!
        setup.py
    

    .

    #!/usr/bin/env python3
    # setup.py
    
    import setuptools
    
    setuptools.setup(
        name="foo",
        ...
        packages=['foo'],
        ...
        entry_points={
            'console_scripts': [
                # "foo" will be added to the installing-environment's text mode shell, eg `bash -c foo`
                'foo=foo.__main__:main',
            ]
        },
    )
    

    让我们用更多的模块来充实它: 基本上,你可以有这样的目录结构:

    some_directory/
        bar.py           # `import bar`
        foo/
            __init__.py  # `import foo`
            __main__.py
            baz.py       # `import foo.baz
            spam/           
                __init__.py  # `import foo.spam`
                eggs.py      # `import foo.spam.eggs`
        setup.py
    

    setup.py 通常包含有关源代码的元数据信息,例如:

    • 安装名为“install_requires”的依赖项
    • 应该为包管理使用什么名称(安装/卸载“名称”),我建议在我们的案例中使用您的主要 python 包名称foo,尽管用下划线代替连字符很流行
    • 许可信息
    • 成熟度标签(alpha/beta/etc),
    • 受众标签(针对开发人员、机器学习等),
    • 单页文档内容(如自述文件),
    • shell 名称(您在用户 shell 中键入的名称,例如 bash,或者您在图形用户 shell 中找到的名称,例如开始菜单),
    • 此软件包将安装(和卸载)的 python 模块列表
    • 事实上的“运行测试”入口点python ./setup.py test

    它非常广泛,如果源模块安装在开发机器上,它甚至可以即时编译 c 扩展。对于日常示例,我推荐PYPA Sample Repository's setup.py

    如果您要发布构建工件,例如要运行几乎相同的计算机的代码副本,则 requirements.txt 文件是快照确切依赖关系信息的常用方法,其中“install_requires”是一个很好的方法捕获最小和最大兼容版本。但是,鉴于目标机器几乎完全相同,我强烈建议创建一个包含整个 python 前缀的 tarball。这可能很棘手,太详细而无法进入这里。查看pip install--target 选项,或virtualenv aka venv 获取线索。

    回到例子

    如何将文件导入上一级目录:

    来自 foo/spam/eggs.py,如果我们想要来自 foo/baz 的代码,我们可以通过它的绝对命名空间来请求它:

    import foo.baz
    

    如果我们想保留在未来通过其他相对的baz 实现将eggs.py 移动到其他目录的能力,我们可以使用相对导入,例如:

    import ..baz
    

    【讨论】:

    • 澄清一下,您是说要使用import foo.baz 还是import ..baz,我需要在正确制作的setup.py 文件上运行python3 -m pip install --editable FILEPATH?令人惊讶的是,python 需要如此多的开销才能从 above 进行导入。 . .
    • 我做了所有这些,现在我可以从我计算机上任何地方的任何 python 脚本导入 foo。有没有什么方法可以在没有 importlib 和所有样板的情况下从上面的文件中导入?
    【解决方案6】:

    为了清楚起见,这里是ThorSummoner's 答案的三步、有点简约的版本。它并没有完全达到我想要的效果(我将在底部解释),但它可以正常工作。

    第一步:制作目录和setup.py

    filepath_to/project_name/
        setup.py
    

    setup.py,写:

    import setuptools
    
    setuptools.setup(name='project_name')
    

    第二步:将该目录安装为包

    在控制台中运行此代码:

    python -m pip install --editable filepath_to/project_name
    

    您可能需要使用python3 或其他东西,而不是python,这取决于您的python 的安装方式。此外,您可以使用-e 代替--editable

    现在,您的目录或多或少会像这样。我不知道鸡蛋的东西是什么。

    filepath_to/project_name/
        setup.py
        test_3.egg-info/
            dependency_links.txt
            PKG-INFO
            SOURCES.txt
            top_level.txt
    

    此文件夹被视为一个 python 包,即使您在计算机上的其他任何地方编写脚本,您也可以从该父目录中的文件导入。

    步骤 3. 从上方导入

    假设您创建了两个文件,一个在项目的主目录中,另一个在子目录中。它看起来像这样:

    filepath_to/project_name/
        top_level_file.py
        subdirectory/
            subfile.py
    
        setup.py          |
        test_3.egg-info/  |----- Ignore these guys
            ...           |
    

    现在,如果 top_level_file.py 看起来像这样:

    x = 1
    

    然后我可以从subfile.py 或您计算机上其他任何位置的任何其他文件导入它。

    # subfile.py  OR  some_other_python_file_somewhere_else.py
    
    import random # This is a standard package that can be imported anywhere.
    import top_level_file # Now, top_level_file.py works similarly.
    
    print(top_level_file.x)
    

    这与我所寻找的不同:我希望 python 有一种从上面的文件导入的单行方式。相反,我必须把脚本当作一个模块,做一堆样板,然后全局安装它,以便整个 python 安装可以访问它。太矫枉过正了。如果有人有比不涉及上述过程或importlib 恶作剧更简单的方法,请告诉我。

    【讨论】:

      【解决方案7】:

      @alex-martellipathlib 的完善答案:

      import pathlib
      import sys
      
      _parentdir = pathlib.Path(__file__).parent.parent.resolve()
      sys.path.insert(0, str(_parentdir))
      
      import module_in_parent_dir
      
      sys.path.remove(str(_parentdir))
      

      【讨论】:

        【解决方案8】:

        运行导入/myprogram/mainmodule.pypython /myprogram/submodule/mymodule.py,例如,通过

        from mainmodule import *
        

        在 Linux 上(例如,在 python Docker image 中),我必须将程序根目录添加到 PYTHONPATH

        export PYTHONPATH=/myprogram
        

        【讨论】:

          【解决方案9】:

          Python 是一个模块化系统

          Python 不依赖文件系统

          为了可靠地加载 python 代码,将该代码放在一个模块中,并将该模块安装在 python 的库中。

          已安装的模块始终可以使用import <name> 从顶级命名空间加载


          这里有一个很好的示例项目:https://github.com/pypa/sampleproject

          基本上,你可以有这样的目录结构:

          the_foo_project/
              setup.py  
          
              bar.py           # `import bar`
              foo/
                __init__.py    # `import foo`
          
                baz.py         # `import foo.baz`
          
                faz/           # `import foo.faz`
                  __init__.py
                  daz.py       # `import foo.faz.daz` ... etc.
          

          .

          请务必在setup.py 中声明您的setuptools.setup()

          官方示例:https://github.com/pypa/sampleproject/blob/master/setup.py

          在我们的例子中,我们可能想要导出 bar.pyfoo/__init__.py,我的简短示例:

          setup.py

          #!/usr/bin/env python3
          
          import setuptools
          
          setuptools.setup(
              ...
              py_modules=['bar'],
              packages=['foo'],
              ...
              entry_points={}, 
                  # Note, any changes to your setup.py, like adding to `packages`, or
                  # changing `entry_points` will require the module to be reinstalled;
                  # `python3 -m pip install --upgrade --editable ./the_foo_project
          )
          

          .

          现在我们可以将我们的模块安装到 python 库中了; 使用 pip,您可以在编辑模式下将the_foo_project 安装到您的 python 库中, 所以我们可以实时处理它

          python3 -m pip install --editable=./the_foo_project
          
          # if you get a permission error, you can always use 
          # `pip ... --user` to install in your user python library
          

          .

          现在从任何 python 上下文中,我们可以加载我们共享的 py_modules 和包

          foo_script.py

          #!/usr/bin/env python3
          
          import bar
          import foo
          
          print(dir(bar))
          print(dir(foo))
          

          【讨论】:

          • 应该提到我总是在使用 pip install --edit foo 处理模块时安装我的模块,几乎总是在 virtualenv 中。我几乎从不编写不打算安装的模块。如果我误解了我想知道的事情。
          • 我还应该提到,使用 venv-auto-creation 测试套件,比如 tox 非常有用,因为 editable egg 链接和安装的 python 模块在各个方面都不完全相同;例如,将在路径查找中找到向可编辑模块添加新命名空间,但如果未在 setup.py 文件中导出,则不会打包/安装!测试你的用例:)
          猜你喜欢
          • 2014-01-28
          • 2013-11-10
          • 2021-01-31
          • 2015-06-25
          • 2021-09-08
          • 2021-01-09
          • 1970-01-01
          • 2019-12-10
          • 1970-01-01
          相关资源
          最近更新 更多