我正在尝试使用
argparse
将参数转换为 timedelta
对象。我的程序读取用户提供的字符串并将它们转换为各种 datetime
对象以供以后使用。但我无法正确处理 filter_length
参数。我的代码:
import datetime
import time
import argparse
def mkdate(datestring):
return datetime.datetime.strptime(datestring, '%Y-%m-%d').date()
def mktime(timestring):
return datetime.datetime.strptime(timestring, '%I:%M%p').time()
def mkdelta(deltatuple):
return datetime.timedelta(deltatuple)
parser = argparse.ArgumentParser()
parser.add_argument('start_date', type=mkdate, nargs=1)
parser.add_argument('start_time', type=mktime, nargs=1, )
parser.add_argument('filter_length', type=mkdelta, nargs=1, default=datetime.timedelta(1))#default filter length is 1 day.
我运行程序,传递
1
作为 timedelta
值(我只希望它是一天):
> python program.py 2012-09-16 11:00am 1
但是我收到以下错误:
>>> program.py: error: argument filter_length: invalid mkdelta value: '1'
我不明白为什么该值无效。如果我单独调用 mkdelta 函数,如下所示:
mkdelta(1)
print mkdelta(1)
返回:
datetime.timedelta(1)
1 day, 0:00:00
这正是我正在寻找的价值。有人可以帮我弄清楚如何使用
argparse
正确进行此转换吗?
注意到错误消息中
'1'
周围的引号了吗?您将一个字符串传递给 mkdelta,而在测试代码中,您传递一个整数。
你的函数不处理字符串参数,这是 argparse 传递给它的;拨打
int()
:
def mkdelta(deltatuple):
return datetime.timedelta(int(deltatuple))
如果您需要支持超过天数,则必须找到一种方法来解析传入的参数到 timedelta 参数。
例如,您可以支持
d
、h
、m
或 s
后缀来表示天、小时、分钟或秒:
_units = dict(d=60*60*24, h=60*60, m=60, s=1)
def mkdelta(deltavalue):
seconds = 0
defaultunit = unit = _units['d'] # default to days
value = ''
for ch in list(str(deltavalue).strip()):
if ch.isdigit():
value += ch
continue
if ch in _units:
unit = _units[ch]
if value:
seconds += unit * int(value)
value = ''
unit = defaultunit
continue
if ch in ' \t':
# skip whitespace
continue
raise ValueError('Invalid time delta: %s' % deltavalue)
if value:
seconds = unit * int(value)
return datetime.timedelta(seconds=seconds)
现在您的
mkdelta
方法接受更完整的增量,甚至仍然是整数:
>>> mkdelta('1d')
datetime.timedelta(1)
>>> mkdelta('10s')
datetime.timedelta(0, 10)
>>> mkdelta('5d 10h 3m 10s')
datetime.timedelta(5, 36190)
>>> mkdelta(5)
datetime.timedelta(5)
>>> mkdelta('1')
datetime.timedelta(1)
默认单位是天。
您可以使用自定义操作来收集所有剩余的参数并将它们解析为
timedelta
。
这将允许您编写 CLI 命令,例如
% test.py 2012-09-16 11:00am 2 3 4 5
datetime.timedelta(2, 3, 5004) # args.filter_length
您还可以为
--days
、--seconds
等提供可选参数,这样您就可以编写 CLI 命令,例如
% test.py 2012-09-16 11:00am --weeks 6 --days 0
datetime.timedelta(42) # args.filter_length
% test.py 2012-09-16 11:00am --weeks 6.5 --days 0
datetime.timedelta(45, 43200)
import datetime as dt
import argparse
def mkdate(datestring):
return dt.datetime.strptime(datestring, '%Y-%m-%d').date()
def mktime(timestring):
return dt.datetime.strptime(timestring, '%I:%M%p').time()
class TimeDeltaAction(argparse.Action):
def __call__(self, parser, args, values, option_string = None):
# print '{n} {v} {o}'.format(n = args, v = values, o = option_string)
setattr(args, self.dest, dt.timedelta(*map(float, values)))
parser = argparse.ArgumentParser()
parser.add_argument('start_date', type = mkdate)
parser.add_argument('start_time', type = mktime)
parser.add_argument('--days', type = float, default = 1)
parser.add_argument('--seconds', type = float, default = 0)
parser.add_argument('--microseconds', type = float, default = 0)
parser.add_argument('--milliseconds', type = float, default = 0)
parser.add_argument('--minutes', type = float, default = 0)
parser.add_argument('--hours', type = float, default = 0)
parser.add_argument('--weeks', type = float, default = 0)
parser.add_argument('filter_length', nargs = '*', action = TimeDeltaAction)
args = parser.parse_args()
if not args.filter_length:
args.filter_length = dt.timedelta(
args.days, args.seconds, args.microseconds, args.milliseconds,
args.minutes, args.hours, args.weeks)
print(repr(args.filter_length))
这个要点似乎可以解决您的问题:https://gist.github.com/jnothman/4057689
以防万一有人来到这里希望向参数解析器添加
pandas.Timedelta
(就像我刚才所做的那样),事实证明以下工作正常:
parser = argparse.ArgumentParser()
parser.add_argument('--timeout', type=pd.Timedelta)
然后:
>>> parser.parse_args(['--timeout', '24 hours'])
Namespace(timeout=Timedelta('1 days 00:00:00'))
为了补充 Martijn 的精彩答案,这里是他发布的函数的一个变体,可以扩展到数周、数月甚至数年。它稍微复杂一点,因为它需要处理多字符时间单位,但我认为增加的复杂性值得额外的功能。它也被移植到 Python 3。在这里
import datetime
def parse_delay_into_timedelta(delta: str):
second = 1
minute = second * 60
hour = minute * 60
day = hour * 24
week = day * 7
month = week * 4
year = month * 12
units = {"s": second,
"m": minute,
"h": hour,
"d": day,
"w": week,
"mo": month,
"y": year}
# We need this to handle multi-character units
# like months
longest = max(len(k) for k in units.keys())
seconds = 0
default_unit = unit = units['d'] # Defaults to days
value = ""
delta = delta.strip().lower()
i = 0
while i < len(delta):
ch = delta[i]
i += 1
# isdigit will return True even for things like numbers
# in Arabic and stuff. For my use case this is undesirable,
# but if you care about that, just swap isnumeric() for isdigit()
if ch.isnumeric():
value += ch
# Only restrict ourselves to ASCII letters. Again, this is specific
# to my use-case. Can edit as necessary
elif ch.isalpha() and ch.isascii():
unit_name = ch
j = i
# Try to parse as many characters of the unit as possible,
# but stop once we either reach the end of the string or
# the length of the current (potential) unit equals the
# length of the longest one we know
while j < len(delta) and len(unit_name) < longest:
if (char := delta[j]).isalpha() and char.isascii():
unit_name += char
else:
break
j += 1
# If the unit was more than one character long, we
# have to skip the next len(unit_name) - 1 characters,
# so we don't try to parse 'mo' and then 'o' again, for
# example. If the unit was one character long, this value
# will just be zero
i += len(unit_name) - 1
try:
unit = units[unit_name]
except KeyError:
raise ValueError(f"invalid unit {unit_name!r}")
if value:
seconds += unit * int(value)
value = ""
unit = default_unit
elif ch in ' \t\r':
# Skip useless characters
continue
else:
# Unknown character
raise ValueError(f"Invalid character {ch!r} in time delta: {delta!r}")
if value:
seconds = unit * int(value)
return datetime.timedelta(seconds=seconds)
你可以这样使用它:
>>> parse_delay_into_timedelta("2y 1mo 1w 5d 6m 1h 3s")
datetime.timedelta(days=712, seconds=3963)
>>> parse_delay_into_timedelta("1s")
datetime.timedelta(seconds=1)
>>> parse_delay_into_timedelta("10s")
datetime.timedelta(seconds=10)
>>> parse_delay_into_timedelta("15h")
datetime.timedelta(seconds=54000)