Python:参数解析验证最佳实践

AK4*_*K47 15 python validation argparse

是否可以在解析参数时使用argparse模块添加验证?

from argparse import ArgumentParser

parser = ArgumentParser(description='Argument parser for PG restore')

parser.add_argument('--database', dest='database',
                    default=None, required=False, help='Database to restore')

parser.add_argument('--backup', dest='backup',
                    required=True, help='Location of the backup file')

parsed_args = parser.parse_args()
Run Code Online (Sandbox Code Playgroud)

是否可以在此参数解析器中添加验证检查,以确保备份文件/数据库存在?而不是必须在此之后为每个参数添加额外的检查,例如:

from os.path import exists
if not database_exists(parsed_args.database):
    raise DatabaseNotFoundError
if not exists(parsed_args.backup):
    raise FileNotFoundError
Run Code Online (Sandbox Code Playgroud)

hpa*_*ulj 16

argparse.FileType是一个type可以打开文件的工厂类,当然,如果文件不存在或无法创建,则会在此过程中引发错误.您可以查看其代码,了解如何创建自己的类(或函数)来测试输入.

参数type参数是一个可调用的(函数等),它接受一个字符串,根据需要对其进行测试,并将其(根据需要)转换为您要保存到args命名空间的值.所以它可以做任何你想要的测试.如果type引发错误,则解析器会创建错误消息(和用法)并退出.

现在,这是否适合进行测试取决于您的情况.有时打开文件FileType很好,但是你必须自己关闭它,或者等待程序结束.您不能在with open(filename) as f:上下文中使用该打开文件.这同样适用于您的数据库.在复杂的程序中,您可能不想立即打开或创建文件.

我为Python bug /问题写了一个变体FileType,创建了context一个可以在with上下文中使用的对象.我还使用os测试来检查文件是否存在或是否可以创建,而实际上并没有这样做.但是,需要进一步的技巧,如果filestdin/out,你不想关闭.有时尝试做这样的事情argparse只是工作而不是它的价值.

无论如何,如果你有一个简单的测试方法,你可以将它包装在一个type像这样的简单函数中:

def database(astring):
    from os.path import exists
    if not database_exists(astring):
        raise ValueError  # or TypeError, or `argparse.ArgumentTypeError
    return astring

parser.add_argument('--database', dest='database',
                type = database, 
                default=None, required=False, help='Database to restore')
Run Code Online (Sandbox Code Playgroud)

我不认为你是否在typeor或者这样实施这样的测试很重要Action.我认为这type更简单,更符合开发人员的意图.

  • 很棒的答案!我想建议使用argparse.ArgumentTypeError(message)来在控制台上打印'message'. (2认同)

Ult*_*nct 12

一定!您只需将自定义操作指定为类,然后覆盖__call__(..).链接到文档.

就像是:

import argparse

class FooAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        if values != "bar":
            print "Got value:", values
            raise ValueError("Not a bar!")
        setattr(namespace, self.dest, values)


parser = argparse.ArgumentParser()
parser.add_argument("--foo", action=FooAction)

parsed_args = parser.parse_args()
Run Code Online (Sandbox Code Playgroud)

在你的特殊情况下,我想你会拥有DatabaseActionFileAction(或类似的东西).


hpa*_*ulj 12

通过这个脚本,我可以测试建议的替代方案。

import argparse

class ValidateUrl(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        if values != "bar":
            parser.error(f"Please enter a valid. Got: {values}")
        setattr(namespace, self.dest, values)

class FooAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        if values != "bar":
            print("Got value:", values)
            #raise ValueError("Not a bar!")  # shows a traceback, not usage
            raise argparse.ArgumentError(self, 'Not a bar')
        setattr(namespace, self.dest, values)

def database(astring):
    if astring != "bar":
        #raise argparse.ArgumentTypeError("not a bar")   # sustom message
        raise ValueError('not a bar') # standard error
        # error: argument --data: invalid database value: 'xxx'
    return astring

parser = argparse.ArgumentParser()
parser.add_argument("--url", action=ValidateUrl)
parser.add_argument("--foo", action = FooAction)
parser.add_argument('--data', type = database)

if __name__=='__main__':
    args = parser.parse_args()
    print(args)
Run Code Online (Sandbox Code Playgroud)

一个工作案例:

1254:~/mypy$ python3 stack37471636.py --url bar --foo bar --data bar
Namespace(data='bar', foo='bar', url='bar')
Run Code Online (Sandbox Code Playgroud)

错误

parser.error案例的使用和退出

1255:~/mypy$ python3 stack37471636.py --url xxx
usage: stack37471636.py [-h] [--url URL] [--foo FOO] [--data DATA]
stack37471636.py: error: Please enter a valid. Got: xxx
Run Code Online (Sandbox Code Playgroud)

函数ValueError中来自 a 的标准化消息type

1256:~/mypy$ python3 stack37471636.py --data xxx
usage: stack37471636.py [-h] [--url URL] [--foo FOO] [--data DATA]
stack37471636.py: error: argument --data: invalid database value: 'xxx'
Run Code Online (Sandbox Code Playgroud)

使用 时ArgumentTypeError,消息显示如下:

1246:~/mypy$ python3 stack37471636.py --url bar --foo bar --data xxx
usage: stack37471636.py [-h] [--url URL] [--foo FOO] [--data DATA]
stack37471636.py: error: argument --data: not a bar
Run Code Online (Sandbox Code Playgroud)

FooActionArgumentError

1257:~/mypy$ python3 stack37471636.py --foo xxx
Got value: xxx
usage: stack37471636.py [-h] [--url URL] [--foo FOO] [--data DATA]
stack37471636.py: error: argument --foo: Not a bar
Run Code Online (Sandbox Code Playgroud)

type转换为ArgumentError. 请注意,ArgumentError标识argument. 打电话parser.error不行。

如果FooAction引发ValueError,则显示常规回溯,但不使用。

1246:~/mypy$ python3 stack37471636.py --url bar --foo xxx --data bar
Got value: xxx
Traceback (most recent call last):
  File "stack37471636.py", line 27, in <module>
    args = parser.parse_args()
  File "/usr/lib/python3.8/argparse.py", line 1780, in parse_args
    args, argv = self.parse_known_args(args, namespace)
  File "/usr/lib/python3.8/argparse.py", line 1812, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
  File "/usr/lib/python3.8/argparse.py", line 2018, in _parse_known_args
    start_index = consume_optional(start_index)
  File "/usr/lib/python3.8/argparse.py", line 1958, in consume_optional
    take_action(action, args, option_string)
  File "/usr/lib/python3.8/argparse.py", line 1886, in take_action
    action(self, namespace, argument_values, option_string)
  File "stack37471636.py", line 13, in __call__
    raise ValueError("Not a bar!")
ValueError: Not a bar!
Run Code Online (Sandbox Code Playgroud)

我相信ArgumentErrorArgumentTypeError是首选,或者至少是有意的选择。自动生成的错误使用这些。

通常parser.error在解析后使用,例如导致

1301:~/mypy$ python3 stack37471636.py
Namespace(data=None, foo=None, url=None)
usage: stack37471636.py [-h] [--url URL] [--foo FOO] [--data DATA]
stack37471636.py: error: not a bar
Run Code Online (Sandbox Code Playgroud)