7

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__.pyPYTHONPATH=$(pwd) python3 foo/
4

1 回答 1

5

正确的样板文件是无,只需编写显式的相对导入,如果有人试图将模块作为脚本运行或sys.path配置错误,则让异常逃逸:

from . import baz

PEP 366 中给出的样板只是为了表明提议的更改足以允许用户直接执行* 如果他们真的想要工作,它并不意味着让直接执行工作是一个好主意(它不是,这是一个坏主意,几乎不可避免地会导致其他问题,即使使用 PEP 的样板文件也是如此)。

您提出的替代样板重新创建了由 Python 2 中的隐式相对导入引起的问题:"baz"模块作为bazfrom导入__main__,但将作为"foo.baz"其他任何地方导入,因此您最终会得到两个sys.modules不同名称的副本。

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

相比之下,如果您使用 PEP 样板,那么__main__将正确导入bazas "foo.baz",而您唯一需要担心的是其他模块可能会导入foo.bar.

如果您想要更简单的样板来明确防止“无意中以不同的名称制作同一模块的两个副本”错误而不对包名称进行硬编码,那么您可以使用它:

if not __package__:
    raise RuntimeError(f"{__file__} must be imported as a package submodule")

但是,如果您要这样做,您也可以按照from . import baz上面的建议无条件地执行,如果有人尝试直接运行脚本而不是通过-m开关运行,则让底层异常逃逸。


*直接执行意味着从以下位置执行代码:

  1. 一个文件路径参数,除了目录和 zip 文件路径 ( python <file path>)。
  2. 一个-c论点 ( python -c <code>)。
  3. 交互式解释器 ( python)。
  4. 标准输入 ( python < <file path>)。

间接执行意味着从以下位置执行代码:

  1. 目录或 zip 文件路径参数 ( python <directory or zip file path>)。
  2. 一个-m论点 ( python -m <module name>)。
  3. 导入语句 ( import <module name>)

现在具体回答你的问题:

  1. 第一个子表达式__name__ == "__main__"是必需的还是已经被第二个子表达式暗示了__package__ is None

除了具有现代导入系统的模块之外,很难找到__package__ is None任何其他地方。__main__但它过去更常见,因为它不是由导入系统在模块加载时设置,__package__而是由模块中执行的第一个显式相对导入延迟设置。换句话说,样板只是试图让直接执行工作(上面的案例 1 到 4)但__package__ is None 用于暗示直接执行或导入语句(上面的案例 7),因此要过滤掉案例 7 的子表达式__name__ == "__main__"(案例 1 到 6以上)是必要的。

  1. 为了处理空字符串的情况(例如在通过提供包含目录从文件系统执行的子模块 中:),不__package__ is None应该改为使用第二个子表达式吗?not __package____package____main__.pyPYTHONPATH=$(pwd) python3 foo/

不,因为样板只是试图让直接执行工作(上面的案例 1 到 4),它并没有试图让其他类型的sys.path错误配置静默通过。

于 2020-09-22T08:00:29.133 回答