标签归档:argparse

Python argparse:默认值或指定值

问题:Python argparse:默认值或指定值

我想有一个可选参数,如果仅存在未指定值的标志,则默认为一个值,但是存储用户指定的值,而不是如果用户指定一个值,则存储默认值。是否已经有可用于此的措施?

一个例子:

python script.py --example
# args.example would equal a default value of 1
python script.py --example 2
# args.example would equal a default value of 2

我可以创建一个动作,但是想查看是否存在执行此操作的方法。

I would like to have a optional argument that will default to a value if only the flag is present with no value specified, but store a user-specified value instead of the default if the user specifies a value. Is there already an action available for this?

An example:

python script.py --example
# args.example would equal a default value of 1
python script.py --example 2
# args.example would equal a default value of 2

I can create an action, but wanted to see if there was an existing way to do this.


回答 0

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--example', nargs='?', const=1, type=int)
args = parser.parse_args()
print(args)

% test.py 
Namespace(example=None)
% test.py --example
Namespace(example=1)
% test.py --example 2
Namespace(example=2)

  • nargs='?' 表示0或1参数
  • const=1 当参数为0时设置默认值
  • type=int 将参数转换为int

如果即使未指定,test.py也要设置example为1 --example,则包括default=1。也就是说,

parser.add_argument('--example', nargs='?', const=1, type=int, default=1)

然后

% test.py 
Namespace(example=1)
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--example', nargs='?', const=1, type=int)
args = parser.parse_args()
print(args)

% test.py 
Namespace(example=None)
% test.py --example
Namespace(example=1)
% test.py --example 2
Namespace(example=2)

  • nargs='?' means 0-or-1 arguments
  • const=1 sets the default when there are 0 arguments
  • type=int converts the argument to int

If you want test.py to set example to 1 even if no --example is specified, then include default=1. That is, with

parser.add_argument('--example', nargs='?', const=1, type=int, default=1)

then

% test.py 
Namespace(example=1)

回答 1

实际上,您只需要使用此脚本中的default参数即可:add_argumenttest.py

import argparse

if __name__ == '__main__':

    parser = argparse.ArgumentParser()
    parser.add_argument('--example', default=1)
    args = parser.parse_args()
    print(args.example)

test.py --example
% 1
test.py --example 2
% 2

详细信息在这里

Actually, you only need to use the default argument to add_argument as in this test.py script:

import argparse

if __name__ == '__main__':

    parser = argparse.ArgumentParser()
    parser.add_argument('--example', default=1)
    args = parser.parse_args()
    print(args.example)

test.py --example
% 1
test.py --example 2
% 2

Details are here.


回答 2

和…之间的不同:

parser.add_argument("--debug", help="Debug", nargs='?', type=int, const=1, default=7)

parser.add_argument("--debug", help="Debug", nargs='?', type=int, const=1)

因此是:

myscript.py =>在第一种情况下,debug是7(默认情况下),在第二种情况下是“ None”

myscript.py --debug =>在每种情况下,调试均为1

myscript.py --debug 2 =>在每种情况下,调试均为2

The difference between:

parser.add_argument("--debug", help="Debug", nargs='?', type=int, const=1, default=7)

and

parser.add_argument("--debug", help="Debug", nargs='?', type=int, const=1)

is thus:

myscript.py => debug is 7 (from default) in the first case and “None” in the second

myscript.py --debug => debug is 1 in each case

myscript.py --debug 2 => debug is 2 in each case


使用argparse获取选定的子命令

问题:使用argparse获取选定的子命令

当我将子命令与python argparse一起使用时,可以获取所选的参数。

