【问题标题】:Which of these is pythonic? and Pythonic vs. Speed以下哪个是蟒蛇?和 Python 与速度
【发布时间】:2011-01-17 14:23:37
【问题描述】:

我是 python 新手,刚刚写了这个模块级函数:

def _interval(patt):
    """ Converts a string pattern of the form '1y 42d 14h56m'
    to a timedelta object.
    y - years (365 days), M - months (30 days), w - weeks, d - days,
    h - hours, m - minutes, s - seconds"""

    m = _re.findall(r'([+-]?\d*(?:\.\d+)?)([yMwdhms])', patt)

    args = {'weeks': 0.0,
            'days': 0.0,
            'hours': 0.0,
            'minutes': 0.0,
            'seconds': 0.0}

    for (n,q) in m:
        if q=='y':
            args['days'] += float(n)*365
        elif q=='M':
            args['days'] += float(n)*30
        elif q=='w':
            args['weeks'] += float(n)
        elif q=='d':
            args['days'] += float(n)
        elif q=='h':
            args['hours'] += float(n)
        elif q=='m':
            args['minutes'] += float(n)
        elif q=='s':
            args['seconds'] += float(n)

    return _dt.timedelta(**args)

我的问题是这里的 for 循环,即长 if elif 块,我想知道是否有更 Pythonic 的方法。
所以我将函数重写为:

def _interval2(patt):

    m = _re.findall(r'([+-]?\d*(?:\.\d+)?)([yMwdhms])', patt)

    args = {'weeks': 0.0,
            'days': 0.0,
            'hours': 0.0,
            'minutes': 0.0,
            'seconds': 0.0}

    argsmap = {'y': ('days', lambda x: float(x)*365),
               'M': ('days', lambda x: float(x)*30),
               'w': ('weeks', lambda x: float(x)),
               'd': ('days', lambda x: float(x)),
               'h': ('hours', lambda x: float(x)),
               'm': ('minutes', lambda x: float(x)),
               's': ('seconds', lambda x: float(x))}

    for (n,q) in m:
        args[argsmap[q][0]] += argsmap[q][1](n)

    return _dt.timedelta(**args)

我使用 timeit 模块测试了这两个代码的执行时间,发现第二个代码花费了大约 5-6 秒(默认重复次数)。

所以我的问题是:
1. 哪个代码被认为更 Python 化?
2. 写这个函数还有更pythonic的吗?
3. Python 性与编程的其他方面(如本例中的速度)之间的权衡如何?

附言我对优雅的代码有点强迫症。

已编辑_interval2在看到this answer后:

argsmap = {'y': ('days', 365),
           'M': ('days', 30),
           'w': ('weeks', 1),
           'd': ('days', 1),
           'h': ('hours', 1),
           'm': ('minutes', 1),
           's': ('seconds', 1)}

for (n,q) in m:
    args[argsmap[q][0]] += float(n)*argsmap[q][1]

【问题讨论】:

  • 到底为什么你会得到一个声称月份是 30 天的格式的时间增量,而实际上它们大多不是? :)
  • @Lennart 哈哈!是的,它们是多余的(即使是 365 天,如果您考虑闰年)。由于这些函数是关于时间间隔的,不用于计算未来的日期,所以我将它们作为附加功能保留。
  • 不管它们是多余的,问题是它们是错误的。 2011 年 1 月 4 日和 2011 年 3 月 4 日之间的差值为 59 天。 2011 年 7 月 4 日和 2011 年 9 月 4 日之间的差异是 62 天。这两个差异都是 2 个月,但不是根据您的语法。计算未来日期是 timedeltas 的全部内容......
  • 如果你想成为强迫症,那么请成为强迫症的错误。您发布的代码忽略了“5y 3”中的“3”,给出了一个“ValueError:float()的无效文字:”用于“-d”和“ValueError:float()的空字符串”以合理的外观“3.d”。此外,在 "_re" 和 "_dt" 中使用前导 _ 也不是 Pythonic。

标签: datetime python timedelta


【解决方案1】:

您似乎每次解析时都会创建很多 lambda。你真的不需要一个 lambda,只需要一个乘数。试试这个:

def _factor_for(what):
    if what == 'y': return 365
    elif what == 'M': return 30
    elif what in ('w', 'd', 'h', 's', 'm'): return 1
    else raise ValueError("Invalid specifier %r" % what)

for (n,q) in m:
    args[argsmap[q][0]] += _factor_for([q][1]) * n

