一起使用 Argparse 和 Json



我是Python的初学者。

我想知道Argparse和JSON是否可以一起使用。比如说,我有变量p,q,r

我可以将它们添加到参数解析中,作为 -

parser.add_argument('-p','--param1',help='x variable', required=True)
parser.add_argument('-q','--param2',help='y variable', required=True)
parser.add_argument('-r','--param3',help='z variable', required=True)

现在假设我想从 JSON 文件中读取相同的变量,可以这样做吗?所以我可以从命令行或 JSON 文件输入值。

JSON 输入文件 -

{
    "testOwner": "my name",
    "tests": [
        "test1",
        "test2",
        "test3"
    ],
    "testParameters": {
        "test1": {
            "param1": "0",
            "param2": "20",
            "param3" : "True"
        },
        "test2": {
            "param1": "cc"
        }
    }   
}

parse_args 中的 args 命名空间可以转换为字典,如下所示:

argparse_dict = vars(args)

JSON值也在字典中,比如json_dict。 您可以将所选值从一个字典复制到另一个字典,或执行整个刻度更新:

argparse_dict.update(json_dict)

这样,json_dict值就会覆盖 argparse 值。

如果要保留两者,则需要具有不同的参数(键(名称,或者值必须是列表,可以追加或扩展列表。 这需要更多的工作,首先在 argparse 中使用正确的nargs值。


修订后的parser生成,并带有测试输入:

In [292]: args=parser.parse_args('-p one -q two -r three'.split())
In [293]: args
Out[293]: Namespace(param1='one', param2='two', param3='three')
In [295]: args_dict = vars(args)    
In [296]: args_dict
Out[296]: {'param1': 'one', 'param2': 'two', 'param3': 'three'}

JSON 字符串在解析时(json.loads?(会产生如下字典:

In [317]: json_dict
Out[317]: 
{'testOwner': 'my name',
 'testParameters': {'test1': {'param1': '0', 'param2': '20', 'param3': 'True'},
  'test2': {'param1': 'cc'}},
 'tests': ['test1', 'test2', 'test3']}

我通过将你的显示粘贴到我的 Ipython 会话中来制作这个,但我认为 JSON 加载器会产生同样的东西

参数解析值可以添加为:

In [318]: json_dict['testParameters']['test3']=args_dict
In [319]: json_dict
Out[319]: 
{'testOwner': 'my name',
 'testParameters': {'test1': {'param1': '0', 'param2': '20', 'param3': 'True'},
  'test2': {'param1': 'cc'},
  'test3': {'param1': 'one', 'param2': 'two', 'param3': 'three'}},
 'tests': ['test1', 'test2', 'test3']}

在这里,我将其添加为第 3 个test集,(偶然(从tests列表中取一个名称。 json_dict['testParameters']['test2']=args_dict将替换 test2 的值。

将 args 值添加到 'test2' 的未定义值的一种方法是:

In [320]: args_dict1=args_dict.copy()    
In [322]: args_dict1.update(json_dict['testParameters']['test2'])
In [324]: json_dict['testParameters']['test2']=args_dict1
In [325]: json_dict
Out[325]: 
{'testOwner': 'my name',
 'testParameters': {'test1': {'param1': '0', 'param2': '20', 'param3': 'True'},
  'test2': {'param1': 'cc', 'param2': 'two', 'param3': 'three'},
  'test3': {'param1': 'one', 'param2': 'two', 'param3': 'three'}},
 'tests': ['test1', 'test2', 'test3']}

我使用此版本的update来优先考虑 JSON 字典中的"cc"值。

事实证明,以下警告非常简单

  1. 安装程序使用命令行上的值覆盖配置文件中的值
  2. 仅当尚未在命令行或设置文件中设置选项时,它才使用默认值
  3. 它不会检查配置文件中的设置是否有效
import argparse
import json
parser = argparse.ArgumentParser()
parser.add_argument('--save_json',
    help='Save settings to file in json format. Ignored in json file')
parser.add_argument('--load_json',
    help='Load settings from file in json format. Command line options override values in file.')
args = parser.parse_args()
if args.load_json:
    with open(args.load_json, 'rt') as f:
        t_args = argparse.Namespace()
        t_args.__dict__.update(json.load(f))
        args = parser.parse_args(namespace=t_args)
# Optional: support for saving settings into a json file
if args.save_json:
    with open(args.save_json, 'wt') as f:
        json.dump(vars(args), f, indent=4)

假设您的 JSON 文件包含以下形式的字典:

d = {"name": ["-x", "--xvar"], "help": "Help message", "required": True}

创建解析器后,您可以像这样"解压缩"字典:

parser = argparse.ArgumentParser()
parser.add_argument(*(d.pop("name")), **d) 
# Put the 'name' as name/flag and then unpack the rest of
# the dict as the rest of the arguments
parser.parse_args("--xvar 12".split())
>>> Namespace(xvar='12')

但是,这会强制您维护字典键以适合方法add_arguments的参数名称。你也没有一种简单/直接的方式来使用更高级的行为,比如使用actiontypechoices参数。

此外,您还必须更改字典的形式以包含要使用的各种参数。一种解决方案是将名称/标志作为元组中字典的键,参数将是字典:

d = {("-x", "--xvar"): {"help": "Help message for x", "required": True}, 
     ("-y", "--yvar"): {"help": "Help message for y", "required": True}}
for names, args in d.iteritems():
    parser.add_argument(*names, **args) # Use a similar unpacking 'magic' as the first example
parser.parse_args("-x 12 --yvar 42".split())
>>> Namespace(xvar='12', yvar='42')

编辑鉴于 OP 的评论,看起来他想要解析从 JSON 文件中获取的值。

d = {"-x": "12", "-y": "42"}
args = []
for item in d.items():
    args.extend(item)
parser.parse_args(args)
>>> Namespace(xvar='12', yvar='42')

编辑 2

查看argparse文档,这一段可能有些相关。

这是defaults.json

{
    "param1": "from json",
    "param2": "from json"
}

这是args.py

import argparse
from pathlib import Path
import json
json_text = Path('defaults.json').read_text()
args = argparse.Namespace(**json.loads(json_text))
parser = argparse.ArgumentParser()
parser.add_argument('--param1', default='from default')
parser.add_argument('--param2', default='from default')
parser.add_argument('--param3', default='from default')
args = parser.parse_args(namespace=args)
print(args)

运行它会给出以下输出

python args.py --param2 'from par'
Namespace(param1='from json', param2='from par', param3='from default')

这里的一些答案是有限的,因为它们既不验证输入,也不将它们转换为预期的类型。一个简单的解决方案是构造一个字符串列表,供argparse[重新]解析。

如果您的配置文件很简单(由标志和单值选项组成(,则可以执行以下操作:

import argparse
import functools
import json
import operator

js = '{ "class": 10, "no_checksum": "True", "my_string": "Jesus is Lord"}'
da = json.loads(js)
parser = argparse.ArgumentParser()
parser.add_argument('--no_checksum', action='store_true')
parser.add_argument('--class', type=int)
parser.add_argument('--my_string')
pairs = [ [f"--{k}", str(v)] if not v=='True' else [f"--{k}"] for k,v in da.items()]
argv = functools.reduce(operator.iadd, pairs, [])
parser.parse_args(argv)

这使用列表推导来构建读取 JSON 字典中的选项和字符串化(如有必要(值的列表。任何设置为 "True" 的选项都会在没有参数的情况下传递(这是针对标志的(;请注意,此代码不处理"False"值。(如果需要,请使用 v in ('True', 'False') 而不是 v=='True' 。生成的pairs值是列表列表(成对或单个标志(;这必须被展平(即删除嵌套(argparse,这就是functools.reduce(operator.iadd, pairs, [])的用途——它迭代和累积地将增量添加操作应用于列表,将所有子列表连接成一个大列表。([]初始值在那里,以防pairs结果为空,否则会中断reduce

结果是:

Namespace(class=10, my_string='Jesus is Lord', no_checksum=True, test=None)

如果您的配置文件包含列表,则代码会稍微复杂一些:

js = '{ "class": 10, "no_checksum": "True", "evangelists": [ "Matthew", "Mark", "Luke", "John"], "my_string": "Jesus is Lord"}'
da = json.loads(js)
parser.add_argument('--evangelists', nargs='*')
pairs = [ functools.reduce(operator.iadd, [[f"--{k}"], [str(v)] if not isinstance(v,list) else list(map(str,v))]) if not v=='True' else [f"--{k}"] for k,v in da.items()]
argv = functools.reduce(operator.iadd, pairs, [])
parser.parse_args(argv)

这将前面的代码扩展到 1( 将列表项转换为字符串,如果它们还不是字符串(list(map(str,v)) ,它将str内置函数应用于 v 的所有元素(;2( 扁平化内部列表值。

结果:

Namespace(class=10, my_string='Jesus is Lord', no_checksum=True, evangelists=['Matthew', 'Mark', 'Luke', 'John'])

如果你的参数文件比这更复杂,你可能不应该使用argparse,我认为。此解决方案确实有一个限制,因为它可能无法正确验证某些极端情况。