parser = argparse.ArgumentParser()
parser.add_argument('-g', '--global')
subparsers = parser.add_subparsers()   
foo_parser = subparsers.add_parser('foo')
foo_parser.add_argument('-c', '--count')
bar_parser = subparsers.add_parser('bar')
args = parser.parse_args(['-g, 'xyz', 'foo', '--count', '42'])
# args => Namespace(global='xyz', count='42')

因此args不包含'foo'sys.argv[1]由于可能存在全局arg,因此简单地编写不起作用。如何获得子命令本身?

When I use subcommands with python argparse, I can get the selected arguments.

parser = argparse.ArgumentParser()
parser.add_argument('-g', '--global')
subparsers = parser.add_subparsers()   
foo_parser = subparsers.add_parser('foo')
foo_parser.add_argument('-c', '--count')
bar_parser = subparsers.add_parser('bar')
args = parser.parse_args(['-g, 'xyz', 'foo', '--count', '42'])
# args => Namespace(global='xyz', count='42')

So args doesn’t contain 'foo'. Simply writing sys.argv[1] doesn’t work because of the possible global args. How can I get the subcommand itself?


回答 0

关于argparse子命令Python文档的最底层介绍了如何执行此操作:

>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-g', '--global')
>>> subparsers = parser.add_subparsers(dest="subparser_name") # this line changed
>>> foo_parser = subparsers.add_parser('foo')
>>> foo_parser.add_argument('-c', '--count')
>>> bar_parser = subparsers.add_parser('bar')
>>> args = parser.parse_args(['-g', 'xyz', 'foo', '--count', '42'])
>>> args
Namespace(count='42', global='xyz', subparser_name='foo')

您也可以使用set_defaults()我发现的示例上方引用的方法。

The very bottom of the Python docs on argparse sub-commands explains how to do this:

>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-g', '--global')
>>> subparsers = parser.add_subparsers(dest="subparser_name") # this line changed
>>> foo_parser = subparsers.add_parser('foo')
>>> foo_parser.add_argument('-c', '--count')
>>> bar_parser = subparsers.add_parser('bar')
>>> args = parser.parse_args(['-g', 'xyz', 'foo', '--count', '42'])
>>> args
Namespace(count='42', global='xyz', subparser_name='foo')

You can also use the set_defaults() method referenced just above the example I found.


回答 1

ArgumentParser.add_subparsersdest正式的说法描述为:

dest-将在其下存储子命令名称的属性的名称;默认情况下None,不存储任何值

在以下使用子解析器的简单任务功能布局的示例中,所选子解析器位于中parser.parse_args().subparser

import argparse


def task_a(alpha):
    print('task a', alpha)


def task_b(beta, gamma):
    print('task b', beta, gamma)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(dest='subparser')

    parser_a = subparsers.add_parser('task_a')
    parser_a.add_argument(
        '-a', '--alpha', dest='alpha', help='Alpha description')

    parser_b = subparsers.add_parser('task_b')
    parser_b.add_argument(
        '-b', '--beta', dest='beta', help='Beta description')
    parser_b.add_argument(
        '-g', '--gamma', dest='gamma', default=42, help='Gamma description')

    kwargs = vars(parser.parse_args())
    globals()[kwargs.pop('subparser')](**kwargs)

ArgumentParser.add_subparsers has dest formal argument described as:

dest – name of the attribute under which sub-command name will be stored; by default None and no value is stored

In the example below of a simple task function layout using subparsers, the selected subparser is in parser.parse_args().subparser.

import argparse


def task_a(alpha):
    print('task a', alpha)


def task_b(beta, gamma):
    print('task b', beta, gamma)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(dest='subparser')

    parser_a = subparsers.add_parser('task_a')
    parser_a.add_argument(
        '-a', '--alpha', dest='alpha', help='Alpha description')

    parser_b = subparsers.add_parser('task_b')
    parser_b.add_argument(
        '-b', '--beta', dest='beta', help='Beta description')
    parser_b.add_argument(
        '-g', '--gamma', dest='gamma', default=42, help='Gamma description')

    kwargs = vars(parser.parse_args())
    globals()[kwargs.pop('subparser')](**kwargs)

Argparse:如果存在“ x”,则必需的参数“ y”

问题:Argparse:如果存在“ x”,则必需的参数“ y”

我的要求如下:

./xyifier --prox --lport lport --rport rport

对于参数prox,我使用action =’store_true’来检查它是否存在。我不需要任何论点。但是,如果设置了–prox,我也需要 rport和lport。有没有一种简单的方法可以使用argparse做到这一点,而无需编写自定义条件编码。

更多代码:

non_int.add_argument('--prox', action='store_true', help='Flag to turn on proxy')
non_int.add_argument('--lport', type=int, help='Listen Port.')
non_int.add_argument('--rport', type=int, help='Proxy port.')

I have a requirement as follows:

./xyifier --prox --lport lport --rport rport

for the argument prox , I use action=’store_true’ to check if it is present or not. I do not require any of the arguments. But, if –prox is set I require rport and lport as well. Is there an easy way of doing this with argparse without writing custom conditional coding.

More Code:

non_int.add_argument('--prox', action='store_true', help='Flag to turn on proxy')
non_int.add_argument('--lport', type=int, help='Listen Port.')
non_int.add_argument('--rport', type=int, help='Proxy port.')

回答 0

不,argparse中没有任何选项可以构成相互包含的选项集。

解决此问题的最简单方法是:

if args.prox and (args.lport is None or args.rport is None):
    parser.error("--prox requires --lport and --rport.")

No, there isn’t any option in argparse to make mutually inclusive sets of options.

The simplest way to deal with this would be:

if args.prox and (args.lport is None or args.rport is None):
    parser.error("--prox requires --lport and --rport.")

回答 1

您是在说要有条件地要求参数。就像@borntyping所说的那样,您可以检查错误并执行parser.error(),或者可以应用与--prox添加新参数时相关的要求。

您的示例的简单解决方案可能是:

non_int.add_argument('--prox', action='store_true', help='Flag to turn on proxy')
non_int.add_argument('--lport', required='--prox' in sys.argv, type=int)
non_int.add_argument('--rport', required='--prox' in sys.argv, type=int)

这种方式required接收True还是False取决于用户是否使用过--prox。这也保证了-lport-rport相互之间的独立行为。

You’re talking about having conditionally required arguments. Like @borntyping said you could check for the error and do parser.error(), or you could just apply a requirement related to --prox when you add a new argument.

A simple solution for your example could be:

non_int.add_argument('--prox', action='store_true', help='Flag to turn on proxy')
non_int.add_argument('--lport', required='--prox' in sys.argv, type=int)
non_int.add_argument('--rport', required='--prox' in sys.argv, type=int)

This way required receives either True or False depending on whether the user as used --prox. This also guarantees that -lport and -rport have an independent behavior between each other.


回答 2

如果存在,如何使用parser.parse_known_args()method然后添加args --lport--rportargs --prox

# just add --prox arg now
non_int = argparse.ArgumentParser(description="stackoverflow question", 
                                  usage="%(prog)s [-h] [--prox --lport port --rport port]")
non_int.add_argument('--prox', action='store_true', 
                     help='Flag to turn on proxy, requires additional args lport and rport')
opts, rem_args = non_int.parse_known_args()
if opts.prox:
    non_int.add_argument('--lport', required=True, type=int, help='Listen Port.')
    non_int.add_argument('--rport', required=True, type=int, help='Proxy port.')
    # use options and namespace from first parsing
    non_int.parse_args(rem_args, namespace = opts)

还请记住,您可以提供opts第一次解析后生成的命名空间,而第二次解析其余参数。这样,最后,在完成所有解析之后,您将拥有一个包含所有选项的命名空间。

缺点:

  • 如果--prox不存在,则命名空间中甚至不存在其他两个从属选项。尽管根据您的用例(如果--prox不存在),则其他选项的发生无关紧要。
  • 需要修改用法消息,因为解析器不知道完整结构
  • --lport并且--rport不显示在帮助消息中

How about using parser.parse_known_args() method and then adding the --lport and --rport args as required args if --prox is present.

# just add --prox arg now
non_int = argparse.ArgumentParser(description="stackoverflow question", 
                                  usage="%(prog)s [-h] [--prox --lport port --rport port]")
non_int.add_argument('--prox', action='store_true', 
                     help='Flag to turn on proxy, requires additional args lport and rport')
opts, rem_args = non_int.parse_known_args()
if opts.prox:
    non_int.add_argument('--lport', required=True, type=int, help='Listen Port.')
    non_int.add_argument('--rport', required=True, type=int, help='Proxy port.')
    # use options and namespace from first parsing
    non_int.parse_args(rem_args, namespace = opts)

Also keep in mind that you can supply the namespace opts generated after the first parsing while parsing the remaining arguments the second time. That way, in the the end, after all the parsing is done, you’ll have a single namespace with all the options.

Drawbacks:

  • If --prox is not present the other two dependent options aren’t even present in the namespace. Although based on your use-case, if --prox is not present, what happens to the other options is irrelevant.
  • Need to modify usage message as parser doesn’t know full structure
  • --lport and --rport don’t show up in help message

回答 3

未设置lport时使用prox。如果不是,为什么不进行lport和的rport论证prox?例如

parser.add_argument('--prox', nargs=2, type=int, help='Prox: listen and proxy ports')

这样可以节省用户输入的时间。测试if args.prox is not None:和一样容易if args.prox:

Do you use lport when prox is not set. If not, why not make lport and rport arguments of prox? e.g.

parser.add_argument('--prox', nargs=2, type=int, help='Prox: listen and proxy ports')

That saves your users typing. It is just as easy to test if args.prox is not None: as if args.prox:.


回答 4

接受的答案对我很有用!由于所有代码都未经测试就被破坏,这就是我测试接受答案的方式。parser.error()不会引发argparse.ArgumentError错误,而是退出该过程。您必须进行测试SystemExit

与pytest

import pytest
from . import parse_arguments  # code that rasises parse.error()


def test_args_parsed_raises_error():
    with pytest.raises(SystemExit):
        parse_arguments(["argument that raises error"])

有单元测试

from unittest import TestCase
from . import parse_arguments  # code that rasises parse.error()

class TestArgs(TestCase):

    def test_args_parsed_raises_error():
        with self.assertRaises(SystemExit) as cm:
            parse_arguments(["argument that raises error"])

启发自:使用unittest测试argparse-退出错误

The accepted answer worked great for me! Since all code is broken without tests here is how I tested the accepted answer. parser.error() does not raise an argparse.ArgumentError error it instead exits the process. You have to test for SystemExit.

with pytest

import pytest
from . import parse_arguments  # code that rasises parse.error()


def test_args_parsed_raises_error():
    with pytest.raises(SystemExit):
        parse_arguments(["argument that raises error"])

with unittests

from unittest import TestCase
from . import parse_arguments  # code that rasises parse.error()

class TestArgs(TestCase):

    def test_args_parsed_raises_error():
        with self.assertRaises(SystemExit) as cm:
            parse_arguments(["argument that raises error"])

inspired from: Using unittest to test argparse – exit errors


使用Python argparse创建隐藏参数

问题:使用Python argparse创建隐藏参数

是否可以在Python argparse.ArgumentParser的用法或帮助(script.py --help)中不显示参数的情况下将其添加到python中?

Is it possible to add an Argument to an python argparse.ArgumentParser without it showing up in the usage or help (script.py --help)?


回答 0

是的,您可以将help选项设置add_argumentargparse.SUPPRESS。这是argparse文档中的示例:

>>> parser = argparse.ArgumentParser(prog='frobble')
>>> parser.add_argument('--foo', help=argparse.SUPPRESS)
>>> parser.print_help()
usage: frobble [-h]

optional arguments:
  -h, --help  show this help message and exit

Yes, you can set the help option to add_argument to argparse.SUPPRESS. Here’s an example from the argparse documentation:

>>> parser = argparse.ArgumentParser(prog='frobble')
>>> parser.add_argument('--foo', help=argparse.SUPPRESS)
>>> parser.print_help()
usage: frobble [-h]

optional arguments:
  -h, --help  show this help message and exit

回答 1

我通过添加启用隐藏选项的选项来做到这一点,并通过查看来获取它sysv.args

如果执行此操作,sys.argv则如果您假设选项是-s启用隐藏选项,则必须将您从中选择的特殊参数直接包含在解析列表中。

parser.add_argument('-a', '-axis',
                    dest="axis", action="store_true", default=False,
                    help="Rotate the earth")
if "-s" in sys.argv or "-secret" in sys.argv:
    parser.add_argument('-s', '-secret',
                        dest="secret", action="store_true", default=False,
                        help="Enable secret options")
    parser.add_argument('-d', '-drill',
                        dest="drill", action="store_true", default=False,
                        help="drill baby, drill")

I do it by adding an option to enable the hidden ones, and grab that by looking at sysv.args.

If you do this, you have to include the special arg you pick out of sys.argv directly in the parse list if you Assume the option is -s to enable hidden options.

parser.add_argument('-a', '-axis',
                    dest="axis", action="store_true", default=False,
                    help="Rotate the earth")
if "-s" in sys.argv or "-secret" in sys.argv:
    parser.add_argument('-s', '-secret',
                        dest="secret", action="store_true", default=False,
                        help="Enable secret options")
    parser.add_argument('-d', '-drill',
                        dest="drill", action="store_true", default=False,
                        help="drill baby, drill")

检查是否设置了argparse可选参数

问题:检查是否设置了argparse可选参数

我想检查用户是否设置了可选的argparse参数。

我可以安全地使用isset检查吗?

像这样:

if(isset(args.myArg)):
    #do something
else:
    #do something else

这对于float / int / string类型参数是否起作用?

我可以设置一个默认参数并检查它(例如,设置myArg = -1,或为字符串““或“ NOT_SET”)。但是,我最终要使用的值仅在脚本的稍后部分计算。因此,我会将其默认设置为-1,然后稍后将其更新为其他内容。与仅检查该值是否由用户设置相比,这似乎有点笨拙。

I would like to check whether an optional argparse argument has been set by the user or not.

Can I safely check using isset?

Something like this:

if(isset(args.myArg)):
    #do something
else:
    #do something else

Does this work the same for float / int / string type arguments?

I could set a default parameter and check it (e.g., set myArg = -1, or “” for a string, or “NOT_SET”). However, the value I ultimately want to use is only calculated later in the script. So I would be setting it to -1 as a default, and then updating it to something else later. This seems a little clumsy in comparison with simply checking if the value was set by the user.


回答 0

我认为,如果未提供可选参数(用指定--),None则将其初始化。因此,您可以使用进行测试is not None。请尝试以下示例:

import argparse as ap

def main():
    parser = ap.ArgumentParser(description="My Script")
    parser.add_argument("--myArg")
    args, leftovers = parser.parse_known_args()

    if args.myArg is not None:
        print "myArg has been set (value is %s)" % args.myArg

I think that optional arguments (specified with --) are initialized to None if they are not supplied. So you can test with is not None. Try the example below:

import argparse as ap

def main():
    parser = ap.ArgumentParser(description="My Script")
    parser.add_argument("--myArg")
    args, leftovers = parser.parse_known_args()

    if args.myArg is not None:
        print "myArg has been set (value is %s)" % args.myArg

回答 1

正如@Honza所说,这is None是一个很好的测试。这是默认设置default,用户无法给您提供重复的字符串。

您可以指定另一个default='mydefaultvalue,然后进行测试。但是,如果用户指定该字符串怎么办?是否算作设置?

您也可以指定default=argparse.SUPPRESS。然后,如果用户不使用该参数,它将不会出现在args命名空间中。但是测试可能会更复杂:

args.foo # raises an AttributeError
hasattr(args, 'foo')  # returns False
getattr(args, 'foo', 'other') # returns 'other'

内部parser保留一个的列表seen_actions,并将其用于“必需”和“互斥”测试。但您无法使用parse_args

As @Honza notes is None is a good test. It’s the default default, and the user can’t give you a string that duplicates it.

You can specify another default='mydefaultvalue, and test for that. But what if the user specifies that string? Does that count as setting or not?

You can also specify default=argparse.SUPPRESS. Then if the user does not use the argument, it will not appear in the args namespace. But testing that might be more complicated:

args.foo # raises an AttributeError
hasattr(args, 'foo')  # returns False
getattr(args, 'foo', 'other') # returns 'other'

Internally the parser keeps a list of seen_actions, and uses it for ‘required’ and ‘mutually_exclusive’ testing. But it isn’t available to you out side of parse_args.


回答 2

我认为使用该选项default=argparse.SUPPRESS最有意义。然后,而不是检查参数是否为,而是检查参数是否not Nonein为结果命名空间。

例:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--foo", default=argparse.SUPPRESS)
ns = parser.parse_args()

print("Parsed arguments: {}".format(ns))
print("foo in namespace?: {}".format("foo" in ns))

用法:

$ python argparse_test.py --foo 1
Parsed arguments: Namespace(foo='1')
foo in namespace?: True
不提供参数:
$ python argparse_test.py
Parsed arguments: Namespace()
foo in namespace?: False

I think using the option default=argparse.SUPPRESS makes most sense. Then, instead of checking if the argument is not None, one checks if the argument is in the resulting namespace.

Example:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--foo", default=argparse.SUPPRESS)
ns = parser.parse_args()

print("Parsed arguments: {}".format(ns))
print("foo in namespace?: {}".format("foo" in ns))

Usage:

$ python argparse_test.py --foo 1
Parsed arguments: Namespace(foo='1')
foo in namespace?: True
Argument is not supplied:
$ python argparse_test.py
Parsed arguments: Namespace()
foo in namespace?: False

回答 3

您可以使用store_truestore_false参数操作选项检查可选传递的标志:

import argparse

argparser = argparse.ArgumentParser()
argparser.add_argument('-flag', dest='flag_exists', action='store_true')

print argparser.parse_args([])
# Namespace(flag_exists=False)
print argparser.parse_args(['-flag'])
# Namespace(flag_exists=True)

这样,您就不必担心按条件检查is not None。您只需检查TrueFalse。在此处阅读更多关于这些选项的信息

You can check an optionally passed flag with store_true and store_false argument action options:

import argparse

argparser = argparse.ArgumentParser()
argparser.add_argument('-flag', dest='flag_exists', action='store_true')

print argparser.parse_args([])
# Namespace(flag_exists=False)
print argparser.parse_args(['-flag'])
# Namespace(flag_exists=True)

This way, you don’t have to worry about checking by conditional is not None. You simply check for True or False. Read more about these options in the docs here


回答 4

如果您的参数是位置参数(即,它没有“-”或“-”前缀,只有参数,通常是文件名),则可以使用nargs参数执行此操作:

parser = argparse.ArgumentParser(description='Foo is a program that does things')
parser.add_argument('filename', nargs='?')
args = parser.parse_args()

if args.filename is not None:
    print('The file name is {}'.format(args.filename))
else:
    print('Oh well ; No args, no problems')

If your argument is positional (ie it doesn’t have a “-” or a “–” prefix, just the argument, typically a file name) then you can use the nargs parameter to do this:

parser = argparse.ArgumentParser(description='Foo is a program that does things')
parser.add_argument('filename', nargs='?')
args = parser.parse_args()

if args.filename is not None:
    print('The file name is {}'.format(args.filename))
else:
    print('Oh well ; No args, no problems')

回答 5

这是我的解决方案,看看我是否正在使用argparse变量

import argparse

ap = argparse.ArgumentParser()
ap.add_argument("-1", "--first", required=True)
ap.add_argument("-2", "--second", required=True)
ap.add_argument("-3", "--third", required=False) 
# Combine all arguments into a list called args
args = vars(ap.parse_args())
if args["third"] is not None:
# do something

这可能会使我对上面的答案有更深入的了解,而我使用该答案并对其进行了修改以使其适合我的程序。

Here is my solution to see if I am using an argparse variable

import argparse

ap = argparse.ArgumentParser()
ap.add_argument("-1", "--first", required=True)
ap.add_argument("-2", "--second", required=True)
ap.add_argument("-3", "--third", required=False) 
# Combine all arguments into a list called args
args = vars(ap.parse_args())
if args["third"] is not None:
# do something

This might give more insight to the above answer which I used and adapted to work for my program.


回答 6

为了解决@kcpr对@Honza Osobne的(当前接受的)答案的评论

不幸的是,它不起作用,然后参数将其定义为默认值。

首先可以通过将参数与 Namespace提供default=argparse.SUPPRESS选项对象abd(请参见@hpaulj和@Erasmus Cedernaes的答案以及此python3 doc),如果未提供参数,则将其设置为默认值。

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--infile', default=argparse.SUPPRESS)
args = parser.parse_args()
if 'infile' in args: 
    # the argument is in the namespace, it's been provided by the user
    # set it to what has been provided
    theinfile = args.infile
    print('argument \'--infile\' was given, set to {}'.format(theinfile))
else:
    # the argument isn't in the namespace
    # set it to a default value
    theinfile = 'your_default.txt'
    print('argument \'--infile\' was not given, set to default {}'.format(theinfile))

用法

$ python3 testargparse_so.py
argument '--infile' was not given, set to default your_default.txt

$ python3 testargparse_so.py --infile user_file.txt
argument '--infile' was given, set to user_file.txt

In order to address @kcpr’s comment on the (currently accepted) answer by @Honza Osobne

Unfortunately it doesn’t work then the argument got it’s default value defined.

one can first check if the argument was provided by comparing it with the Namespace object abd providing the default=argparse.SUPPRESS option (see @hpaulj’s and @Erasmus Cedernaes answers and this python3 doc) and if it hasn’t been provided, then set it to a default value.

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--infile', default=argparse.SUPPRESS)
args = parser.parse_args()
if 'infile' in args: 
    # the argument is in the namespace, it's been provided by the user
    # set it to what has been provided
    theinfile = args.infile
    print('argument \'--infile\' was given, set to {}'.format(theinfile))
else:
    # the argument isn't in the namespace
    # set it to a default value
    theinfile = 'your_default.txt'
    print('argument \'--infile\' was not given, set to default {}'.format(theinfile))

Usage

$ python3 testargparse_so.py
argument '--infile' was not given, set to default your_default.txt

$ python3 testargparse_so.py --infile user_file.txt
argument '--infile' was given, set to user_file.txt

回答 7

很简单,在通过“ args = parser.parse_args()”定义args变量后,它也包含args子集变量的所有数据。要检查是否设置了变量或假设使用的是’action =“ store_true” …

if args.argument_name:
   # do something
else:
   # do something else

Very simple, after defining args variable by ‘args = parser.parse_args()’ it contains all data of args subset variables too. To check if a variable is set or no assuming the ‘action=”store_true” is used…

if args.argument_name:
   # do something
else:
   # do something else

指定输入参数argparse python的格式

问题:指定输入参数argparse python的格式

我有一个需要一些命令行输入的python脚本,并且我正在使用argparse进行解析。我发现文档有点混乱,找不到在输入参数中检查格式的方法。这个示例脚本解释了我检查格式的意思:

parser.add_argument('-s', "--startdate", help="The Start Date - format YYYY-MM-DD ", required=True)
parser.add_argument('-e', "--enddate", help="The End Date format YYYY-MM-DD (Inclusive)", required=True)
parser.add_argument('-a', "--accountid", type=int, help='Account ID for the account for which data is required (Default: 570)')
parser.add_argument('-o', "--outputpath", help='Directory where output needs to be stored (Default: ' + os.path.dirname(os.path.abspath(__file__)))

我需要检查选项,-s并且-e用户输入的格式为YYYY-MM-DD。我不知道argparse中有一个选项可以完成此任务。

I have a python script that requires some command line inputs and I am using argparse for parsing them. I found the documentation a bit confusing and couldn’t find a way to check for a format in the input parameters. What I mean by checking format is explained with this example script:

parser.add_argument('-s', "--startdate", help="The Start Date - format YYYY-MM-DD ", required=True)
parser.add_argument('-e', "--enddate", help="The End Date format YYYY-MM-DD (Inclusive)", required=True)
parser.add_argument('-a', "--accountid", type=int, help='Account ID for the account for which data is required (Default: 570)')
parser.add_argument('-o', "--outputpath", help='Directory where output needs to be stored (Default: ' + os.path.dirname(os.path.abspath(__file__)))

I need to check for option -s and -e that the input by the user is in the format YYYY-MM-DD. Is there an option in argparse that I do not know of which accomplishes this.


回答 0

根据文档

type关键字参数add_argument()允许执行任何必要的类型检查和类型转换… type=可以接受带有单个字符串参数并返回转换后值的任何可调用对象

您可以执行以下操作:

def valid_date(s):
    try:
        return datetime.strptime(s, "%Y-%m-%d")
    except ValueError:
        msg = "Not a valid date: '{0}'.".format(s)
        raise argparse.ArgumentTypeError(msg)

然后将其用作type

parser.add_argument("-s", 
                    "--startdate", 
                    help="The Start Date - format YYYY-MM-DD", 
                    required=True, 
                    type=valid_date)

Per the documentation:

The type keyword argument of add_argument() allows any necessary type-checking and type conversions to be performed … type= can take any callable that takes a single string argument and returns the converted value

You could do something like:

def valid_date(s):
    try:
        return datetime.strptime(s, "%Y-%m-%d")
    except ValueError:
        msg = "Not a valid date: '{0}'.".format(s)
        raise argparse.ArgumentTypeError(msg)

Then use that as type:

parser.add_argument("-s", 
                    "--startdate", 
                    help="The Start Date - format YYYY-MM-DD", 
                    required=True, 
                    type=valid_date)

回答 1

只是为了补充上面的答案,如果您想将lambda函数保持为单一格式,则可以使用该函数。例如:

parser.add_argument('--date', type=lambda d: datetime.strptime(d, '%Y%m%d'))

旧线程,但问题至少仍然与我有关!

Just to add on to the answer above, you can use a lambda function if you want to keep it to a one-liner. For example:

parser.add_argument('--date', type=lambda d: datetime.strptime(d, '%Y%m%d'))

Old thread but the question was still relevant for me at least!


回答 2

对于其他通过搜索引擎实现此目标的人:在Python 3.7中,您可以使用标准的.fromisoformatclass方法,而不是为ISO-8601兼容日期重新发明轮子,例如:

parser.add_argument('-s', "--startdate",
    help="The Start Date - format YYYY-MM-DD",
    required=True,
    type=datetime.date.fromisoformat)
parser.add_argument('-e', "--enddate",
    help="The End Date format YYYY-MM-DD (Inclusive)",
    required=True,
    type=datetime.date.fromisoformat)

For others who hit this via search engines: in Python 3.7, you can use the standard .fromisoformat class method instead of reinventing the wheel for ISO-8601 compliant dates, e.g.:

parser.add_argument('-s', "--startdate",
    help="The Start Date - format YYYY-MM-DD",
    required=True,
    type=datetime.date.fromisoformat)
parser.add_argument('-e', "--enddate",
    help="The End Date format YYYY-MM-DD (Inclusive)",
    required=True,
    type=datetime.date.fromisoformat)

如果未指定,则argparse存储为false

问题:如果未指定,则argparse存储为false

parser.add_argument('-auto', action='store_true')

如果-auto未指定,如何存储假?我可以淡淡地记得这种方式,如果未指定,它将存储None

parser.add_argument('-auto', action='store_true')

How can I store false if -auto is unspecified? I can faintly remember that this way, it stores None if unspecified


回答 0

store_true选项自动创建默认值False

同样,当不存在命令行参数时,store_false默认为True

此行为的来源简洁明了:http : //hg.python.org/cpython/file/2.7/Lib/argparse.py#l861

argparse文档尚不清楚,所以我现在将对其进行更新:http : //hg.python.org/cpython/rev/49677cc6d83a

The store_true option automatically creates a default value of False.

Likewise, store_false will default to True when the command-line argument is not present.

The source for this behavior is succinct and clear: http://hg.python.org/cpython/file/2.7/Lib/argparse.py#l861

The argparse docs aren’t clear on the subject, so I’ll update them now: http://hg.python.org/cpython/rev/49677cc6d83a


回答 1

import argparse
parser=argparse.ArgumentParser()
parser.add_argument('-auto', action='store_true', )
args=parser.parse_args()
print(args)

跑步

% test.py

Yield

Namespace(auto=False)

因此False,默认情况下它似乎正在存储。

With

import argparse
parser=argparse.ArgumentParser()
parser.add_argument('-auto', action='store_true', )
args=parser.parse_args()
print(args)

running

% test.py

yields

Namespace(auto=False)

So it appears to be storing False by default.


回答 2

Raymond Hettinger已经回答了OP的问题。

但是,我的小组使用“ store_false”遇到了可读性问题。特别是当新成员加入我们的小组时。这是因为最直观的思考方式是,当用户指定参数时,与该参数相对应的值为True或1。

例如,如果代码是-

parser.add_argument('--stop_logging', action='store_false')

当stop_logging中的值为true时,代码阅读器可能希望关闭日志记录语句。但是,如下所示的代码将导致所需行为的反面

if not stop_logging:
    #log

另一方面,如果将接口定义为以下内容,则“ if语句”有效并且更直观地阅读-

parser.add_argument('--stop_logging', action='store_true')
if not stop_logging:
    #log

Raymond Hettinger answers OP’s question already.

However, my group has experienced readability issues using “store_false”. Especially when new members join our group. This is because it is most intuitive way to think is that when a user specifies an argument, the value corresponding to that argument will be True or 1.

For example, if the code is –

parser.add_argument('--stop_logging', action='store_false')

The code reader may likely expect the logging statement to be off when the value in stop_logging is true. But code such as the following will lead to the opposite of the desired behavior –

if not stop_logging:
    #log

On the other hand, if the interface is defined as the following, then the “if-statement” works and is more intuitive to read –

parser.add_argument('--stop_logging', action='store_true')
if not stop_logging:
    #log

回答 3

store_false实际上将默认为0默认值(您可以测试验证)。要更改默认设置,只需添加default=True到声明中即可。

因此,在这种情况下: parser.add_argument('-auto', action='store_true', default=True)

store_false will actually default to 0 by default (you can test to verify). To change what it defaults to, just add default=True to your declaration.

So in this case: parser.add_argument('-auto', action='store_true', default=True)


argparse模块如何添加不带任何参数的选项?

问题:argparse模块如何添加不带任何参数的选项?

我使用创建了一个脚本argparse

脚本需要使用配置文件名作为选项,用户可以指定是完全执行脚本还是仅模拟脚本。

要传递的args:./script -f config_file -s./script -f config_file

-f config_file部分可以,但是它一直在询问我-s的参数,该参数是可选的,不应跟随任何参数。

我已经试过了:

parser = argparse.ArgumentParser()
parser.add_argument('-f', '--file')
#parser.add_argument('-s', '--simulate', nargs = '0')
args = parser.parse_args()
if args.file:
    config_file = args.file
if args.set_in_prod:
        simulate = True
else:
    pass

有以下错误:

File "/usr/local/lib/python2.6/dist-packages/argparse.py", line 2169, in _get_nargs_pattern
nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs)
TypeError: can't multiply sequence by non-int of type 'str'

和相同的错误,''而不是0

I have created a script using argparse.

The script needs to take a configuration file name as an option, and user can specify whether they need to proceed totally the script or only simulate it.

The args to be passed: ./script -f config_file -s or ./script -f config_file.

It’s ok for the -f config_file part, but It keeps asking me for arguments for the -s which is optionnal and should not be followed by any.

I have tried this:

parser = argparse.ArgumentParser()
parser.add_argument('-f', '--file')
#parser.add_argument('-s', '--simulate', nargs = '0')
args = parser.parse_args()
if args.file:
    config_file = args.file
if args.set_in_prod:
        simulate = True
else:
    pass

With the following errors:

File "/usr/local/lib/python2.6/dist-packages/argparse.py", line 2169, in _get_nargs_pattern
nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs)
TypeError: can't multiply sequence by non-int of type 'str'

And same errror with '' instead of 0.


回答 0

@Felix Kling建议使用action='store_true'

>>> from argparse import ArgumentParser
>>> p = ArgumentParser()
>>> _ = p.add_argument('-f', '--foo', action='store_true')
>>> args = p.parse_args()
>>> args.foo
False
>>> args = p.parse_args(['-f'])
>>> args.foo
True

As @Felix Kling suggested use action='store_true':

>>> from argparse import ArgumentParser
>>> p = ArgumentParser()
>>> _ = p.add_argument('-f', '--foo', action='store_true')
>>> args = p.parse_args()
>>> args.foo
False
>>> args = p.parse_args(['-f'])
>>> args.foo
True

回答 1

要创建不需要任何值的选项,请设置 action [文档]的它'store_const''store_true''store_false'

例:

parser.add_argument('-s', '--simulate', action='store_true')

To create an option that needs no value, set the action [docs] of it to 'store_const', 'store_true' or 'store_false'.

Example:

parser.add_argument('-s', '--simulate', action='store_true')

在Python中,使用argparse,仅允许使用正整数

问题:在Python中,使用argparse,仅允许使用正整数

标题几乎总结了我想发生的事情。

这就是我所拥有的,虽然程序不会在非正整数上崩溃,但我希望通知用户非正整数基本上是无稽之谈。

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-g", "--games", type=int, default=162,
                    help="The number of games to simulate")
args = parser.parse_args()

并输出:

python simulate_many.py -g 20
Setting up...
Playing games...
....................

输出为负:

python simulate_many.py -g -2
Setting up...
Playing games...

现在,显然我可以添加一个if来确定if args.games是否为负,但是我很好奇是否有一种方法可以将其捕获到该argparse级别,以便利用自动用法打印。

理想情况下,它将打印类似于以下内容的内容:

python simulate_many.py -g a
usage: simulate_many.py [-h] [-g GAMES] [-d] [-l LEAGUE]
simulate_many.py: error: argument -g/--games: invalid int value: 'a'

像这样:

python simulate_many.py -g -2
usage: simulate_many.py [-h] [-g GAMES] [-d] [-l LEAGUE]
simulate_many.py: error: argument -g/--games: invalid positive int value: '-2'

现在,我正在这样做,我想我很高兴:

if args.games <= 0:
    parser.print_help()
    print "-g/--games: must be positive."
    sys.exit(1)

The title pretty much summarizes what I’d like to have happen.

Here is what I have, and while the program doesn’t blow up on a nonpositive integer, I want the user to be informed that a nonpositive integer is basically nonsense.

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-g", "--games", type=int, default=162,
                    help="The number of games to simulate")
