运行 带有 exec 的脚本时如何将 args 隐藏到 argparse?
How to hide args to argparse when running script with exec?
我有 2 个文件,runner.py
运行 target.py
with subprocess or exec.
它们都有命令行选项。
如果 runner 使用 subprocess 运行 target 没关系:
$ python runner.py
run target.py with subprocess...
target.py: running with dummy = False
如果运行器使用 exec 运行目标代码(使用 -e
选项):
$ python runner.py -e
run target.py with exec...
usage: runner.py [-h] [-d]
runner.py: error: unrecognized arguments: -e
命令行参数 -e
是 target.py
代码(仅接受一个 --dummy
选项)的 "seen" 并引发错误。
当 运行 带有 exec 的脚本时,如何将 args 隐藏到 argparse?
代码如下:
runner.py
import subprocess
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-e", "--exec", help="run with exec", action="store_true")
args = parser.parse_args()
target_filename = "target.py"
if args.exec:
print("run target.py with exec...")
source_code = open(target_filename).read()
compiled = compile(source_code, filename=target_filename, mode="exec")
exec(compiled) # OPTION 1 - error on argparse
# exec(compiled, {}) # OPTION 2 - target does not go inside "if main"
# exec(compiled, dict(__name__="__main__")) # OPTION 3 - same error as OPTION 1
else:
print("run target.py with subprocess...")
subprocess.run(["python3", target_filename])
我尝试使用上面的注释选项隐藏全局变量,但没有成功。
似乎与 argparse 的工作方式有关。
target.py
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-d", "--dummy", help="a dummy option", action="store_true")
args = parser.parse_args()
print(f"target.py: running with dummy = {args.dummy}")
有 argparse conflict_handler 选项所以可以写
argparse.ArgumentParser(conflict_handler='resolve')
在目标脚本中。
resolve
删除了冲突的选项,但这并不能很好地处理类似的情况,其中选项在 运行ner 和 target 中具有相同的名称,或者您不能或不想更改目标文件。
这是我找到的解决方案。
在内部 argparse 使用 sys.argv
来检索使用命令行设置的选项。
您可以直接设置 sys.argv = [target_filename]
来删除选项,但是更改 sys
会带来很多其他问题。
使用 unittest.mock.patch
(python3.4+) sys.argv
可以像这样安全地更改:
from unittest.mock import patch
# [...]
source_code = open(target_filename).read()
compiled = compile(source_code, filename=target_filename, mode="exec")
# remove command args
with patch('sys.argv', [target_filename]):
exec(compiled)
所以也可以 运行 目标脚本代码选项:
# run target
with patch('sys.argv', [target_filename]):
exec(compiled)
# run target with -d
with patch('sys.argv', [target_filename, "-d"]):
exec(compiled)
我有 2 个文件,runner.py
运行 target.py
with subprocess or exec.
它们都有命令行选项。
如果 runner 使用 subprocess 运行 target 没关系:
$ python runner.py
run target.py with subprocess...
target.py: running with dummy = False
如果运行器使用 exec 运行目标代码(使用 -e
选项):
$ python runner.py -e
run target.py with exec...
usage: runner.py [-h] [-d]
runner.py: error: unrecognized arguments: -e
命令行参数 -e
是 target.py
代码(仅接受一个 --dummy
选项)的 "seen" 并引发错误。
当 运行 带有 exec 的脚本时,如何将 args 隐藏到 argparse?
代码如下:
runner.py
import subprocess
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-e", "--exec", help="run with exec", action="store_true")
args = parser.parse_args()
target_filename = "target.py"
if args.exec:
print("run target.py with exec...")
source_code = open(target_filename).read()
compiled = compile(source_code, filename=target_filename, mode="exec")
exec(compiled) # OPTION 1 - error on argparse
# exec(compiled, {}) # OPTION 2 - target does not go inside "if main"
# exec(compiled, dict(__name__="__main__")) # OPTION 3 - same error as OPTION 1
else:
print("run target.py with subprocess...")
subprocess.run(["python3", target_filename])
我尝试使用上面的注释选项隐藏全局变量,但没有成功。
似乎与 argparse 的工作方式有关。
target.py
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-d", "--dummy", help="a dummy option", action="store_true")
args = parser.parse_args()
print(f"target.py: running with dummy = {args.dummy}")
有 argparse conflict_handler 选项所以可以写
argparse.ArgumentParser(conflict_handler='resolve')
在目标脚本中。
resolve
删除了冲突的选项,但这并不能很好地处理类似的情况,其中选项在 运行ner 和 target 中具有相同的名称,或者您不能或不想更改目标文件。
这是我找到的解决方案。
在内部 argparse 使用 sys.argv
来检索使用命令行设置的选项。
您可以直接设置 sys.argv = [target_filename]
来删除选项,但是更改 sys
会带来很多其他问题。
使用 unittest.mock.patch
(python3.4+) sys.argv
可以像这样安全地更改:
from unittest.mock import patch
# [...]
source_code = open(target_filename).read()
compiled = compile(source_code, filename=target_filename, mode="exec")
# remove command args
with patch('sys.argv', [target_filename]):
exec(compiled)
所以也可以 运行 目标脚本代码选项:
# run target
with patch('sys.argv', [target_filename]):
exec(compiled)
# run target with -d
with patch('sys.argv', [target_filename, "-d"]):
exec(compiled)