如何以特定顺序验证特定参数

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_ 的做法。

方法

  1. 使用 parse_config 字典中的字典为 <category> <verb> 创建条目,例如 user info
  2. user infoparse_config 可以在 parse_config['user']['info'] 中找到并且包含字段 desc(ription)mainprepare_parser
  3. main 是一个可调用对象(即函数),它在脚本的最后一行被调​​用,如下所示:sys.exit(args.main(args))。为此忽略 sys.exit(..)args.main 是一个函数,它在解析从命令行给出的参数后将根据给定的参数指向正确的入口点。它的工作原理是使用 subparser.set_defaults(main=main_for_this_subparser)。这意味着如果这个特定的子解析器被激活,那么 args.main 指向 main_for_this_subparser 可以是任何函数。
  4. prepare_parser也是一个函数,可以自定义为每个命令添加更多的参数。由于 user 总是需要 <user name> 作为第一个参数,我对所有三个动词都使用了 def parser_user_default(parser): parser.add_argument('user_name') (info , 创建, 删除)。由于 create 需要更多的参数,这会得到一个特殊的 parser_user_create 函数,其中首先调用 parser_user_default,然后添加更多的参数。

进一步阅读

进一步说明

到目前为止,我的解决方案无法处理调用 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=[])