args = parser.parse_args()

And the output:

python simulate_many.py -g 20
Setting up...
Playing games...
....................

Output with a negative:

python simulate_many.py -g -2
Setting up...
Playing games...

Now, obviously I could just add an if to determine if args.games is negative, but I was curious if there was a way to trap it at the argparse level, so as to take advantage of the automatic usage printing.

Ideally, it would print something similar to this:

python simulate_many.py -g a
usage: simulate_many.py [-h] [-g GAMES] [-d] [-l LEAGUE]
simulate_many.py: error: argument -g/--games: invalid int value: 'a'

Like so:

python simulate_many.py -g -2
usage: simulate_many.py [-h] [-g GAMES] [-d] [-l LEAGUE]
simulate_many.py: error: argument -g/--games: invalid positive int value: '-2'

For now I’m doing this, and I guess I’m happy:

if args.games <= 0:
    parser.print_help()
    print "-g/--games: must be positive."
    sys.exit(1)

回答 0

这应该是可以利用的type。您仍然需要定义一个实际的方法来为您确定:

def check_positive(value):
    ivalue = int(value)
    if ivalue <= 0:
        raise argparse.ArgumentTypeError("%s is an invalid positive int value" % value)
    return ivalue

parser = argparse.ArgumentParser(...)
parser.add_argument('foo', type=check_positive)

