微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Python中显式相对导入的正确样板是什么?

如何解决Python中显式相对导入的正确样板是什么?

PEP 366 - Main module explicit relative imports中引入了模块作用域变量__package__,以允许在子模块中进行显式相对导入,其中摘录如下:

当主模块由其文件名指定时,则 __package__属性将设置为None。为了在直接执行模块时允许相对导入,样板类似于 在第一个相对导入语句之前,需要执行以下操作:

if __name__ == "__main__" and __package__ is None:
    __package__ = "expected.package.name"

请注意,仅当已经可以通过以下方式访问顶层软件包时,此样板才足够 sys.path。在其中需要操纵sys.path的其他代码 在没有顶级软件包的情况下直接执行命令 已经可以导入。

方法也具有与使用绝对值相同的缺点 导入同级模块-如果脚本移到其他位置 包或子包,样板将需要更新 手动。这样做的好处是只需要进行一次更改 每个文件,无论相对导入的数量如何。

我尝试在以下设置中使用此样板:

  • 目录布局:

    foo
    ├── bar.py
    └── baz.py
    
  • bar.py子模块的内容

    if __name__ == "__main__" and __package__ is None:
        __package__ = "foo"
    
    from . import baz
    

文件系统执行子模块bar.py时,样板工作(PYTHONPATH修改使软件包foo /可在sys.path上访问):

PYTHONPATH=$(pwd) python3 foo/bar.py

当从模块名称空间执行子模块bar.py时,样板也可以工作:

python3 -m foo.bar

但是,以下两种可选样板在两种情况下都与bar.py子模块的内容一样有效:

if __package__:
    from . import baz
else:
    import baz

此外,该替代样例更简单,并且在将子模块baz.py与子模块bar.py一起移动到其他软件包时,不需要对其进行任何更新(因为它不会对软件包名称"foo"进行硬编码) )。

这是我对PEP 366样板的疑问:

  1. 一个子表达式__name__ == "__main__"是必需的还是第二个子表达式__package__ is None已经隐含了?
  2. 第二个子表达式__package__ is None不应是not __package__,以便处理__package__是空字符串的情况(例如在__main__.py子模块中执行通过提供包含目录:PYTHONPATH=$(pwd) python3 foo/)来建立文件系统?

解决方法

正确的样板为空,只要编写明确的相对导入,并在有人尝试将模块作为脚本运行或配置sys.path时让异常转义:

from . import baz

PEP 366中给出的样板只是为了表明所提议的更改足以使用户真正愿意执行直接执行 *,但这并不意味着使直接执行工作是一个好主意(不是一个坏主意,即使有了PEP的样板,也几乎不可避免地会引起其他问题)。

您提出的替代样板重新创建了Python 2中隐式相对导入所引起的问题:"baz"模块从baz中以__main__的形式导入,但将以{{1}的形式导入},因此您最终在"foo.baz"中得到了两个副本,名称不同。

在其他问题中,这意味着如果其他某个模块抛出sys.modules,而您的foo.baz.SomeException模块试图捕获__main__,则该模块将不起作用,因为这将是两个不同的异常来自两个不同模块的对象。

相比之下,如果您使用PEP样板,则baz.SomeException会将__main__正确地导入为baz,而您唯一需要担心的是其他可能导入{{ 1}}。

如果您希望使用更简单的样板来明确地防止“无意间以不同的名称制作同一模块的两个副本”错误而无需对软件包名称进行硬编码,则可以使用以下方法:

"foo.baz"

但是,如果您要这样做,则可以像上面建议的那样无条件执行foo.bar,并且如果有人尝试直接运行脚本而不是通过{{1 }}开关。


* 直接执行表示从以下位置执行代码:

  1. 除了目录和zip文件路径(if not __package__: raise RuntimeError(f"{__file__} must be imported as a package submodule") )以外的文件路径参数。
  2. 一个from . import baz自变量(-m)。
  3. 交互式解释器(python <file path>)。
  4. 标准输入(-c)。

但不是来自:

  1. 目录或zip文件路径参数(python -c <code>)。
  2. 一个python自变量(python < <file path>)。
  3. 导入语句(python <directory or zip file path>

现在回答您的问题:

  1. 第一个子表达式-m是必需的还是第二个子表达式python -m <module name>已经隐含了?

使用现代导入系统很难获得import <module name>模块之外的__name__ == "__main__"模块。但是它曾经更常见,因为__package__ is None不是由导入系统在模块加载时设置的,而是由模块中执行的第一个显式相对导入来延迟设置的。换句话说,样板仅试图让直接执行工作(上述情况1至4),但是__package__ is None 用于暗示直接执行或import语句 (上述情况7),因此要过滤出情况7,需要使用子表达式__main__(上述情况1至6)。

  1. 第二个子表达式__package__不应为__package__ is None,以便处理__name__ == "__main__"为空字符串的情况(如在__package__ is None子模块中执行 通过提供包含目录的文件系统: not __package__)?

样板仅试图让直接执行工作(上述情况1至4),而不是试图让__package__错误配置的其他形式静默地通过。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。