不要将_factor_for 设为方法的本地函数或方法,以加快速度。

【讨论】:

  • 天啊!看到您的评论后,我更改了_interval2 功能。 +1
  • 小错字:您将_factor_for 定义为函数,但将其用作字典。
【解决方案2】:

(我没有计时,但是)如果你要经常使用这个函数,可能值得预编译正则表达式。

这是我对你的功能的看法:

re_timestr = re.compile("""
    ((?P<years>\d+)y)?\s*
    ((?P<months>\d+)M)?\s*
    ((?P<weeks>\d+)w)?\s*
    ((?P<days>\d+)d)?\s*
    ((?P<hours>\d+)h)?\s*
    ((?P<minutes>\d+)m)?\s*
    ((?P<seconds>\d+)s)?
""", re.VERBOSE)

def interval3(patt):
    p = {}
    match = re_timestr.match(patt)
    if not match: 
        raise ValueError("invalid pattern : %s" % (patt))

    for k,v in match.groupdict("0").iteritems():
        p[k] = int(v) # cast string to int

    p["days"] += p.pop("years")  * 365 # convert years to days
    p["days"] += p.pop("months") * 30  # convert months to days
    return datetime.timedelta(**p)

更新

来自this question,看起来预编译正则表达式模式并没有带来明显的性能提升,因为 Python 缓存并重用它们。您只节省了检查缓存所需的时间,除非您重复多次,否则可以忽略不计。

更新2

正如您正确指出的那样,此解决方案不支持interval3("1h 30s" + "2h 10m")。但是,timedelta 支持算术运算,这意味着您仍然可以将其表示为 interval3("1h 30s") + interval3("2h 10m")

此外,正如问题中的一些 cmets 所提到的,您可能希望避免在输入中支持“年”和“月”。 timedelta 不支持这些论点是有原因的。它不能被正确处理(而且不正确的代码几乎永远不会优雅)。

这是另一个版本,这次支持浮点、负值和一些错误检查。

re_timestr = re.compile("""
    ^\s*
    ((?P<weeks>[+-]?\d+(\.\d*)?)w)?\s*
    ((?P<days>[+-]?\d+(\.\d*)?)d)?\s*
    ((?P<hours>[+-]?\d+(\.\d*)?)h)?\s*
    ((?P<minutes>[+-]?\d+(\.\d*)?)m)?\s*
    ((?P<seconds>[+-]?\d+(\.\d*)?)s)?\s*
    $
""", re.VERBOSE)

def interval4(patt):
    p = {}
    match = re_timestr.match(patt)
    if not match: 
        raise ValueError("invalid pattern : %s" % (patt))

    for k,v in match.groupdict("0").iteritems():
        p[k] = float(v) # cast string to int

    return datetime.timedelta(**p)

示例用例:

>>> print interval4("1w 2d 3h4m")  # basic use
9 days, 3:04:00

>>> print interval4("1w") - interval4("2d 3h 4m") # timedelta arithmetic 
4 days, 20:56:00

>>> print interval4("0.3w -2.d +1.01h") # +ve and -ve floats 
3:24:36

>>> print interval4("0.3x") # reject invalid input 
Traceback (most recent call last): 
  File "date.py", line 19, in interval4
    raise ValueError("invalid pattern : %s" % (patt)) 
ValueError: invalid pattern : 0.3x

>>> print interval4("1h 2w") # order matters 
Traceback (most recent call last):   
  File "date.py", line 19, in interval4
    raise ValueError("invalid pattern : %s" % (patt)) 
ValueError: invalid pattern : 1h 2w

【讨论】:

  • 我也有同样的想法,但这不会解析像“1w 1w 1w”或“1s 1y”这样的东西——这对我来说似乎比原来的正则表达式好得多,但可能不适合要求。
  • 我怀疑如果允许像“1w 1w 1w”这样的输入,人们会担心更多;应该是 3 周,还是 1 周,还是打错字?
  • 是的,我也想到了这个正则表达式。但我的有点支持鸭子打字:"1h30m" + "2h45m" 自动导致timedelta"4h15m"
【解决方案3】:

是的,有。请改用time.strptime

解析表示时间的字符串 根据格式。回报 返回的值是 struct_time 通过gmtime()localtime()

【讨论】:

  • time.strptime() 适用于 datetime 对象,而不适用于 timedelta。 2 个月 10 天不是 10-2 月。
猜你喜欢
  • 2018-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-04
  • 2016-11-02
  • 2017-08-30
相关资源
最近更新 更多