这基本上是从刚刚适应的例子perfect_square在函数文档argparse

This should be possible utilizing type. You’ll still need to define an actual method that decides this for you:

def check_positive(value):
    ivalue = int(value)
    if ivalue <= 0:
        raise argparse.ArgumentTypeError("%s is an invalid positive int value" % value)
    return ivalue

parser = argparse.ArgumentParser(...)
parser.add_argument('foo', type=check_positive)

This is basically just an adapted example from the perfect_square function in the docs on argparse.


回答 1

type 就像Yuushi的回答一样,将是处理条件/检查的推荐选项。

在您的特定情况下,choices如果您的上限也已知,则也可以使用参数:

parser.add_argument('foo', type=int, choices=xrange(5, 10))

注:使用range的,而不是xrange为Python 3.X

type would be the recommended option to handle conditions/checks, as in Yuushi’s answer.

In your specific case, you can also use the choices parameter if your upper limit is also known:

parser.add_argument('foo', type=int, choices=xrange(5, 10))

Note: Use range instead of xrange for python 3.x


回答 2

如果您有一个可预测的最大值和最小值,则快速而肮脏的方法是使用choices范围

parser.add_argument('foo', type=int, choices=xrange(0, 1000))

The quick and dirty way, if you have a predictable max as well as min for your arg, is use choices with a range

