代码之家  ›  专栏  ›  技术社区  ›  Géry Ogam

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

  •  1
  • Géry Ogam  · 技术社区  · 5 年前

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

    __包装__ 属性将设置为 None . 为了在直接执行模块时允许相对导入,样板类似于

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

    请注意,仅当顶层包已通过 sys.path . 操作的附加代码 搜索路径 可能需要 直接执行命令在没有顶层包的情况下工作

    这种方法也有与使用绝对值相同的缺点 包或子包,样板需要更新 手动操作。它的优点是只需进行一次更改 每个文件,不管相对导入的数量。

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

    • 目录布局:

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

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

    样板在执行子模块时起作用棒.py从文件系统(的 PYTHONPATH 修改使包foo/accessible在 搜索路径 ):

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

    在执行子模块时,样板也可以工作棒.py从模块命名空间:

    python3 -m foo.bar
    

    但是,下面的替代样板在这两种情况下的效果与棒.py子模块:

    if __package__:
        from . import baz
    else:
        import baz
    

    "foo" ).

    1. 是第一个子表达式 __name__ == "__main__" 是必需的还是已经被第二个子表达式暗示了 __package__ is None ?
    2. 不应该是第二个子表达式 __包\uuu为无 not __package__ 相反,为了处理这个案子 __包装__ 是空字符串(就像 __main__.py 通过提供包含目录从文件系统执行的子模块: PYTHONPATH=$(pwd) python3 foo/
    0 回复  |  直到 5 年前
        1
  •  4
  •   Géry Ogam    5 年前

    正确的样板是none,只需编写显式的相对导入,并在有人试图将模块作为脚本运行或 sys.path

    from . import baz
    

    pep366中给出的样板文件只是为了证明提议的更改足以让用户进行 *工作如果他们真的愿意,这并不意味着让直接执行工作是一个好主意(事实并非如此,这是一个坏主意,几乎不可避免地会导致其他问题,即使是政治公众人物的样板)。

    您建议的替代样板重现了Python2中隐式相对导入引起的问题: "baz" 模块导入为 baz __main__ ,但将作为 "foo.baz" sys.modules 以不同的名字。

    在其他问题中,这意味着如果其他模块抛出 foo.baz.SomeException __主__ baz.SomeException ,它不会工作,因为这将是来自两个不同模块的两个不同的异常对象。

    相比之下,如果使用PEP样板,那么 __主__ 将正确导入 巴兹 作为 ,您唯一需要担心的是其他可能导入的模块 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. A 论据( python -m <module name> ).
    3. 进口声明( import <module name>

    1. 是第一个子表达式 __name__ == "__main__" __package__ is None

    很难得到 除了 模块采用现代进口系统。但它以前更常见,因为它不是由导入系统在模块加载时设置的, __package__ __包\uuuu不是 用于 暗示直接执行 (上面的第7种情况),所以要过滤掉情况7中的子表达式 __名称\¨¨¨主\” (上文案例1至6)是必要的。

    1. 不应该是第二个子表达式 __包\uuuu不是 not __package__ 相反,为了处理这个案子 __包装__ __main__.py 子模块从执行 提供包含目录的文件系统: PYTHONPATH=$(pwd) python3 foo/

    直接执行 工作(案例1到4以上),它不试图让其他口味 搜索路径