当使用python
显式调用脚本时,argv
被破坏,因此argv[0]
是正在运行的脚本的路径。如果作为python foo/bar.py
甚至python -m foo.bar
调用,则就是这种情况。
我需要一种方法来恢复原始argv
(即python
收到的)。不幸的是,这并不像在sys.argv
前面加上sys.executable
那么容易,因为python foo/bar.py
与python -m foo.bar
不同(隐式PYTHONPATH
不同,这可能至关重要,具体取决于您的模块结构)。
更具体地说,在python foo/bar.py some other args
和python -m foo.bar some other args
的情况下,我希望分别恢复['python', 'foo/bar.py', 'some', 'other', 'args']
和['python', '-m', 'foo.bar', 'some', 'other', 'args']
。
我知道之前关于这个问题的问题:
- 如何在 python 中获取 ORIGINAL 命令行? 带有空格、制表符等
- 键入的完整命令行
但这些似乎对贝壳的工作原理存在误解,答案反映了这一点。我对撤消 shell 的工作不感兴趣(例如,评估的 shell vars 和函数很好),我只想了解提供给python
的原始argv
。
我找到的唯一解决方案是使用/proc/<PID>/cmdline
:
import os
with open("/proc/{}/cmdline".format(os.getpid()), 'rb') as f:
original_argv = f.read().split(' ')[:-1]
这确实有效,但它仅适用于Linux(没有OSX,Windows支持似乎需要安装wmi软件包)。幸运的是,对于我当前的用例,此限制很好。但是,最好有一个更干净的跨平台方法。
事实上,这种/proc/<PID>/cmdline
方法有效,这给了我希望 python 在运行脚本之前不会执行(至少不是 syscall exec,但可能是内置的exec
)。我记得在某处读到所有这些参数处理(例如。-m
) 是用纯 python 完成的,而不是 C(这得到了证实,python -m this.does.not.exist
将产生一个看起来像来自运行时的异常)。所以,我冒昧地猜测,在纯python的某个地方,原始argv
是可用的(也许这需要通过运行时初始化进行一些探索?
tl;dr是否有一种跨平台(最好是内置的)方法来获取传递给python
的原始argv
(在它删除python
可执行文件并将-m blah
转换为blah.py
之前)?
编辑从洞穴探险中,我发现了Py_GetArgcArgv
,可以通过ctypes访问(在这里找到它,链接到几个提到这种方法的SO帖子):
import ctypes
_argv = ctypes.POINTER(ctypes.c_wchar_p)()
_argc = ctypes.c_int()
ctypes.pythonapi.Py_GetArgcArgv(ctypes.byref(_argc),
ctypes.byref(_argv))
argv = _argv[:_argc.value]
print(argv)
现在这是操作系统可移植的,但不是 python 实现可移植的(仅适用于 cpython,如果你不需要它ctypes
很糟糕)。另外,奇怪的是,我在 Ubunutu 16.04 上没有得到正确的输出(python -m foo.bar
给了我['python', '-m', '-m']
),但我可能只是犯了一个愚蠢的错误(我在 OSX 上得到了相同的行为)。拥有一个完全便携式的解决方案(不会深入研究ctypes
)会很棒。
Python3.10 添加了sys.orig_argv
,文档将其描述为最初传递给 Python 可执行文件的参数。如果这不是您要查找的,则在这种或类似情况下可能会有所帮助。
考虑了很多可能性,包括更改sys.argv
,但我认为这是明智地选择为最有效和无破坏性的选择。
这似乎是XY问题,并且您正在进入杂草以适应一些现有的复杂测试设置(我已经在您的评论中找到了问题背后的问题)。 进一步的努力最好花在编写一个健全的测试设置上。
- 使用更好的测试运行程序,而不是单元测试。
- 在进入 Python 运行时之前,在测试设置中创建任何初始状态,而不是在外部环境中创建。 使用
- 插件进行随机化和种子的东西,我个人使用这个,但还有其他插件。
例如,如果您决定使用 pytest 运行器,则可以在pyproject.toml
文件的[tool.pytest.ini_options]
部分中和/或使用conftest.py
中定义的夹具配置所有测试设置。 覆盖默认测试配置可以使用环境变量和/或命令行参数来完成,这些方法都不会被 shell 或 Python 解释器启动期间弄乱。
执行测试套件的方式可以而且应该像执行单个命令一样简单:
pytest
然后,您认为需要恢复原始sys.argv
的问题将消失。
你陈述的问题是:
- 用户使用环境变量和参数调用我的应用。
- 我想显示一个"像这样运行"诊断,它将完全重现当前运行的结果。
至少有两种解决方案:
- 放弃"复制"方面,因为原始的bash调用命令丢失到可移植的python应用程序中,而是追求"相同的效果"。
- 使用包装器捕获原始调用命令,如让-弗朗索瓦·法布尔所建议的那样。
对于(1),你愿意接受['-m','foo']变成['foo.py'],甚至把它变成['/some/dir/foo.py'],以防PYTHONPATH可能造成麻烦。将 ['a', 'b c'] 显示为"a" "b c"
,或更简洁地显示为a "b c"
,很简单。 如果像 SEED 这样的环境变量是命令行界面的重要组成部分,那么你也需要迭代 envp 并输出它们。对于真正的可重现性,您可以选择将输入参数转换为规范形式,与观察到的输入参数进行比较,如果它们不相同,则使用规范形式执行,因此无法使用"奇数"语法执行大部分代码。
使用 (2) 您可以将应用程序埋在一些不方便命名的文件中,广泛宣传包装程序,并享受在被咀嚼之前看到 args 的好处。