python argparse 总是设置与可选参数相关的默认值

python argparse always set defaults related to an optional argument

我使用 python 3,我想将 argparse 设置为始终设置与我选择的预定义可选参数相关的默认键。

例如我将一些输入文件传递给脚本。可选参数指定输入文件是否应由脚本旋转。我得到了一个输入文件列表,但我还想要一个相同大小的列表,其中包含 'True' 用户想要旋转输入文件的位置,或者当用户没有做出选择时默认为 false

例如

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--input', action='append', required=True, help='Input pdf files.')
parser.add_argument('--rotate', action='store_true', default=False, help='Rotate the input file')
args = parser.parse_args()
print(args)

我用这个命令调用脚本:

python argparse_test.py --input 1.pdf --input 2.pdf --rotate

这是输出

Namespace(input=['1.pdf', '2.pdf'], rotate=True)

但我想要这个输出

Namespace(input=['1.pdf', '2.pdf'], rotate=[False,True])

请注意,--rotate 可以是我在 --input 文件上编写的 python 脚本必须在 --rotate 本身之前执行的任何操作。所以我需要知道哪个 --input 文件 --rotate 指的是

所以

python argparse_test.py --input 1.pdf --input 2.pdf --rotate

的意思是 - 对于 2.pdfrotate 设置为 True 因为它遵循该名称,但对于 1.pdf 将其保留为 False 因为没有 --rotate flag 跟随它?

这样不行。 --rotate--input 等带标记的参数可以以任何顺序出现。 store_true 参数不能像您的 --input.

那样在 append 模式下运行

一些可能有效的替代方案:

--input as nargs=2
python argparse_test.py --input 1.pdf 0 --input 2.pdf 1
Namespace(input=[['1.pdf','0'], ['2.pdf','1']])

--input as nargs='+'
python argparse_test.py --input 1.pdf --input 2.pdf 1
Namespace(input=[['1.pdf'], ['2.pdf','1']])

也就是说,用户在文件名后加一个字符串,表示是否旋转。

我本来打算为 rotate 建议 append_const,但无法将默认值插入列表。

我可以想象定义一对自定义操作 类 - 见下文。

我认为 Waylan 的建议是有 2 个 append 列表,一个用于应该轮换的文件,另一个用于不应该轮换的文件。

您如何在使用和帮助消息中表达这样的要求?在开发过程中对您来说似乎合乎逻辑的内容,对其他用户或六个月后的您自己来说可能并不那么明显。


自定义操作 类 似乎符合预期。这不是一个微不足道的定制,但也不是一个复杂或晦涩的定制。它确实需要对 argparse 代码有一些详细的了解。

import argparse

class Input_action(argparse._AppendAction):
    def __call__(self, parser, namespace, values, option_string=None):
        # assume attribute has already been set to []
        items = getattr(namespace, self.dest)
        items.append(values)
        setattr(namespace, self.dest, items)

        dest = 'file_rotate'
        values = False
        items = getattr(namespace, dest)
        items.append(values)
        setattr(namespace, dest, items)

class Rotate_action(argparse._StoreTrueAction):

    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, self.const)
        dest = 'file_rotate'  # not same as self.dest
        items = getattr(namespace, dest)
        if items:
            items[-1] = True
        setattr(namespace, dest, items)

parser = argparse.ArgumentParser()
parser.add_argument('-i','--input', action=Input_action)
parser.add_argument('-r','--rotate', action=Rotate_action)

parser.print_help()

# simpler to initialize these attributes here than in the Actions
ns = argparse.Namespace(input=[], file_rotate=[])
print parser.parse_args(namespace=ns)

输出:

1030:~/mypy$ python stack28967342.py -i one -r -i two -i three -r
usage: stack28967342.py [-h] [-i INPUT] [-r]

optional arguments:
  -h, --help            show this help message and exit
  -i INPUT, --input INPUT
  -r, --rotate
Namespace(file_rotate=[True, False, True], input=['one', 'two', 'three'], rotate=True)

注意生成的命名空间中的 2 个列表。 rotate 属性不是必需的,但删除它需要做更多的工作。 help 不表示任何关于选项的特殊配对。


这是一个备用动作对,适用于一个列表和具有名称和旋转属性的复合对象。推广这种方法可能更容易。

class MyObj(argparse._AttributeHolder):
    "simple class to hold name and various attributes"
    def __init__(self, filename):
        self.name = filename
        self.rotate = False
        # define other defaults here

class Input_action(argparse._AppendAction):
    def __call__(self, parser, namespace, values, option_string=None):
        items = argparse._ensure_value(namespace, self.dest, [])
        items.append(MyObj(values))
        setattr(namespace, self.dest, items)

class Rotate_action(argparse._StoreTrueAction):

    def __call__(self, parser, namespace, values, option_string=None):
        # with default=SUPPRESS, rotate does not appear in namespace
        dest = 'input'  # could make this a parameter
        items = getattr(namespace, dest)
        if items:
            items[-1].rotate = True
        # no need to set, since I'm just modifying an existing object

parser = argparse.ArgumentParser()
parser.add_argument('-i','--input', action=Input_action,
    help='create a default input object with this name')
parser.add_argument('-r','--rotate', action=Rotate_action, default=argparse.SUPPRESS,
    help = 'set rotate attribute of preceeding input object')

parser.print_help()
print parser.parse_args()

生成类似

的参数
Namespace(input=[MyObj(name='one', rotate=True), MyObj(name='two', rotate=False), MyObj(name='three', rotate=True)])