Mag*_*ero 6 python boilerplate relative-import
在PEP 366 -主模块显式相对导入中引入了模块范围变量__package__以允许在子模块中显式相对导入,有以下摘录:
当主模块由其文件名指定时,该
__package__属性将设置为None. 为了在直接执行模块时允许相对导入,在第一个相对导入语句之前需要类似于以下的样板:Run Code Online (Sandbox Code Playgroud)if __name__ == "__main__" and __package__ is None: __package__ = "expected.package.name"请注意,仅当顶级包已经可以通过
sys.path. 需要额外的操作代码sys.path才能直接执行工作,而无需导入顶级包。这种方法也有与使用兄弟模块的绝对导入相同的缺点——如果脚本被移动到不同的包或子包,样板将需要手动更新。它的优点是每个文件只需进行一次此更改,而不管相关导入的数量如何。
我尝试在以下设置中使用此样板:
目录布局:
foo
??? bar.py
??? baz.py
Run Code Online (Sandbox Code Playgroud)
bar.py 子模块的内容:
if __name__ == "__main__" and __package__ is None:
__package__ = "foo"
from . import baz
Run Code Online (Sandbox Code Playgroud)
当从文件系统执行子模块 bar.py 时,样板工作(PYTHONPATH修改使包 foo/ 可访问sys.path):
PYTHONPATH=$(pwd) python3 foo/bar.py
Run Code Online (Sandbox Code Playgroud)
当从模块命名空间执行子模块 bar.py 时,样板也可以工作:
python3 -m foo.bar
Run Code Online (Sandbox Code Playgroud)
但是,以下替代样板在这两种情况下都与 bar.py 子模块的内容一样有效:
if __package__:
from . import baz
else:
import baz
Run Code Online (Sandbox Code Playgroud)
此外,这个替代样板更简单,当它与子模块 baz.py 一起移动到不同的包时,不需要对子模块 bar.py 进行任何更新(因为它没有对包名称进行硬编码"foo")。
所以这里是我关于 PEP 366 样板的问题:
__name__ == "__main__"必需的还是第二个子表达式已经暗示了它__package__ is None?__package__ is None是not __package__代替,以便处理其中的情况下__package__是空字符串(如在一个__main__.py从通过供应含目录的文件系统执行的子模块:PYTHONPATH=$(pwd) python3 foo/)?正确的样板是无,只需编写显式相对导入,并在有人尝试将模块作为脚本运行或有异常时让异常转义sys.path配置错误时让异常转义:
from . import baz\nRun Code Online (Sandbox Code Playgroud)\nPEP 366 中给出的样板只是为了表明建议的更改足以允许用户使直接执行*工作(如果他们确实愿意),它并不是 \xe2\x80\x99t 旨在表明使直接执行工作是一个好主意(它不是\xe2\x80\x99t,这是一个坏主意,几乎不可避免地会导致其他问题,即使使用来自 PEP 的样板)。
\n您提出的替代样板文件重现了由 Python 2 中的隐式相对导入引起的问题:模块作为from"baz"导入,但会像其他地方一样导入,因此最终会以不同的名称获得两个副本。baz__main__"foo.baz"sys.modules
除其他问题外,这意味着如果其他模块抛出异常foo.baz.SomeException并且您的__main__模块尝试捕获baz.SomeException,它将\xe2\x80\x99无法工作,因为它们将是来自两个不同模块的两个不同的异常对象。
相比之下,如果您使用 PEP 样板,则将__main__正确导入baz为"foo.baz",您唯一需要担心的是其他模块可能导入foo.bar。
如果您想要更简单的样板文件,明确防止“无意中以不同名称创建同一模块的两个副本”错误,而无需对包名称进行硬编码,那么您可以使用以下命令:
\nif not __package__:\n raise RuntimeError(f"{__file__} must be imported as a package submodule")\nRun Code Online (Sandbox Code Playgroud)\n但是,如果您打算这样做,您也可以from . import baz按照上面的建议无条件地执行,并且如果有人尝试直接运行脚本而不是通过开关运行脚本,则让底层异常逃脱-m。
*直接执行意味着执行代码:
\npython <file path>) 除外。-c论点(python -c <code>)。python)。python < <file path>)。间接执行意味着执行以下位置的代码:
\npython <directory or zip file path>)。-m论点(python -m <module name>)。import <module name>)现在具体回答一下大家的问题:
\n\n\n\n
\n- 第一个子表达式是
\n__name__ == "__main__"必要的还是第二个子表达式已经隐含了__package__ is None?
除了具有现代导入系统的模块__package__ is None之外,很难找到任何其他地方。__main__但它过去更常见,因为不是由导入系统在模块加载时设置,__package__而是由模块中执行的第一个显式相对导入延迟设置。换句话说,样板只是试图让直接执行工作(上面的情况 1 到 4),但__package__ is None 用于暗示直接执行或 import 语句(上面的情况 7),因此要过滤掉 case 7 的子表达式__name__ == "__main__"(情况 1 到 6)上面)是必要的。
\n\n\n
\n- \n
__package__ is None\xe2\x80\x99t是否应该改为第二个子表达式not __package__,以便处理__package__是 空字符串的情况(例如在__main__.py通过提供包含目录来从文件系统执行的子模块中:\nPYTHONPATH=$(pwd) python3 foo/)?
不,因为样板文件只是试图让直接执行工作(上面的情况 1 到 4),它并不是\xe2\x80\x99 试图让其他类型的错误sys.path配置默默地通过。