parser.add_argument('foo', type=int, choices=xrange(0, 1000))

回答 3

一个更简单的替代方法(尤其是在子类化的情况下argparse.ArgumentParser)是从parse_args方法内部启动验证。

在这样的子类中:

def parse_args(self, args=None, namespace=None):
    """Parse and validate args."""
    namespace = super().parse_args(args, namespace)
    if namespace.games <= 0:
         raise self.error('The number of games must be a positive integer.')
    return namespace

这项技术可能不像自定义可调用项那么酷,但是可以完成这项工作。


关于ArgumentParser.error(message)

此方法打印一条包括标准错误消息的用法消息,并以状态代码2终止程序。


信用:乔纳丹回答

A simpler alternative, especially if subclassing argparse.ArgumentParser, is to initiate the validation from inside the parse_args method.

Inside such a subclass:

def parse_args(self, args=None, namespace=None):
    """Parse and validate args."""
    namespace = super().parse_args(args, namespace)
    if namespace.games <= 0:
         raise self.error('The number of games must be a positive integer.')
    return namespace

This technique may not be as cool as a custom callable, but it does the job.


About ArgumentParser.error(message):

This method prints a usage message including the message to the standard error and terminates the program with a status code of 2.


Credit: answer by jonatan


回答 4

如果有人(像我一样)在Google搜索中遇到此问题,以下示例说明了如何使用模块化方法来巧妙地解决更宽泛的问题,即允许在指定范围内使用 argparse整数:

