Argparse:如果存在'x'则需要必选参数'y'

165

我有以下要求:

./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.')

1
插播一下,但我想提一下我的库 joffrey。它可以让你做这个问题想要的事情,例如,不需要让你自己验证所有内容(就像被接受的答案中那样),也不需要依赖于一个有漏洞的 hack(就像第二名得票最高的答案)。 - user4698348
对于所有来到这里的人,另一个神奇的解决方案:https://dev59.com/El8e5IYBdhLWcg3wja_5#44210638 - Tomerikoo
6个回答

188

在argparse中,没有任何选项可以使选项集相互包含。

处理这种情况的最简单方法是:

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

实际上已经有一个提出增强建议的开放PR:

https://github.com/python/cpython/issues/55797

3
那就是我最终做的事情。 - asudhak
33
谢谢您提供 parser.error 方法,这正是我所需要的! - MarSoft
8
你不应该使用 'or' 吗?毕竟你需要两个参数。如果 args.prox 为真且 args.lport 或 args.rport 中有一个为空,则执行以下操作: - yossiz74
2
你可以使用 not args.lport 代替 args.lport is None,这样会更符合 Python 的风格。 - CGFoX
13
这将阻止您将 --lport--rport 设为 0,尽管这可能是程序的有效输入。 - borntyping
显示剩余5条评论

86

你在谈论有条件的必需参数。就像@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将会根据用户是否使用了--prox而接收到TrueFalse。这也保证了-lport-rport之间具有独立的行为。


13
请注意,ArgumentParser 可用于解析除 sys.argv 以外的列表中的参数,如果这样做,则该代码将失败。 - BallpointBen
2
如果使用 --prox=<value> 语法,这也会失败。 - fnkr
2
然后 required='--prox' in " ".join(sys.argv) - debuti
还有一些情况是解析器在使用前就构建了,例如 Django 命令,这阻止了这种方法的实用性。 - artu-hnrq

20
如果存在 --prox,则使用 parser.parse_known_args() 方法,并将 --lport--rport 参数添加为所需参数。
# 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不会显示在帮助消息中

这看起来非常整洁。不幸的是,在需要--prox作为必需参数的情况下,调用parse_args(…)会失败,因为必需参数不在rem_args列表中。 - Hermann
我不确定你所说的"--prox是一个必需参数"是什么意思,你是不是指--prox不是一个可选的参数?能否请你详细解释一下(最好附上一个例子)? - Aditya Sriram
当然,这里就是你要的。这个例子说明了一个常见参数不是标志而是选择的情况。我现在意识到问题明确要求store_true变体,所以答案仍然非常好。 - Hermann
嗯,这确实是一个有趣的情况。我尝试过使用parse_known_argsparse_args进行一些尝试,但最好的解决方案看起来非常笨拙。所以,在这种情况下,我认为我会选择borntyping的答案(https://stackoverflow.com/a/19414853/1614140)。 - undefined

8

如果没有设置prox,您是否使用lport?如果没有,为什么不将lportrport作为prox的参数呢?例如:

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

这可以帮助用户避免输入。测试 if args.prox is not None:if args.prox: 一样简单。


3
举例说明,当nargs>1时,您将在解析的参数中获得一个列表等,您可以按照通常的方式进行处理。例如,a,b = args.proxa = args.prox[0]等。 - Dannid

2
最初的回答对我非常有用!由于没有测试的所有代码都是错误的,因此这是我如何测试接受的答案。 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 - 退出错误

2
“没有测试的代码都是有问题的” LOL 谁说的? - TheEagle

0
我是这样做的:
if t or x or y:
    assert t and x and y, f"args: -t, -x and -y should be given together"

你把这个逻辑放在哪里了? - artu-hnrq

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接