当前位置:首页>python>Python 调用时使用了哪些参数?

Python 调用时使用了哪些参数?

  • 2026-08-25 21:16:52
Python 调用时使用了哪些参数?

告别我2018年为Werkzeug提交的第一个大型PR[1] 。现在Python 3.9已经停止维护,我可以移除那个庞大的临时解决方案,并用一行代码取而代之。以下是我所做的更改以及替代方案的说明。

Werkzeug 的重载器必须确定要使用哪些参数来启动新进程,包括 Python 可执行文件、脚本、是否使用 -m 运行以及传递给脚本的参数。

遗憾的是,Python 并不会暴露它以 python -m module 身份运行的事实 it replaces -m module with the path to the module in sys.argv[2]。此外,Python 对 python -m module 和 python file.py 的行为也不同:运行文件会将其目录添加到 sys.path。当使用 python -m werkzeug.serving 运行重载器时,它会在重新加载时调用 python /path/to/werkzeug/serving.py,从而添加了 /path/to/werkzeug to sys.path。这导致与 Werkzeug 中的模块名称发生导入冲突,例如 http,从而覆盖了内置函数。

经过一番使用调试器深入研究内部机制后,我发现可以检测到 Python 是否以 -m 的方式运行,并相应地调整 sys.argv。这花了我几个星期的时间进行调查,因为我发现了越来越多的脚本安装方法和各种特殊情况。

为了确定是否使用了 -m,我查看了 __main__.__package__。__main__[3] 是分配给已运行脚本的模块名称,无论脚本名称是什么,也无论它是否以 -m 的方式运行。就我能想到的所有情况而言,如果 __package__[4] 不为 None,则它是以 -m 的方式运行的;否则,它是以文件的方式运行的。如果我们以 -m 的方式运行,那么可以通过将 sys.argv[0] 中的文件名附加到 __main__.__package__ 来重建模块名称。

除此之外,还有一些其他因素会影响该值。如果脚本是 Windows 系统上通过 pip 安装的入口点,则它是 exe,但不包含 sys.argv 中的扩展名。Python -m a.b 可能指的是模块 a/b.py 或包 a/b/__init__.py,也可能是 a/b/__main__.py。此外,许多 IDE 调试器的后端 pydevd 会错误地将 -m script 重写为 script 而不是 path/to/script.py。

def _get_args_for_reloading() -> list[str]:
    """Determine how the script was executed, and return the args needed
    to execute it again in a new process.
    """
    rv = [sys.executable]
    py_script = sys.argv[0]
    args = sys.argv[1:]
    # Need to look at main module to determine how it was executed.
    __main__ = sys.modules["__main__"]

    # The value of __package__ indicates how Python was called. It may
    # not exist if a setuptools script is installed as an egg. It may be
    # set incorrectly for entry points created with pip on Windows.
    if getattr(__main__, "__package__", None) is None or (
        os.name == "nt"
        and __main__.__package__ == ""
        and not os.path.exists(py_script)
        and os.path.exists(f"{py_script}.exe")
    ):
        # Executed a file, like "python app.py".
        py_script = os.path.abspath(py_script)

        if os.name == "nt":
            # Windows entry points have ".exe" extension and should be
            # called directly.
            if not os.path.exists(py_script) and os.path.exists(f"{py_script}.exe"):
                py_script += ".exe"

            if (
                os.path.splitext(sys.executable)[1] == ".exe"
                and os.path.splitext(py_script)[1] == ".exe"
            ):
                rv.pop(0)

        rv.append(py_script)
    else:
        # Executed a module, like "python -m werkzeug.serving".
        if os.path.isfile(py_script):
            # Rewritten by Python from "-m script" to "/path/to/script.py".
            py_module = t.cast(str, __main__.__package__)
            name = os.path.splitext(os.path.basename(py_script))[0]

            if name != "__main__":
                py_module += f".{name}"
        else:
            # Incorrectly rewritten by pydevd debugger from "-m script" to "script".
            py_module = py_script

        rv.extend(("-m", py_module.lstrip(".")))

    rv.extend(args)
    return rv

虽然“这个脚本是如何被调用的”这个问题的答案看似简单,“只需查看sys.argv”,但实际情况却远比这复杂得多。而且上面的代码仍然不完整,它除了-m之外,并没有捕获arguments that were passed to python[5],例如-u、-X等等。这就是Python 3.10添加sys.orig_argv[6]的原因。它准确地捕获了在任何重写操作之前传递给python的内容。以上所有代码都可以用以下代码更准确地替换:

args = [sys.executable, *sys.orig_argv[1:]]

https://davidism.com/python-args/

引用链接

[1] 我2018年为Werkzeug提交的第一个*大型*PR: https://github.com/pallets/werkzeug/pull/1416
[2] it replaces `-m module` with the path to the module in `sys.argv`: https://docs.python.org/3/using/cmdline.html
#cmdoption-m
[3] `__main__`: https://docs.python.org/3/library/__main__.html#module-__main__
[4] `__package__`: https://docs.python.org/3/reference/datamodel.html#module.__package__
[5] arguments that were passed to `python`: https://docs.python.org/3/using/cmdline.html
[6] `sys.orig_argv`: https://docs.python.org/3/library/sys.html#sys.orig_argv

最新文章

随机文章