# Custom argparse type representing a bounded int
class IntRange:

    def __init__(self, imin=None, imax=None):
        self.imin = imin
        self.imax = imax

    def __call__(self, arg):
        try:
            value = int(arg)
        except ValueError:
            raise self.exception()
        if (self.imin is not None and value < self.imin) or (self.imax is not None and value > self.imax):
            raise self.exception()
        return value

    def exception(self):
        if self.imin is not None and self.imax is not None:
            return argparse.ArgumentTypeError(f"Must be an integer in the range [{self.imin}, {self.imax}]")
        elif self.imin is not None:
            return argparse.ArgumentTypeError(f"Must be an integer >= {self.imin}")
        elif self.imax is not None:
            return argparse.ArgumentTypeError(f"Must be an integer <= {self.imax}")
        else:
            return argparse.ArgumentTypeError("Must be an integer")

这使您可以执行以下操作:

parser = argparse.ArgumentParser(...)
parser.add_argument('foo', type=IntRange(1))     # Must have foo >= 1
parser.add_argument('bar', type=IntRange(1, 7))  # Must have 1 <= bar <= 7

变量foo现在只允许使用正整数,就像要求的OP一样。

请注意,除了上述形式以外,使用以下形式还可以设置最大值IntRange

parser.add_argument('other', type=IntRange(imax=10))  # Must have other <= 10

In case someone (like me) comes across this question in a Google search, here is an example of how to use a modular approach to neatly solve the more general problem of allowing argparse integers only in a specified range:

# Custom argparse type representing a bounded int
class IntRange:

    def __init__(self, imin=None, imax=None):
        self.imin = imin
        self.imax = imax

    def __call__(self, arg):
        try:
            value = int(arg)
        except ValueError:
            raise self.exception()
        if (self.imin is not None and value < self.imin) or (self.imax is not None and value > self.imax):
            raise self.exception()
        return value

    def exception(self):
        if self.imin is not None and self.imax is not None:
            return argparse.ArgumentTypeError(f"Must be an integer in the range [{self.imin}, {self.imax}]")
        elif self.imin is not None:
            return argparse.ArgumentTypeError(f"Must be an integer >= {self.imin}")
        elif self.imax is not None:
            return argparse.ArgumentTypeError(f"Must be an integer <= {self.imax}")
        else:
            return argparse.ArgumentTypeError("Must be an integer")

