Python中的参数依赖关系 - 无法使其工作

3

我正在尝试在我的脚本中添加参数依赖关系。想法是--clone参数将需要非空的--gituser

在查看这个例子后,我尝试了以下方法:

In [93]: class CloneAction(argparse.Action):
    ...:     def __call__(self, parser, namespace, _):
    ...:         if not namespace.git_user and namespace.clone:
    ...:             parser.error('"--clone" requires legal git user')
    ...:             
In [94]: parser = argparse.ArgumentParser()

In [95]: parser.add_argument('-g', '--gituser', dest='git_user', type=str, default='', action=CloneAction)
Out[95]: CloneAction(option_strings=['-g', '--gituser'], dest='git_user', nargs=None, const=None, default='', type=<type 'str'>, choices=None, help=None, metavar=None)

In [96]: parser.add_argument('--clone', action='store_true', default=False)
Out[96]: _StoreTrueAction(option_strings=['--clone'], dest='clone', nargs=0, const=True, default=False, type=None, choices=None, help=None, metavar=None)

哎呀,它没起作用

In [97]: parser.parse_args(['--clone'])
Out[97]: Namespace(clone=True, git_user='')

我做错了什么?


1
请问您能否包含您的CloneAction实现? - Martijn Pieters
另外,请注意--clone操作只是_StoreTrueAction,不会检查任何依赖项。你必须使用一个特殊的操作类型在那里来检查它,因为没有传递-g参数来触发CloneAction被检查。 - Martijn Pieters
@MartijnPieters 不好意思,我错过了。已添加。我需要为两个参数定义自定义操作吗? - volcano
这要看情况;如果没有设置 --clone,提供 --gituser 是否会出错? - Martijn Pieters
1个回答

8
这种相互依赖的参数在解析后更容易实现。
args = parser.parse_args()
if not namespace.git_user and namespace.clone:
    parser.error('"--clone" requires legal git user')

此时,git_userclone均已被解析并具有其最终值。
按照您的实现方式,只有在出现--gituser参数时才会运行自定义操作。 因此,我认为如果您给出--gituser而没有给出--clone,它将引发错误。
您可以为--clone提供类似的自定义操作,但还必须处理store_true细节。 那么--clone --gituser value序列应该发生什么? 在解析gituser值之前,将运行clone操作。 这样的测试遇到一些棘手的参数顺序问题。
还有其他几个问题:
  • 您的自定义操作不存储任何值,无论有无错误。 最好自定义store子类。
  • 自定义操作应引发argparse.ArgumentError而不是直接调用parser.error
test / test_argparse.py单元测试文件中有一个相互测试的自定义操作示例。 但这只是一种玩具,验证了这样的代码是否允许。
您可以理论上实现一个--clone操作,该操作设置--gituser操作的required属性。 这样,如果未使用--gituser,则parse_args的最终required操作测试将引发错误。 但这需要保存对在out [95]中显示的操作的引用(或在parse._actions列表中找到该操作)。 可行但混乱。
下面是来自test / test_argparse.py的一对交互式自定义操作类的示例。
class OptionalAction(argparse.Action):

    def __call__(self, parser, namespace, value, option_string=None):
        try:
            # check destination and option string
            assert self.dest == 'spam', 'dest: %s' % self.dest
            assert option_string == '-s', 'flag: %s' % option_string
            # when option is before argument, badger=2, and when
            # option is after argument, badger=<whatever was set>
            expected_ns = NS(spam=0.25)
            if value in [0.125, 0.625]:
                expected_ns.badger = 2
            elif value in [2.0]:
                expected_ns.badger = 84
            else:
                raise AssertionError('value: %s' % value)
            assert expected_ns == namespace, ('expected %s, got %s' %
                                              (expected_ns, namespace))
        except AssertionError:
            e = sys.exc_info()[1]
            raise ArgumentParserError('opt_action failed: %s' % e)
        setattr(namespace, 'spam', value)

NSargparse.Namespace的简写。

class PositionalAction(argparse.Action):

    def __call__(self, parser, namespace, value, option_string=None):
        try:
            assert option_string is None, ('option_string: %s' %
                                           option_string)
            # check destination
            assert self.dest == 'badger', 'dest: %s' % self.dest
            # when argument is before option, spam=0.25, and when
            # option is after argument, spam=<whatever was set>
            expected_ns = NS(badger=2)
            if value in [42, 84]:
                expected_ns.spam = 0.25
            elif value in [1]:
                expected_ns.spam = 0.625
            elif value in [2]:
                expected_ns.spam = 0.125
            else:
                raise AssertionError('value: %s' % value)
            assert expected_ns == namespace, ('expected %s, got %s' %
                                              (expected_ns, namespace))
        except AssertionError:
            e = sys.exc_info()[1]
            raise ArgumentParserError('arg_action failed: %s' % e)
        setattr(namespace, 'badger', value)

它们被用于

parser = argparse.ArgumentParser()
parser.add_argument('-s', dest='spam', action=OptionalAction,
        type=float, default=0.25)
parser.add_argument('badger', action=PositionalAction,
        type=int, nargs='?', default=2)

需要搭配以下内容一起使用:

'-s0.125' producing: NS(spam=0.125, badger=2)),
'42',                NS(spam=0.25, badger=42)),
'-s 0.625 1',        NS(spam=0.625, badger=1)),
'84 -s2',            NS(spam=2.0, badger=84)),

这是一种可以进行的交叉检查的示例。 但我要重申,通常最好在解析后处理交互,而不是在解析期间处理。

至于实现问题 - 如果用户没有给出--gituser,则永远不会调用您的自定义操作。 optionalAction.__call__仅在使用该参数时使用。 positionals始终被使用,但不是optionals


谢谢回答,但这不是我要找的。我想了解具体的实现细节。 - volcano
关于argparse如何实现解析或者如何通过自定义操作链接两个参数的具体细节? - hpaulj
2
我已经加入了一个与argparse单元测试中相互作用的动作类示例。也许它会给你一些实现这样一对动作类的想法。 - hpaulj
谢谢!这正是我在找的。 - volcano

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