Python argparse:带有可选和位置参数的互斥参数

4
我希望使用argparse库来实现以下功能:
PROG --yesterday | begin-date [end-date]

我尝试结合互斥和参数组,但没有成功。
该程序应仅接受以下内容:
PROG --yesterday
PROG 2015-11-12
PROG 2015-11-12 2015-11-15

使用argparse可以完成这个任务吗?


感谢hpaulj。以下是最终结果:

import argparse
from datetime import datetime
import pytz


def argument_date(str_date):
    try:
        return datetime.strptime(str_date, "%Y-%m-%d").replace(tzinfo=pytz.utc)
    except ValueError as e:
        raise argparse.ArgumentTypeError(e)

parser = argparse.ArgumentParser(prog='PROG')
parser.usage = """PROG [-h] [--yesterday | start [end]]"""
parser.add_argument('start', type=argument_date, nargs='?', help='Start date (format YYYY-MM-DD)')
parser.add_argument('end', type=argument_date, nargs='?', help='End date (format YYYY-MM-DD)')
parser.add_argument('--yesterday', action='store_true', help='Only yesterday')

args = parser.parse_args()

if args.yesterday and args.start:
    raise parser.error("--yesterday option is not incompatible with start argument")

if not args.yesterday and not args.start:
    raise parser.error("--yesterday option or start argument should be filled")

if args.end and (args.start >= args.end):
    raise parser.error("end argument should be granter than start")

有没有真正的理由使用 --yesterday 而不是将 yesterday 作为 begin-date 的特殊值? - chepner
startend参数应该是一个日期时间对象,而不是一个简单的字符串。因此,我认为从数据角度来看它并不一致。 - Aurélien
所有的参数都是简单字符串,直到你将它们转换成datetime对象。在这样做之前,检查特殊情况(如“昨天”)是相对简单的。 - chepner
2个回答

4

--yesterday是多余的,因为它只是将start_date设置为昨天日期的快捷方式。相反,让"yesterday"成为start_date的可接受值。实际上,您可以将datetime泛化以允许其他缩写,以满足任何参数需求。例如:

def argument_date(str_date):
    # Not the most efficient to roundtrip like this, but
    # fits well with your existing code
    now = datetime.datetime.utcnow().date()
    if str_date == "yesterday":
        str_date = str(now - datetime.timedelta(1))
    elif str_date == "today"
        str_date = str(now)

    try:
        return datetime.strptime(str_date, "%Y-%m-%d").replace(tzinfo=pytz.utc)
    except ValueError as e:
        raise argparse.ArgumentTypeError(e)

完成这些步骤后,你的代码就变成了以下内容:
parser = argparse.ArgumentParser(prog='PROG')
parser.add_argument('start', type=argument_date, help='Start date (YYYY-MM-DD, yesterday, today)')
parser.add_argument('end', type=argument_date, nargs='?', help='End date (YYYY-MM-DD, yesterday, today)')

有点离题,但因为有效地使用了自定义类型 type=argument_date 而被赞同,我认为这是正确的做法。 - Fuujuhi

3

您最好的选择是在解析后测试值,如果需要,提供自己的自定义用法

mutually_exclusive_group可以与一个可选的位置参数一起使用,例如:

group = parser.add_mutually_exclusive_group()
group.add_argument('-y','--yesterday', action='store_true')
group.add_argument('dates',nargs='?')

我认为可以使用nargs='*',但是会出现ValueError: mutually exclusive arguments must be optional错误。

因此,一个可选的位置值是可以的,但是没有办法使用两个可选的位置值进行测试。

parser.add_argument('--yesterday',action='store_true')
parser.add_argument('start',nargs='?')
parser.add_argument('end',nargs='?')

然后测试args.yesterdayargs.start is Noneargs.end is None。如果其中某些组合是错误的,则引发parser.error('....')

只要您可以区分默认值和用户给定的值,在解析后进行测试就与您可能强制解析器执行的任何操作一样好。

考虑为您的用户提供什么使用消息也是一个好主意。例如:

例如:

PROG  [--yesterday | [start [end]]]

这不是argparse能自动生成的东西。


在这种情况下,nargs='*' 只有在您没有提供默认值时才会引发 ValueError。如果您使用 default=[],它应该可以与 nargs='*' 很好地配合使用。 - Mazino

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