This allows you to do something like:

parser = argparse.ArgumentParser(...)
parser.add_argument('foo', type=IntRange(1))     # Must have foo >= 1
parser.add_argument('bar', type=IntRange(1, 7))  # Must have 1 <= bar <= 7

The variable foo now allows only positive integers, like the OP asked.

Note that in addition to the above forms, just a maximum is also possible with IntRange:

parser.add_argument('other', type=IntRange(imax=10))  # Must have other <= 10

如何为python模块的argparse部分编写测试?[关闭]

问题:如何为python模块的argparse部分编写测试?[关闭]

我有一个使用argparse库的Python模块。如何为代码库的该部分编写测试?

I have a Python module that uses the argparse library. How do I write tests for that section of the code base?


回答 0

您应该重构代码并将解析移至函数:

def parse_args(args):
    parser = argparse.ArgumentParser(...)
    parser.add_argument...
    # ...Create your parser as you like...
    return parser.parse_args(args)

然后在main函数中,应使用以下命令调用它:

parser = parse_args(sys.argv[1:])

(其中sys.argv代表脚本名称的第一个元素被删除,以使其在CLI操作期间不作为附加开关发送。)

在测试中,然后可以使用要测试的参数列表调用解析器函数:

def test_parser(self):
    parser = parse_args(['-l', '-m'])
    self.assertTrue(parser.long)
    # ...and so on.

这样,您就不必执行应用程序的代码即可测试解析器。

如果稍后需要在应用程序中更改和/或向解析器添加选项,请创建一个工厂方法:

def create_parser():
    parser = argparse.ArgumentParser(...)
    parser.add_argument...
    # ...Create your parser as you like...
    return parser

以后,您可以根据需要对其进行操作,然后进行如下测试:

class ParserTest(unittest.TestCase):
    def setUp(self):
        self.parser = create_parser()

    def test_something(self):
        parsed = self.parser.parse_args(['--something', 'test'])
        self.assertEqual(parsed.something, 'test')

You should refactor your code and move the parsing to a function:

def parse_args(args):
    parser = argparse.ArgumentParser(...)
    parser.add_argument...
    # ...Create your parser as you like...
    return parser.parse_args(args)

Then in your main function you should just call it with:

parser = parse_args(sys.argv[1:])

(where the first element of sys.argv that represents the script name is removed to not send it as an additional switch during CLI operation.)

In your tests, you can then call the parser function with whatever list of arguments you want to test it with:

def test_parser(self):
    parser = parse_args(['-l', '-m'])
    self.assertTrue(parser.long)
    # ...and so on.

This way you’ll never have to execute the code of your application just to test the parser.

If you need to change and/or add options to your parser later in your application, then create a factory method:

def create_parser():
    parser = argparse.ArgumentParser(...)
    parser.add_argument...
    # ...Create your parser as you like...
    return parser

You can later manipulate it if you want, and a test could look like:

class ParserTest(unittest.TestCase):
    def setUp(self):
        self.parser = create_parser()

    def test_something(self):
        parsed = self.parser.parse_args(['--something', 'test'])
        self.assertEqual(parsed.something, 'test')

回答 1

“ argparse部分”有点含糊不清,因此该答案仅集中在一部分:parse_args方法上。这是与命令行交互并获取所有传递的值的方法。基本上,您可以模拟parse_args返回的内容,因此实际上不需要从命令行获取值。该mock 软件包可以通过pip安装,适用于python 2.6-3.2版本。unittest.mock从版本3.3开始,它是标准库的一部分。

import argparse
try:
    from unittest import mock  # python 3.3+
except ImportError:
    import mock  # python 2.6-3.2


@mock.patch('argparse.ArgumentParser.parse_args',
            return_value=argparse.Namespace(kwarg1=value, kwarg2=value))
def test_command(mock_args):
    pass

您必须包括所有命令方法的参数,Namespace 即使它们没有被传递。赋予这些args值为None。(请参阅docs)此样式对于快速进行测试(对于每个方法参数传递不同值的情况)很有用。如果您选择模拟Namespace自己以完全避免测试中的argparse依赖,请确保其行为与实际Namespace类相似。

以下是使用argparse库中第一个代码段的示例。

# test_mock_argparse.py
import argparse
try:
    from unittest import mock  # python 3.3+
except ImportError:
    import mock  # python 2.6-3.2


def main():
    parser = argparse.ArgumentParser(description='Process some integers.')
    parser.add_argument('integers', metavar='N', type=int, nargs='+',
                        help='an integer for the accumulator')
    parser.add_argument('--sum', dest='accumulate', action='store_const',
                        const=sum, default=max,
                        help='sum the integers (default: find the max)')

    args = parser.parse_args()
    print(args)  # NOTE: this is how you would check what the kwargs are if you're unsure
    return args.accumulate(args.integers)


@mock.patch('argparse.ArgumentParser.parse_args',
            return_value=argparse.Namespace(accumulate=sum, integers=[1,2,3]))
def test_command(mock_args):
    res = main()
    assert res == 6, "1 + 2 + 3 = 6"


if __name__ == "__main__":
    print(main())

“argparse portion” is a bit vague so this answer focuses on one part: the parse_args method. This is the method that interacts with your command line and gets all the passed values. Basically, you can mock what parse_args returns so that it doesn’t need to actually get values from the command line. The mock package can be installed via pip for python versions 2.6-3.2. It’s part of the standard library as unittest.mock from version 3.3 onwards.

import argparse
try:
    from unittest import mock  # python 3.3+
except ImportError:
    import mock  # python 2.6-3.2


@mock.patch('argparse.ArgumentParser.parse_args',
            return_value=argparse.Namespace(kwarg1=value, kwarg2=value))
def test_command(mock_args):
    pass

You have to include all your command method’s args in Namespace even if they’re not passed. Give those args a value of None. (see the docs) This style is useful for quickly doing testing for cases where different values are passed for each method argument. If you opt to mock Namespace itself for total argparse non-reliance in your tests, make sure it behaves similarly to the actual Namespace class.

Below is an example using the first snippet from the argparse library.

# test_mock_argparse.py
import argparse
try:
    from unittest import mock  # python 3.3+
except ImportError:
    import mock  # python 2.6-3.2


def main():
    parser = argparse.ArgumentParser(description='Process some integers.')
    parser.add_argument('integers', metavar='N', type=int, nargs='+',
                        help='an integer for the accumulator')
    parser.add_argument('--sum', dest='accumulate', action='store_const',
                        const=sum, default=max,
                        help='sum the integers (default: find the max)')

    args = parser.parse_args()
    print(args)  # NOTE: this is how you would check what the kwargs are if you're unsure
    return args.accumulate(args.integers)


@mock.patch('argparse.ArgumentParser.parse_args',
            return_value=argparse.Namespace(accumulate=sum, integers=[1,2,3]))
def test_command(mock_args):
    res = main()
    assert res == 6, "1 + 2 + 3 = 6"


if __name__ == "__main__":
    print(main())

回答 2

让您的main()函数argv作为参数,而不是像默认情况那样让它读取sys.argv

# mymodule.py
import argparse
import sys


