如何判断命令行中是否实际指定了argparse参数?

24
我正在使用argparse通过命令行指定的值更新配置字典。由于我只想更新在命令行中显式提及数值的配置值,因此我尝试通过检查每个动作是否为未指定参数来识别未指定参数。如果 getattr(args, action.dest) == action.default 或类型转换后的参数相等,则说明该参数未被指定。然后我将更新所有这些参数对应的字典值。
但是,如果我在命令行中明确指定一个与默认参数相同的参数,则此方法会失败。是否有可能在argparser中标识这些明确指定的参数,或者我必须在sys.argv中手动标识它们呢?
谢谢!

为什么会有影响?如果参数和默认值相同,为什么还要考虑呢? - Morgan Thrapp
https://dev59.com/XKPia4cB1Zd3GeqPzX0s#57663956 - Benjamin Du
2
举个例子说明我的用例:在设置参数值时设置优先级;用户从命令中指定 > 用户从配置文件中指定 > 解析器中的默认值。 - THN
1
@THN 这正是我的使用情况!很高兴看到我在这里不孤单! - rayryeng
5个回答

13

如果您希望使用argparse并能够指定默认值,则可以使用两个解析器的简单解决方案。

I. 定义您的主要解析器,并使用适当的默认值解析所有参数:

parser = argparse.ArgumentParser()
parser.add_argument('--test1', default='meaningful_default1')
parser.add_argument('--test2', default='meaningful_default2')
...
args, unparsed = parser.parse_known_args()

II. 使用argument_default = argparse.SUPPRESS定义一个辅助解析器,以排除未指定的参数。将主要解析器中的所有参数添加到辅助解析器中,但不包括任何默认值:

II. 使用argument_default=argparse.SUPPRESS 定义一个辅助解析器, 排除未指定的参数。将主解析器中的所有参数添加到辅助解析器中,但不含任何默认值:

aux_parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS)
for arg in vars(args): aux_parser.add_argument('--'+arg)
cli_args, _ = aux_parser.parse_known_args()

这不是一个非常优雅的解决方案,但它与argparse以及它的所有好处都很配合。


1
这似乎是唯一一个接近回答问题的答案。 - אלימלך שרייבר
1
谢谢。我最终使用了这个解决方案来满足我的需求。对此进行小修改是,如果参数的操作是 action=store_true,则需要在循环中的 add_argument 方法中添加 action=store_true。否则,action=store_true 的参数应该有第二个参数,否则程序将崩溃。没有简单的方法来区分 store_truestore_false,但是假设所有这些类型的变量都是其中之一,您可以在循环中放置一个 if 语句来检查参数值是否为 bool,然后添加操作。 - rayryeng
可以通过重用“parser”并设置“parser.set_defaults(**{arg: None for arg in vars(args)})”来避免额外的解析器。然后,在“aux_args = parser.parse_args()”之后,命令行参数就是“just” cmd_line_args = [arg for arg, value in vars(aux_args).items() if value is not None] - heiner
对于store_truestore_false,你可以这样做:for arg, val in vars(args).items(): if isinstance(val, bool): if val: aux_parser.add_argument("--" + arg, action="store_true") else: aux_parser.add_argument("--" + arg, action="store_false") else: aux_parser.add_argument("--" + arg) - undefined

6
您可以使用自定义操作来确定参数的值是默认设置还是在命令行上设置的:
import argparse

class FooAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, values)
        setattr(namespace, self.dest+'_nondefault', True)

parser = argparse.ArgumentParser()
parser.add_argument('--test0', default='meaningful_default', action=FooAction)
parser.add_argument('--test1', default='meaningful_default', action=FooAction)

args = parser.parse_args('--test0 2'.split())

if hasattr(args, 'test0_nondefault'):
    print('argument test0 set on command line')
else:
    print('default value of test0 is used')

if hasattr(args, 'test1_nondefault'):
    print('argument test1 set on command line')
else:
    print('default value of test1 is used')

6
parser在解析过程中(在_parse_known_args方法中)维护一个seen_actions集合对象。解析结束时,它会将此集合与必需的参数(使用required=True标记的参数)进行比较,并可能发出错误。互斥组也使用了一种变体。
但是,该变量无法在该函数之外使用。因此,除非有某种“钩子”可以让您在parse_args操作中应用自己的测试,否则最好的选择是测试默认值。或者您可以查看sys.argv[1:]
默认值defaultNone。对于此目的来说,这很好,因为用户无法给出此值。也就是说,在任何正常的type方法中,都没有将字符串转换为None的方法。
parser.add_argument('--foo') # default=None
...
if args.foo is None:
    # clearly foo has not been specified.
    args.foo = 'the real default'
else:
    # foo was specified
    pass

6
由于我依赖于非None默认值,因此这并不是一个真正的选择。现在我正在循环遍历所有操作时使用any([arg.startswith(option) for arg in sys.argv[1:] for option in a.option_strings])。这似乎有效。 - aem
4
这句话的意思是“这几乎完全违背了default=参数的目的”。 - n611x007
1
当你深入细节时,处理默认值会变得非常复杂。在这种情况下,方便地设置默认值和知道用户是否提供了值(即使是默认值)会产生冲突。 - hpaulj

5
您可以使用 "const" 和 "default" 的组合来模拟您想要的效果。在这种情况下,"nargs" 必须是 '?'。例如:
args_group.add_argument('--abc',
                         metavar='',
                         nargs='?',
                         action='store',
                         const='github.com',
                         default='',
                         help='blabla [ default: %(const)s ]')

不同的命令行参数会影响abc的值。

当命令行中没有abc时:

abc =

当使用--abc参数时:

abc = github.com

当使用--abc stackoverflow.com参数时:

abc = stackoverflow.com

因此,当abc为空时,您将知道它在命令行中不存在,例如:

if not args.abc:
    # to something

0

一个解决方法是通过独特的物体。而不是

parser.add_argument("--testdir", default="tests/")

我们可以做到
testdir_default = "tests/"
parser.add_argument("--testdir", default=testdir_default)
args = parser.parse_args()

if args.testdir is testdir_default:
   ...
else: 
   ...

有一个注意事项:Python会缓存某些常用的值,特别是介于-5和256之间的整数。
x = 1
y = 1
x is y  # True

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