如何使用argparse读取Python脚本中的日期参数

605 阅读4分钟

我敢打赌,在我的职业生涯中,我已经写了数以百计的Python脚本,在命令行上接受一个日期参数。我经常需要一些简短的实用脚本,可以在一个由日期限制的数据子集上运行。我在Python中解析命令行选项的首选方法是使用核心模块argparse 。但是argparse并没有内置对日期处理的支持。我第一次使用它时,是完成参数解析后才进行所有的日期解析的。在这篇文章中,我将向你展示两种简单的方法,将日期参数解析直接添加到你的命令行解析中。

首先,让我们快速回顾一下 argparse以及它是如何工作的。然后我们将添加一个简单的日期解析器,接着是一个稍微复杂的解析器。

argparse 基础知识

argparse 模块有一个很好的教程,如果你以前没有用Python写过命令行脚本,或者没有使用过argparse ,我会推荐你作为一个起点。我将假设你至少知道如何创建一个ArgumentParser 并让它解析一些不同类型的基本参数。让我们看一个例子。注意,由于这个例子的代码是在Jupyter笔记本里面运行的,所以我总是在这里传入我自己的参数,但是在你的命令行脚本中,你只需要调用parser.parse_args() 来解析命令行中的参数:

import argparse

parser = argparse.ArgumentParser()
# since this example is running in Jupyter, I'll always pass in the arguments 
try:
    parser.parse_args(["-h"])
except SystemExit: # calling help will call SystemExit, we can catch this instead
    pass
usage: ipykernel_launcher.py [-h]

optional arguments:
  -h, --help  show this help message and exit

好的,让我们添加一些不同类型的参数:

parser = argparse.ArgumentParser()
parser.add_argument('-n', '--number', type=int, help='Number of times to run')
parser.add_argument('-x', '--extension', type=str, help='File extension to search for')
parser.add_argument('-d', '--debug', action='store_true', help='Turn on debug logging')

parser.parse_args(["-n", "10", "--extension", ".xls", "-d"])
Namespace(debug=True, extension='.xls', number=10)

现在让我们用某种日期参数来试试。让我们做一些天真的事情,看看我们是否可以将类型设置为日期。这样就可以了吗?

import datetime

parser = argparse.ArgumentParser()
parser.add_argument('-s', '--start', type=datetime.date, help='Set a start date')

try:
    parser.parse_args(["-s", "2022-01-01"])
except:
    pass
usage: ipykernel_launcher.py [-h] [-s START]
ipykernel_launcher.py: error: argument -s/--start: invalid date value: '2022-01-01'

唉,没那么容易。我没有给你看完整的输出(我把这个放在了try/except中),但它不能工作的原因是datetime.date ,在它的结构体中不接受一个字符串参数。parser.add_argument 中的type 参数可以是任何接受单个字符串的可调用参数,而在这种情况下,argparse 只是将该字符串传递给date 构造函数,这并不奏效。它正在期待三个int参数。

所以让我们像这样做一些基本的日期解析:

parser = argparse.ArgumentParser()
parser.add_argument('-s', '--start',
                    type=lambda d: datetime.datetime.strptime(d, '%Y-%m-%d').date(),
                    help='Set a start date')
parser.parse_args(["-s", "2022-01-01"])
Namespace(start=datetime.date(2022, 1, 1))

我将把它分解一下。lambda 需要一个参数。它将通过命令行字符串令牌(在本例中是2022-01-01 ),并以正确的格式将其传入datetime.datetime.strptime ,然后对其调用date() ,只返回日期部分。

更复杂的日期解析

但是如果你想接受多种格式的日期呢?

你可以创建一个更复杂的lambda或者一个单独的函数来做这个。但事实证明,当然有人已经创建了这个,它叫做 dateutil.如果你的环境中还没有它,你可以用pip install python-dateutil 来安装它。parse 方法可以很好地从一个字符串中得到一个有效的日期。

为了将这种解析与我们的参数解析联系起来,argparse API可以通过自定义的Action 类进行扩展。动作类只需要实现__call__ 方法,以接受正确的参数并正确处理传递给它的值。函数签名包括argparse.Namespace 对象,建议你用正确的名称将处理结果存储在命名空间中,使用setattr 。我们通过指定它作为我们的参数的动作来连接它和参数解析器:

from dateutil.parser import parse, ParserError

class DateParser(argparse.Action):
    def __call__(self, parser, namespace, values, option_strings=None):
        setattr(namespace, self.dest, parse(values).date())

parser = argparse.ArgumentParser()
parser.add_argument('-s', '--start',
                    action=DateParser,
                    help='Set a start date')
parser.parse_args(["-s", "1/1/2022"])
Namespace(start=datetime.date(2022, 1, 1))

这适用于相当广泛的日期格式。如果出现解析错误,它还会引发一个ValueError (实际上是它的一个子类):

assert parser.parse_args(["-s", "1/1/2022"]).start == datetime.date(2022,1,1)
assert parser.parse_args(["-s", "2022-1-1"]).start == datetime.date(2022,1,1)
assert parser.parse_args(["-s", "Jan1,2022"]).start == datetime.date(2022,1,1)
assert parser.parse_args(["-s", "1-Jan-2022"]).start == datetime.date(2022,1,1)
assert parser.parse_args(["-s", "1-Jan-22"]).start == datetime.date(2022,1,1)
assert parser.parse_args(["-s", "January, 1 2022"]).start == datetime.date(2022,1,1)
assert parser.parse_args(["-s", "January 1st, 2022"]).start == datetime.date(2022,1,1)
try:
    parser.parse_args(["-s", "tomorrow"])
except ValueError as ex:
    print(ex)
Unknown string format: tomorrow

就这样,你现在可以轻松地在你的Python脚本中添加灵活的命令行选项的日期解析。我经常要这样做,所以我通常把这段代码从现有的脚本中复制出来。 我希望你觉得这篇文章有用。