def main(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('-a')
    process(**vars(parser.parse_args(args)))
    return 0


def process(a=None):
    pass

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

然后您就可以正常测试了。

import mock

from mymodule import main


@mock.patch('mymodule.process')
def test_main(process):
    main([])
    process.assert_call_once_with(a=None)


@mock.patch('foo.process')
def test_main_a(process):
    main(['-a', '1'])
    process.assert_call_once_with(a='1')

Make your main() function take argv as an argument rather than letting it read from sys.argv as it will by default:

# mymodule.py
import argparse
import sys


def main(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('-a')
    process(**vars(parser.parse_args(args)))
    return 0


def process(a=None):
    pass

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

Then you can test normally.

import mock

from mymodule import main


@mock.patch('mymodule.process')
def test_main(process):
    main([])
    process.assert_call_once_with(a=None)


@mock.patch('foo.process')
def test_main_a(process):
    main(['-a', '1'])
    process.assert_call_once_with(a='1')

回答 3

  1. 使用sys.argv.append(),然后调用 parse(),填充arg列表,检查结果并重复。
  2. 从带有您的标志和转储args标志的批处理/ bash文件中调用。
  3. 将所有参数解析放在一个单独的文件中,然后在if __name__ == "__main__":调用解析中转储/评估结果,然后从批处理/ bash文件进行测试。
  1. Populate your arg list by using sys.argv.append() and then call parse(), check the results and repeat.
  2. Call from a batch/bash file with your flags and a dump args flag.
  3. Put all your argument parsing in a separate file and in the if __name__ == "__main__": call parse and dump/evaluate the results then test this from a batch/bash file.

回答 4

我不想修改原始的服务脚本,所以我只是sys.argv在argparse中模拟了该部分。

from unittest.mock import patch

with patch('argparse._sys.argv', ['python', 'serve.py']):
    ...  # your test code here

如果argparse实现更改但足以进行快速测试脚本,则此操作会中断。无论如何,在测试脚本中,敏感性比特异性要重要得多。

I did not want to modify the original serving script so I just mocked out the sys.argv part in argparse.

from unittest.mock import patch

with patch('argparse._sys.argv', ['python', 'serve.py']):
    ...  # your test code here

This breaks if argparse implementation changes but enough for a quick test script. Sensibility is much more important than specificity in test scripts anyways.


回答 5

测试解析器的一种简单方法是:

parser = ...
parser.add_argument('-a',type=int)
...
argv = '-a 1 foo'.split()  # or ['-a','1','foo']
args = parser.parse_args(argv)
assert(args.a == 1)
...

另一种方法是修改sys.argv,然后调用args = parser.parse_args()

有很多的测试的例子argparselib/test/test_argparse.py

A simple way of testing a parser is:

parser = ...
parser.add_argument('-a',type=int)
...
argv = '-a 1 foo'.split()  # or ['-a','1','foo']
args = parser.parse_args(argv)
assert(args.a == 1)
...

Another way is to modify sys.argv, and call args = parser.parse_args()

There are lots of examples of testing argparse in lib/test/test_argparse.py


回答 6

parse_args抛出a SystemExit并打印到stderr,您可以捕获以下两个:

import contextlib
import io
import sys

@contextlib.contextmanager
def captured_output():
    new_out, new_err = io.StringIO(), io.StringIO()
    old_out, old_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = new_out, new_err
        yield sys.stdout, sys.stderr
    finally:
        sys.stdout, sys.stderr = old_out, old_err

def validate_args(args):
    with captured_output() as (out, err):
        try:
            parser.parse_args(args)
            return True
        except SystemExit as e:
            return False

您检查stderr(使用,err.seek(0); err.read()但通常不需要粒度。

现在,您可以使用assertTrue或进行任何喜欢的测试:

assertTrue(validate_args(["-l", "-m"]))

另外,您可能想捕获并抛出另一个错误(而不是SystemExit):

def validate_args(args):
    with captured_output() as (out, err):
        try:
            return parser.parse_args(args)
        except SystemExit as e:
            err.seek(0)
            raise argparse.ArgumentError(err.read())

parse_args throws a SystemExit and prints to stderr, you can catch both of these:

import contextlib
import io
import sys

@contextlib.contextmanager
def captured_output():
    new_out, new_err = io.StringIO(), io.StringIO()
    old_out, old_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = new_out, new_err
        yield sys.stdout, sys.stderr
    finally:
        sys.stdout, sys.stderr = old_out, old_err

def validate_args(args):
    with captured_output() as (out, err):
        try:
            parser.parse_args(args)
            return True
        except SystemExit as e:
            return False

You inspect stderr (using err.seek(0); err.read() but generally that granularity isn’t required.

Now you can use assertTrue or whichever testing you like:

assertTrue(validate_args(["-l", "-m"]))

Alternatively you might like to catch and rethrow a different error (instead of SystemExit):

def validate_args(args):
    with captured_output() as (out, err):
        try:
            return parser.parse_args(args)
        except SystemExit as e:
            err.seek(0)
            raise argparse.ArgumentError(err.read())

回答 7

将结果从argparse.ArgumentParser.parse_args传递给函数时,有时会使用a namedtuple来模拟参数以进行测试。

import unittest
from collections import namedtuple
from my_module import main

class TestMyModule(TestCase):

    args_tuple = namedtuple('args', 'arg1 arg2 arg3 arg4')

    def test_arg1(self):
        args = TestMyModule.args_tuple("age > 85", None, None, None)
        res = main(args)
        assert res == ["55289-0524", "00591-3496"], 'arg1 failed'

    def test_arg2(self):
        args = TestMyModule.args_tuple(None, [42, 69], None, None)
        res = main(args)
        assert res == [], 'arg2 failed'

if __name__ == '__main__':
    unittest.main()

When passing results from argparse.ArgumentParser.parse_args to a function, I sometimes use a namedtuple to mock arguments for testing.

import unittest
from collections import namedtuple
from my_module import main

class TestMyModule(TestCase):

    args_tuple = namedtuple('args', 'arg1 arg2 arg3 arg4')

    def test_arg1(self):
        args = TestMyModule.args_tuple("age > 85", None, None, None)
        res = main(args)
        assert res == ["55289-0524", "00591-3496"], 'arg1 failed'

    def test_arg2(self):
        args = TestMyModule.args_tuple(None, [42, 69], None, None)
        res = main(args)
        assert res == [], 'arg2 failed'

if __name__ == '__main__':
    unittest.main()

回答 8

为了测试CLI(命令行界面),而不是命令输出,我做了类似的事情

import pytest
from argparse import ArgumentParser, _StoreAction

ap = ArgumentParser(prog="cli")
ap.add_argument("cmd", choices=("spam", "ham"))
ap.add_argument("-a", "--arg", type=str, nargs="?", default=None, const=None)
...

def test_parser():
    assert isinstance(ap, ArgumentParser)
    assert isinstance(ap, list)
    args = {_.dest: _ for _ in ap._actions if isinstance(_, _StoreAction)}
    
    assert args.keys() == {"cmd", "arg"}
    assert args["cmd"] == ("spam", "ham")
    assert args["arg"].type == str
    assert args["arg"].nargs == "?"
    ...

For testing CLI (command line interface), and not command output I did something like this

import pytest
from argparse import ArgumentParser, _StoreAction

ap = ArgumentParser(prog="cli")
ap.add_argument("cmd", choices=("spam", "ham"))
ap.add_argument("-a", "--arg", type=str, nargs="?", default=None, const=None)
...

def test_parser():
    assert isinstance(ap, ArgumentParser)
    assert isinstance(ap, list)
    args = {_.dest: _ for _ in ap._actions if isinstance(_, _StoreAction)}
    
    assert args.keys() == {"cmd", "arg"}
    assert args["cmd"] == ("spam", "ham")
    assert args["arg"].type == str
    assert args["arg"].nargs == "?"
    ...