Pyhton Parse

导言:对于一些功能相仿,但是其输入参数并不相同的程序,我们往往在进入main函数时使用同一个parser进行参数的解析。当然可以针对不同的.py文件写出不同的参数读取的入口,但是往往那样并不优雅。使用subparser可以重用接口。

1. parse 以及 subparsers:

parse大家都很熟悉了,用来读取输入main函数的数据。所以本文的重点不是parse,主要落于subparsers
每一个subparser可以继承父类的parse,并使用其参数接口。

1.1 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
# add sub-parser
# 新建一个父parser
parser = argparse.ArgumentParser(add_help=add_help, description='IO Processor')

# 在之后向其中添加子parser
# 这个subparser只是一个句柄,可以向其中添加多个子parser.
# 其中dest 属性是可以让uer在之后能够获得到subparser的当前子parser的名字,相当于别名。
subparsers = parser.add_subparsers(dest='processor')

# 这里的processor是一个dict,装了各种之后会用的不同功能的类。但只是import,并未实例化。节约了内存资源
# subparsers.add_parser(k, parents=[p.get_parser()]) 向子parser句柄中添加parser成员,k是parser的名字,而parents参数是让其获得和之相同的参数接口。
for k, p in processors.items():
subparsers.add_parser(k, parents=[p.get_parser()])

k是键,{"recognition" "demo"},即parser的名称,注意后面使用parse_args()时所输入的子parser器名称必须要和其一样。不然会报错。详细见下面例子
实例:

1
2
3
python3 main.py recognition -h #正确调用,因为parser有名为recognition的parse

python3 main.py sdeqds -h #错误,找不到名为sdeqds子parse。

在terminal使用python进行程序的运行的时候先要指定子解释器的名字,在输入参数。

p是值,包含了两个类。

p.get_parser()可以进入到该类的get_parser方法中

在对应的类中,get_parser都会执行,每一个get_parser都会返回一个parser类,赋值给add_parser中的parents参数。

subparsers = parser.add_subparsers(dest='processor')得到的subparsers可以有多个parser,相当于parser子parsers句柄。向其中加入parser使用add_parser即可

parser.add_subparsers(dest=’processor’)返回的是子parser的句柄!!

使用add_parser()向subparsers中加入parser

add_parser(“名字”,parent = “parser”)。

子类中的parser的构造

recognition类的get_parser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def get_parser(add_help=False):

# parameter priority: command line > config > default
parent_parser = Processor.get_parser(add_help=False)
parser = argparse.ArgumentParser(
add_help=add_help,
parents=[parent_parser],
description='Spatial Temporal Graph Convolution Network')
# parents=[parent_parser]有点类似与克隆。

# region arguments yapf: disable
# evaluation
parser.add_argument('--show_topk', type=int, default=[1, 5], nargs='+', help='which Top K accuracy will be shown')
# optim
parser.add_argument('--base_lr', type=float, default=0.01, help='initial learning rate')
parser.add_argument('--step', type=int, default=[], nargs='+', help='the epoch where optimizer reduce the learning rate')
parser.add_argument('--optimizer', default='SGD', help='type of optimizer')
parser.add_argument('--nesterov', type=str2bool, default=True, help='use nesterov or not')
parser.add_argument('--weight_decay', type=float, default=0.0001, help='weight decay for optimizer')
# endregion yapf: enable

return parser

调用处:subparsers.add_parser(k, parents=[p.get_parser()])
返回了一个parser
这个parser继承了Processor的parser,并且添加了自己的参数。

各个类的父子关系:

1
2
3
|IO----|----demo
|
|----processor----|----REC_Processor

subparser的作用可以复用相同的参数接口

