如何在包内引用 Python 中的***模块?

Posted

技术标签:

【中文标题】如何在包内引用 Python 中的***模块?【英文标题】:How to reference to the top-level module in Python inside a package? 【发布时间】:2012-02-01 03:23:04 【问题描述】:

在下面的层次结构中,是否有一种方便且通用的方式来使用下面所有 .py 文件中的通用术语来引用 top_package?我希望有一种一致的方式来导入其他模块,这样即使“top_package”更改名称也不会中断。

我不赞成使用像“..level_one_a”这样的相对导入,因为相对路径将与下面的每个 python 文件不同。我正在寻找一种方法:

    对于包中的同一个模块,每个 python 文件可以有相同的导入语句。

    在包内的任何 .py 文件中对“top_package”的解耦引用,因此无论“top_package”名称更改为什么,都不会中断。

    top_package/
      __init__.py
      level_one_a/
        __init__.py
        my_lib.py
        level_two/
          __init__.py
          hello_world.py
      level_one_b/
        __init__.py
        my_lib.py
      main.py
    

【问题讨论】:

为什么要引用顶层模块? (有时这个答案揭示了真正的问题/解决方案。) 我想让包尽可能地复用,这样当我重命名顶层模块时,一切仍然可以正常工作,而不必更改里面的每个导入名称。 【参考方案1】:

这应该可以完成工作:

top_package = __import__(__name__.split('.')[0])

这里的诀窍是,对于每个模块,__name__ 变量包含模块的完整路径,用点分隔,例如top_package.level_one_a.my_lib。因此,如果您想获取***包名,您只需要获取路径的第一个组件并使用__import__ 导入即可。

尽管用于访问包的变量名称仍称为top_package,但您可以重命名包,如果仍然可以使用。

【讨论】:

-1 该问题要求一个不依赖于名称“top_package”的解决方案。 @Jon-Eric 感谢您的评论。无论顶部包名称如何,我都已修复我的答案。 这并不完全正确;如果当前模块不是从顶层导入的,则其__name__ 将不包含该模块的完整路径,而仅包含其中的一部分,它将从您导入该模块的位置开始。【参考方案2】:

我相信如果不使用相对导入或命名包,#2 是不可能的。您必须通过显式调用其名称或使用相对导入来指定要导入的模块。否则口译员怎么知道你想要什么?

如果您将应用程序启动器设置为比top_level/ 高一级并拥有import top_level,那么您可以从top_level 包内的任何位置引用top_level.*

(我可以向您展示我正在开发的软件的示例:http://github.com/toddself/beerlog/)

【讨论】:

【参考方案3】:

您可以结合使用 __import__() 函数和包的 __path__ 属性。

例如,假设您希望从包中的其他位置导入<whatever>.level_one_a.level_two.hello_world。你可以这样做:

import os
_temp = __import__(__path__[0].split(os.sep)[0] + ".level_one_a.level_two.hello_world")
my_hello_world = _temp.level_one_a.level_two.hello_world

此代码独立于***包的名称,可以在包中的任何位置使用。也很丑。

【讨论】:

【参考方案4】:

将您的包和main 脚本放入外部容器目录中,如下所示:

container/
    main.py
    top_package/
        __init__.py
        level_one_a/
            __init__.py
            my_lib.py
            level_two/
                __init__.py
                hello_world.py
        level_one_b/
            __init__.py
            my_lib.py

main.py 运行时,其父目录(container)会自动添加到sys.path 的开头。由于top_package 现在位于同一目录中,因此可以从包树中的任何位置导入它。

所以hello_world.py 可以像这样导入level_one_b/my_lib.py

from top_package.level_one_b import my_lib

无论容器目录的名称是什么,或者它位于何处,导入将始终使用这种安排。

但请注意,在您的原始示例中,top_package 它可以很容易地用作容器目录本身。您所要做的就是删除top_package/__init__.py,您将得到有效的相同安排。

之前的导入语句将更改为:

from level_one_b import my_lib

您可以随意重命名top_package

【讨论】:

如果你去掉top_package/__init__.py,那么level_one_a和level_one_b就不能互相导入了。 @DonGar。不——它会工作得很好。但请注意,我指的是 OPs 问题中的结构,而不是我的答案中显示的结构。关键区别在于main.py 脚本的位置。 我同意您回答的主要部分,但以“但请注意”开头的段落是我不同意的。除非您还意味着将 level_one_a 和 level_one_b 都移动到 top_container 的新子模块中。 @DonGar,你显然没有用真实的代码做任何测试。我的答案中的所有内容都完全按照说明进行。我不明白你的分歧是基于什么。 是的,我有。事实上,我只是经历了第一次设置不使用路径修改的项目的痛苦过程。但是,经过反思,如果由 main 导入,库可以相互导入。但如果从 level_one 模块内的测试程序调用,则不会,即使测试程序是由“发现”机制调用的。您对***包装器包的想法最终解决了我的各种导入问题(我需要 main/tests/lint 来完成所有工作)。【参考方案5】:

这在库模块中起作用:

import __main__ as main_package
TOP_PACKAGE = main_package.__package__.split('.')[0]

【讨论】:

以上是关于如何在包内引用 Python 中的***模块?的主要内容,如果未能解决你的问题,请参考以下文章

Python:导入包含的包

如何保护类,使其在包外不可见

如何保护类,使其在包外不可见

如何编译包内的两个程序?

使用 dbms_job 在包内执行 oracle 中的存储过程

如何在 Eclipse 项目资源管理器中的包内创建子包