python - 显式相对导入的正确样板是什么?

标签 python boilerplate relative-import

PEP 366 - Main module explicit relative imports其中引入了模块范围变量 __package__为了允许子模块中的显式相对导入,有以下摘录:

When the main module is specified by its filename, then the __package__ attribute will be set to None. To allow relative imports when the module is executed directly, boilerplate similar to the following would be needed before the first relative import statement:

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

Note that this boilerplate is sufficient only if the top level package is already accessible via sys.path. Additional code that manipulates sys.path would be needed in order for direct execution to work without the top level package already being importable.

This approach also has the same disadvantage as the use of absolute imports of sibling modules - if the script is moved to a different package or subpackage, the boilerplate will need to be updated manually. It has the advantage that this change need only be made once per file, regardless of the number of relative imports.


我尝试在以下设置中使用此样板:
  • 目录布局:
    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 样板的问题:
  • 是第一个子表达式 __name__ == "__main__"必要的还是第二个子表达式 __package__ is None 已经暗示了? ?
  • 第二个子表达式不应该 __package__ is Nonenot __package__相反,为了处理 __package__ 的情况是空字符串(如 __main__.py 通过提供包含目录从文件系统执行的子模块: PYTHONPATH=$(pwd) python3 foo/ )?
  • 最佳答案

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

    from . import baz
    
    PEP 366 中给出的样板只是为了表明提议的更改足以允许用户在他们真正想要的情况下使直接执行*工作,这并不意味着使直接执行工作是一个好主意(它不是,这是一个坏主意,几乎不可避免地会导致其他问题,即使使用 PEP 的样板文件也是如此)。
    您提出的替代样板重新创建了由 Python 2 中的隐式相对导入引起的问题:"baz"模块被导入为 baz来自 __main__ , 但将作为 "foo.baz" 导入其他任何地方,所以你最终会在 sys.modules 中得到两个副本在不同的名称下。
    除其他问题外,这意味着如果某些其他模块抛出 foo.baz.SomeException和你的__main__模块试图捕捉 baz.SomeException ,它不起作用,因为它们将是来自两个不同模块的两个不同异常对象。
    相比之下,如果您使用 PEP 样板,那么 __main__将正确导入 baz"foo.baz" ,而您唯一需要担心的是其他模块可能会导入 foo.bar .
    如果您想要更简单的样板来明确防止“无意中以不同的名称制作同一模块的两个副本”错误而不对包名称进行硬编码,那么您可以使用它:
    if not __package__:
        raise RuntimeError(f"{__file__} must be imported as a package submodule")
    
    但是,如果你要这样做,你也可以这样做 from . import baz无条件地按照上面的建议,如果有人试图直接运行脚本而不是通过 -m 运行脚本,则让底层异常逃逸。转变。

    * 直接执行意味着从以下位置执行代码:
  • 一个文件路径参数,除了目录和 zip 文件路径 ( python <file path> )。
  • 一个 -c论点(python -c <code>)。
  • 交互式解释器 (python)。
  • 标准输入 (python < <file path>)。

  • 间接执行意味着从以下位置执行代码:
  • 目录或 zip 文件路径参数 ( python <directory or zip file path> )。
  • 一个 -m论点(python -m <module name>)。
  • 进口声明(import <module name>)

  • 现在具体回答你的问题:
    1. Is the first subexpression __name__ == "__main__" necessary or is it already implied by the second subexpression __package__ is None?

    很难得到__package__ is None __main__ 以外的任何地方具有现代导入系统的模块。但它过去更常见,而不是由导入系统在模块加载时设置,__package__而是由模块中执行的第一个显式相对导入延迟设置。换句话说,样板只是试图让直接执行工作(上面的案例 1 到 4)但是 __package__ is None用于暗示直接执行或导入语句(上面的案例 7),因此要过滤掉案例 7 子表达式 __name__ == "__main__" (上述案例 1 至 6)是必要的。
    1. Shouldn’t the second subexpression __package__ is None be not __package__ instead, in order to handle the case where __package__ is the empty string (like in a __main__.py submodule executed from the file system by supplying the containing directory: PYTHONPATH=$(pwd) python3 foo/)?

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

    关于python - 显式相对导入的正确样板是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63909243/

    相关文章:

    python - 如何分别传播字符串列表?

    python - 将文件中的数据插入 SQLite 数据库

    c# - Asp.Net Boilerplate 在应用程序服务验证时抛出异常

    html - 固定头 Bootstrap 的定位

    python - Python中导入不同目录下的文件

    namespaces - XML 命名空间中的分隔符 - 命名空间的相对导入

    Python 包 : relative imports

    python - python 3.x 逻辑中的奇怪语法错误

    python + 如何在 python 中验证 linux 命令是否成功

    c++ - 如何使用最少的样板文件实现参数化类模板