所有parser写完后调用根parse进行解析:

  1. parser = argparse.ArgumentParser(description='Processor collection')

  2. subparsers = parser.add_subparsers(dest='processor'),添加子解析器

  3. arg = parser.parse_args(),开启解析,定义了所有参数之后,你就可以给 parse_args() 传递一组参数字符串来解析命令行。默认情况下,参数是从 sys.argv[1:] 中获取,但你也可以传递自己的参数列表。选项是使用GNU/POSIX语法来处理的,所以在序列中选项和参数值可以混合。

parse_args() 的返回值是一个命名空间,包含传递给命令的参数。该对象将参数保存其属性,因此如果你的参数 destmyoption,那么你就可以args.myoption来访问该值。

  1. 可以自己向parse_args中传递参数:
    1
    parser.parse_args(['-a', '-bval', '-c', '3'])

如果不parse_args()不加参数则是默认从sys.argv[1:]来传入

argparse.add_argument() dest参数的意义:

subparsers = parser.add_subparsers(dest='processor')
dest指定的值用作key值,从解析后的对象中取出用户输入的第一个参数
所以上述的parser拥有一个arg.processor的属性。而这个属性对应了cmd中第一个输入值,也等于sys.argv[1]

add_argument()函数中参数的定义以及作用:

参数名称:

add_argument()必须知道参数是可选的还是必须的位置参数,第一个传递给add_arguments的参数必须是可选参数或者是位置参数,例如,下面是可选参数。

1
2
3
4
...
parser.add_argument('--use_gpu', type=str2bool, default=True, help='use GPUs or not')
parser.add_argument('bar') #位置参数,必须给出
...

  • 其中双横线--代表不能省略,单横线-代表简写名字,别名

type参数:

  • type表明该参数的类型,可以使用函数进行自定义类型的搭建:
    1
    2
    3
    4
    5
    6
    7
    8

    def str2bool(v):
    if v.lower() in ('yes', 'true', 't', 'y', '1'):
    return True
    elif v.lower() in ('no', 'false', 'f', 'n', '0'):
    return False
    else:
    raise argparse.ArgumentTypeError('Boolean value expected.')

其中的返回值如果有限制必须要返回到限制域中,不在限制里的其他输入需要进行异常处理。

action参数:

当输入参数需要输入列表、字典类型时可以使用action进行传递:

store_const:

store_const:存储const指定的值。

1
2
3
4
5
6

parser=argparse.ArgumentParser()
parser.add_argument('--foo',action='store_const',const=42)
parser.parse_args('--foo'.split())

# Namespace(foo=42)

append

1
2
3
4
5

parser = argparse.ArgumentParser()
parser.add_argument('--foo', action='append')
parser.parse_args('--foo 1 --foo 2'.split())
Namespace(foo=['1','2'])

append:保存为列表格式,将每个参数的值添加到这个列表。

自定义Action类:

在argparse中使用Action自定义类是为了可以让输入参数的类型自定义化,可以让其输入列表、字典等参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# /processor.py get_parser()
parser.add_argument('--model_args', action=DictAction, default=dict(), help='the arguments of model')
...

# /torchlight/io.py
class DictAction(argparse.Action):
def __init__(self, option_strings, dest, nargs=None, **kwargs):
if nargs is not None:
raise ValueError("nargs not allowed")
super(DictAction, self).__init__(option_strings, dest, **kwargs)

def __call__(self, parser, namespace, values, option_string=None):
input_dict = eval('dict({})'.format(values)) #pylint: disable=W0123
output_dict = getattr(namespace, self.dest)
for k in input_dict:
output_dict[k] = input_dict[k]
setattr(namespace, self.dest, output_dict)
...

可以通过继承Action类来实现自定义action类型,通过继承argparse.action,并提供call()方法,提供四个参数。

  • parser:ArgumentParser对象。
  • namespace:parse_args()返回的命名空间。
  • values:相关联的命令行参数
  • option_string:可选字符串,用来触发action,如果没有指定,就通过位置参数来关联。

__call__(self, parser, namespace, values, option_string=None) 中使用setattr(namespace, self.dest, output_dict)来进行赋值。self.dest指代的是调用call的那个parser。