如何以特定顺序验证特定参数
How to validate specific arguments in a specific order
我希望能够验证传递给脚本的参数,以便我可以使用以下选项:
test.py user info <user name>
test.py user create <user name> <last name> <first name> <email>
test.py user create batch <file name>
test.py user delete <user name>
test.py room info <room number>
test.py room create <room number> <room name>
test.py room delete <room number
我目前有一个非常糟糕的级联 if, elif, else
:
if len(sys.argv) >= 2:
if (sys.argv[1]).lower() == "user":
if len(sys.argv) >= 3:
if (sys.argv[2]).lower() == "info":
if len(sys.argv) >= 4:
username = sys.argv[3]
get_user_info(username)
else:
print("username not specified")
我什至不打算添加 elif 和 else 语句。
我读到过使用 argparse 是执行此操作的更好方法,但我只是不知道如何以满足我需要的方式实现它。
就像现在一样,如果有人只执行 test.py
文件本身,它会打印出有效参数。然后,如果他们自己执行 test.py user
,它会为他们提供下一组有效参数,依此类推。一旦到达 ,就不会验证这些。
请帮忙!提前致谢。
这是一个使用 argparse 的解决方案。它的灵感来自 _catkin_tools_ 的做法。
方法
- 使用
parse_config
字典中的字典为 <category> <verb>
创建条目,例如 user info
。
user info
的 parse_config
可以在 parse_config['user']['info']
中找到并且包含字段 desc(ription)
、main
和 prepare_parser
。
main
是一个可调用对象(即函数),它在脚本的最后一行被调用,如下所示:sys.exit(args.main(args))
。为此忽略 sys.exit(..)
。 args.main
是一个函数,它在解析从命令行给出的参数后将根据给定的参数指向正确的入口点。它的工作原理是使用 subparser.set_defaults(main=main_for_this_subparser)
。这意味着如果这个特定的子解析器被激活,那么 args.main
指向 main_for_this_subparser
可以是任何函数。
prepare_parser
也是一个函数,可以自定义为每个命令添加更多的参数。由于 user 总是需要 <user name>
作为第一个参数,我对所有三个动词都使用了 def parser_user_default(parser): parser.add_argument('user_name')
(info , 创建, 删除)。由于 create 需要更多的参数,这会得到一个特殊的 parser_user_create
函数,其中首先调用 parser_user_default
,然后添加更多的参数。
进一步阅读
documentation of set_defaults 有帮助:
Parser-level defaults can be particularly useful when working with multiple parsers. See the add_subparsers() method for an example of this type.
Catkin-Tools 代码非常有用(而且很聪明):
setup.py
: https://github.com/catkin/catkin_tools/blob/master/setup.py
- 具有所有参数准备的主要入口点:https://github.com/catkin/catkin_tools/blob/master/catkin_tools/commands/catkin.py
- 示例
prepare_parser
/prepare_arguments
catkin build
:https://github.com/catkin/catkin_tools/blob/master/catkin_tools/verbs/catkin_build/cli.py#L95
进一步说明
到目前为止,我的解决方案无法处理调用 test.py user create batch <batch file>
。首先,我不认为这实际上是一个好的 CLI,因为用户可能永远不会被称为 "batch"。也许使用 user create --batch <file>
会更好。另一方面,我无法直接使用 argparse。如果您对此进行试验,请考虑 parse_known_args()
,它可以让您将 argparse 与手动参数处理混合使用。
我对我的实施并不完全满意。事后看来,将嵌套字典处理到任何级别的递归实现会更清晰。目前,该实现恰好处理 <category> <verb>
。
完整代码
"""
test.py user info <user name>
test.py user create <user name> <last name> <first name> <email>
test.py user create batch <file name>
test.py user delete <user name>
test.py room info <room number>
test.py room create <room number> <room name>
test.py room delete <room number
"""
from argparse import ArgumentParser
import sys
def main_user_info(args):
print('You called "user info" for user {}'.format(args.user_name))
def main_user_create(args):
print('You called "user create" for : {u}, {last} {first} {mail}'.format(u=args.user_name,
last=args.last_name,
first=args.first_name,
mail=args.email))
def main_user_create_batch(args):
print('User batch creation using file {}'.format(args.filename))
def main_user_delete(args):
pass
def main_room_info(args):
print('Room info called for room number {}'.format(args.room_number))
def main_room_create(args):
print('Creating room number {nr} with name {name}'.format(nr=args.room_number,
name=args.room_name))
def main_room_delete(args):
pass
def parser_user_default(parser):
parser.add_argument('user_name', metavar='<user name>')
return parser
def parser_user_create(parser):
parser = parser_user_default(parser) # argument <user name>
parser.add_argument('last_name', metavar='<last name>')
parser.add_argument('first_name', metavar='<first name>')
parser.add_argument('email', metavar='<email>')
return parser
def parser_room_default(parser):
parser.add_argument('room_number', metavar='<room number>')
# Above: restrict room number to ints: 'type=int'
# Restrict room number to known rooms: 'choices=list_of_known_rooms'
# Restrict room numbers to 1 to 1000: 'choices=range(1, 1001)'
return parser
def parser_room_create(parser):
parser = parser_room_default(parser)
parser.add_argument('room_name', metavar='<room name>')
return parser
parse_config = {
'user': {'info': {'desc': 'Show user info', 'main': main_user_info,
'prepare_parser': parser_user_default},
'create': {'desc': 'Create new user', 'main': main_user_create,
'prepare_parser': parser_user_create},
'delete': {'desc': 'Delete user', 'main': main_user_delete,
'prepare_parser': parser_user_default}
},
'room': {'info': {'desc': 'Show room info', 'main': main_room_info,
'prepare_parser': parser_room_default},
'create': {'desc': 'Show room info', 'main': main_room_create,
'prepare_parser': parser_room_create},
'delete': {'desc': 'Delete a room', 'main': main_room_delete,
'prepare_parser': parser_room_default}
},
}
def prepare_verb_args(parser, category):
subparsers = parser.add_subparsers(title='verb')
for verb, config in parse_config[category].items():
subparser = subparsers.add_parser(verb, description=config['desc'])
subparser = config['prepare_parser'](subparser)
# Reading 'set_defaults' is helpful. Notably, this overwrites default of args.main
# when given this particular category on command line.
subparser.set_defaults(main=config['main'])
return parser
if __name__ == '__main__':
parser = ArgumentParser()
parser.set_defaults(main=lambda _: parser.print_usage())
# Inspired by 'catkin_tools'.
subparsers = parser.add_subparsers(dest='category', title='category')
for c in parse_config.keys():
subparser = subparsers.add_parser(c, description='Act on {}'.format(c))
subparser = prepare_verb_args(subparser, category=c)
# NOTE: Somehow the lambda binds to the last 'subparser' in the loop.
# This is a bug but I haven't got the time to fix it.
subparser.set_defaults(main=lambda x: subparser.print_usage())
args = parser.parse_args()
sys.exit(args.main(args))
您可以使用带有选项的 argparse 来限制前 2 个字符串的输入。其余的字符串更多变,但 if 测试可以利用共性。
import argparse
def lower(astr):
return astr.lower()
parser = argparse.ArgumentParser()
parser.add_argument('first', type=lower, choices=['user', 'room'])
parser.add_argument('second', type=lower, choices=['info','create','delete'])
parser.add_argument('rest', nargs='+')
parser.set_defaults(file=[], user=[], room=[]) # defaults
args = parser.parse_args()
print(args)
# partial parse of the `rest` list of strings:
if args.first in ['user']:
if args.second in ['create']:
if args.rest[0] in ['batch']:
args.files=args.rest[1:]
else:
args.user = args.rest
else:
args.user = args.rest[0]
else:
args.room = args.rest
args.rest=[]
print(args)
帮助:
1553:~/mypy$ python stack50071515.py -h
usage: stack50071515.py [-h] {user,room} {info,create,delete} rest [rest ...]
positional arguments:
{user,room}
{info,create,delete}
rest
optional arguments:
-h, --help show this help message and exit
测试输入:
1545:~/mypy$ python stack50071515.py user info name
Namespace(file=[], first='user', rest=['name'], room=[], second='info', user=[])
Namespace(file=[], first='user', rest=[], room=[], second='info', user='name')
1549:~/mypy$ python stack50071515.py user create a b c
Namespace(file=[], first='user', rest=['a', 'b', 'c'], room=[], second='create', user=[])
Namespace(file=[], first='user', rest=[], room=[], second='create', user=['a', 'b', 'c'])
1549:~/mypy$ python stack50071515.py user create batch file
Namespace(file=[], first='user', rest=['batch', 'file'], room=[], second='create', user=[])
Namespace(file=[], files=['file'], first='user', rest=[], room=[], second='create', user=[])
1550:~/mypy$ python stack50071515.py user delete name
Namespace(file=[], first='user', rest=['name'], room=[], second='delete', user=[])
Namespace(file=[], first='user', rest=[], room=[], second='delete', user='name')
1550:~/mypy$ python stack50071515.py room info number
Namespace(file=[], first='room', rest=['number'], room=[], second='info', user=[])
Namespace(file=[], first='room', rest=[], room=['number'], second='info', user=[])
1550:~/mypy$ python stack50071515.py room create a b
Namespace(file=[], first='room', rest=['a', 'b'], room=[], second='create', user=[])
Namespace(file=[], first='room', rest=[], room=['a', 'b'], second='create', user=[])
1550:~/mypy$ python stack50071515.py room delete 23
Namespace(file=[], first='room', rest=['23'], room=[], second='delete', user=[])
Namespace(file=[], first='room', rest=[], room=['23'], second='delete', user=[])
我希望能够验证传递给脚本的参数,以便我可以使用以下选项:
test.py user info <user name>
test.py user create <user name> <last name> <first name> <email>
test.py user create batch <file name>
test.py user delete <user name>
test.py room info <room number>
test.py room create <room number> <room name>
test.py room delete <room number
我目前有一个非常糟糕的级联 if, elif, else
:
if len(sys.argv) >= 2:
if (sys.argv[1]).lower() == "user":
if len(sys.argv) >= 3:
if (sys.argv[2]).lower() == "info":
if len(sys.argv) >= 4:
username = sys.argv[3]
get_user_info(username)
else:
print("username not specified")
我什至不打算添加 elif 和 else 语句。
我读到过使用 argparse 是执行此操作的更好方法,但我只是不知道如何以满足我需要的方式实现它。
就像现在一样,如果有人只执行 test.py
文件本身,它会打印出有效参数。然后,如果他们自己执行 test.py user
,它会为他们提供下一组有效参数,依此类推。一旦到达 ,就不会验证这些。
请帮忙!提前致谢。
这是一个使用 argparse 的解决方案。它的灵感来自 _catkin_tools_ 的做法。
方法
- 使用
parse_config
字典中的字典为<category> <verb>
创建条目,例如user info
。 user info
的parse_config
可以在parse_config['user']['info']
中找到并且包含字段desc(ription)
、main
和prepare_parser
。main
是一个可调用对象(即函数),它在脚本的最后一行被调用,如下所示:sys.exit(args.main(args))
。为此忽略sys.exit(..)
。args.main
是一个函数,它在解析从命令行给出的参数后将根据给定的参数指向正确的入口点。它的工作原理是使用subparser.set_defaults(main=main_for_this_subparser)
。这意味着如果这个特定的子解析器被激活,那么args.main
指向main_for_this_subparser
可以是任何函数。prepare_parser
也是一个函数,可以自定义为每个命令添加更多的参数。由于 user 总是需要<user name>
作为第一个参数,我对所有三个动词都使用了def parser_user_default(parser): parser.add_argument('user_name')
(info , 创建, 删除)。由于 create 需要更多的参数,这会得到一个特殊的parser_user_create
函数,其中首先调用parser_user_default
,然后添加更多的参数。
进一步阅读
documentation of set_defaults 有帮助:
Parser-level defaults can be particularly useful when working with multiple parsers. See the add_subparsers() method for an example of this type.
Catkin-Tools 代码非常有用(而且很聪明):
setup.py
: https://github.com/catkin/catkin_tools/blob/master/setup.py- 具有所有参数准备的主要入口点:https://github.com/catkin/catkin_tools/blob/master/catkin_tools/commands/catkin.py
- 示例
prepare_parser
/prepare_arguments
catkin build
:https://github.com/catkin/catkin_tools/blob/master/catkin_tools/verbs/catkin_build/cli.py#L95
进一步说明
到目前为止,我的解决方案无法处理调用 test.py user create batch <batch file>
。首先,我不认为这实际上是一个好的 CLI,因为用户可能永远不会被称为 "batch"。也许使用 user create --batch <file>
会更好。另一方面,我无法直接使用 argparse。如果您对此进行试验,请考虑 parse_known_args()
,它可以让您将 argparse 与手动参数处理混合使用。
我对我的实施并不完全满意。事后看来,将嵌套字典处理到任何级别的递归实现会更清晰。目前,该实现恰好处理 <category> <verb>
。
完整代码
"""
test.py user info <user name>
test.py user create <user name> <last name> <first name> <email>
test.py user create batch <file name>
test.py user delete <user name>
test.py room info <room number>
test.py room create <room number> <room name>
test.py room delete <room number
"""
from argparse import ArgumentParser
import sys
def main_user_info(args):
print('You called "user info" for user {}'.format(args.user_name))
def main_user_create(args):
print('You called "user create" for : {u}, {last} {first} {mail}'.format(u=args.user_name,
last=args.last_name,
first=args.first_name,
mail=args.email))
def main_user_create_batch(args):
print('User batch creation using file {}'.format(args.filename))
def main_user_delete(args):
pass
def main_room_info(args):
print('Room info called for room number {}'.format(args.room_number))
def main_room_create(args):
print('Creating room number {nr} with name {name}'.format(nr=args.room_number,
name=args.room_name))
def main_room_delete(args):
pass
def parser_user_default(parser):
parser.add_argument('user_name', metavar='<user name>')
return parser
def parser_user_create(parser):
parser = parser_user_default(parser) # argument <user name>
parser.add_argument('last_name', metavar='<last name>')
parser.add_argument('first_name', metavar='<first name>')
parser.add_argument('email', metavar='<email>')
return parser
def parser_room_default(parser):
parser.add_argument('room_number', metavar='<room number>')
# Above: restrict room number to ints: 'type=int'
# Restrict room number to known rooms: 'choices=list_of_known_rooms'
# Restrict room numbers to 1 to 1000: 'choices=range(1, 1001)'
return parser
def parser_room_create(parser):
parser = parser_room_default(parser)
parser.add_argument('room_name', metavar='<room name>')
return parser
parse_config = {
'user': {'info': {'desc': 'Show user info', 'main': main_user_info,
'prepare_parser': parser_user_default},
'create': {'desc': 'Create new user', 'main': main_user_create,
'prepare_parser': parser_user_create},
'delete': {'desc': 'Delete user', 'main': main_user_delete,
'prepare_parser': parser_user_default}
},
'room': {'info': {'desc': 'Show room info', 'main': main_room_info,
'prepare_parser': parser_room_default},
'create': {'desc': 'Show room info', 'main': main_room_create,
'prepare_parser': parser_room_create},
'delete': {'desc': 'Delete a room', 'main': main_room_delete,
'prepare_parser': parser_room_default}
},
}
def prepare_verb_args(parser, category):
subparsers = parser.add_subparsers(title='verb')
for verb, config in parse_config[category].items():
subparser = subparsers.add_parser(verb, description=config['desc'])
subparser = config['prepare_parser'](subparser)
# Reading 'set_defaults' is helpful. Notably, this overwrites default of args.main
# when given this particular category on command line.
subparser.set_defaults(main=config['main'])
return parser
if __name__ == '__main__':
parser = ArgumentParser()
parser.set_defaults(main=lambda _: parser.print_usage())
# Inspired by 'catkin_tools'.
subparsers = parser.add_subparsers(dest='category', title='category')
for c in parse_config.keys():
subparser = subparsers.add_parser(c, description='Act on {}'.format(c))
subparser = prepare_verb_args(subparser, category=c)
# NOTE: Somehow the lambda binds to the last 'subparser' in the loop.
# This is a bug but I haven't got the time to fix it.
subparser.set_defaults(main=lambda x: subparser.print_usage())
args = parser.parse_args()
sys.exit(args.main(args))
您可以使用带有选项的 argparse 来限制前 2 个字符串的输入。其余的字符串更多变,但 if 测试可以利用共性。
import argparse
def lower(astr):
return astr.lower()
parser = argparse.ArgumentParser()
parser.add_argument('first', type=lower, choices=['user', 'room'])
parser.add_argument('second', type=lower, choices=['info','create','delete'])
parser.add_argument('rest', nargs='+')
parser.set_defaults(file=[], user=[], room=[]) # defaults
args = parser.parse_args()
print(args)
# partial parse of the `rest` list of strings:
if args.first in ['user']:
if args.second in ['create']:
if args.rest[0] in ['batch']:
args.files=args.rest[1:]
else:
args.user = args.rest
else:
args.user = args.rest[0]
else:
args.room = args.rest
args.rest=[]
print(args)
帮助:
1553:~/mypy$ python stack50071515.py -h
usage: stack50071515.py [-h] {user,room} {info,create,delete} rest [rest ...]
positional arguments:
{user,room}
{info,create,delete}
rest
optional arguments:
-h, --help show this help message and exit
测试输入:
1545:~/mypy$ python stack50071515.py user info name
Namespace(file=[], first='user', rest=['name'], room=[], second='info', user=[])
Namespace(file=[], first='user', rest=[], room=[], second='info', user='name')
1549:~/mypy$ python stack50071515.py user create a b c
Namespace(file=[], first='user', rest=['a', 'b', 'c'], room=[], second='create', user=[])
Namespace(file=[], first='user', rest=[], room=[], second='create', user=['a', 'b', 'c'])
1549:~/mypy$ python stack50071515.py user create batch file
Namespace(file=[], first='user', rest=['batch', 'file'], room=[], second='create', user=[])
Namespace(file=[], files=['file'], first='user', rest=[], room=[], second='create', user=[])
1550:~/mypy$ python stack50071515.py user delete name
Namespace(file=[], first='user', rest=['name'], room=[], second='delete', user=[])
Namespace(file=[], first='user', rest=[], room=[], second='delete', user='name')
1550:~/mypy$ python stack50071515.py room info number
Namespace(file=[], first='room', rest=['number'], room=[], second='info', user=[])
Namespace(file=[], first='room', rest=[], room=['number'], second='info', user=[])
1550:~/mypy$ python stack50071515.py room create a b
Namespace(file=[], first='room', rest=['a', 'b'], room=[], second='create', user=[])
Namespace(file=[], first='room', rest=[], room=['a', 'b'], second='create', user=[])
1550:~/mypy$ python stack50071515.py room delete 23
Namespace(file=[], first='room', rest=['23'], room=[], second='delete', user=[])
Namespace(file=[], first='room', rest=[], room=['23'], second='delete', user=[])