Что из этого является питоническим? и Pythonic против скорости - PullRequest
6 голосов
/ 17 января 2011

Я новичок в 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, и мне было интересно, есть ли более питонический способ сделать это.
Поэтому я переписал функцию как:

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. Какой код считается более питоническим?
2. Есть ли еще более питонический вариант написания этой функции?
3. Как насчет компромиссов между питоничностью и другими аспектами (например, скоростью в данном случае) программирования?

p.s. У меня вроде есть OCD для элегантного кода.

ИЗД. _interval2 после просмотра этого ответа :

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]

Ответы [ 3 ]

4 голосов
/ 17 января 2011

Кажется, вы создаете много лямбд каждый раз, когда разбираете.Вам действительно не нужна лямбда, просто множитель.Попробуйте это:

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 локальной функцией или методом, хотя, чтобы ускорить процесс.

3 голосов
/ 17 января 2011

(я не рассчитывал это, но) если вы собираетесь часто использовать эту функцию, возможно, стоит предварительно скомпилировать выражение регулярного выражения.

Вот мой взгляд на вашу функцию:

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)

update

Начиная с этот вопрос , похоже, что предварительная компиляция шаблонов регулярных выражений не приводит к заметному улучшению производительности, поскольку кэши Python и их повторное использование все равно используются.Вы экономите только время, необходимое для проверки кэша, которое, если вы не повторяете его много раз, ничтожно мало.

update2

Как вы совершенно правильно отметили, это решение не поддерживает interval3("1h 30s" + "2h 10m").Тем не менее, timedelta поддерживает арифметические операции, что означает, что вы все равно можете выразить его как interval3("1h 30s") + interval3("2h 10m").

Кроме того, как уже упоминалось в некоторых комментариях по этому вопросу, вы можете избежать поддержки "лет" и«месяцы» во входах.Есть причина, по которой 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
0 голосов
/ 17 января 2011

Да, есть. Используйте time.strptime вместо:

Разобрать строку, представляющую время в соответствии с форматом. Возврат значение равно struct_time как возвращено gmtime() или localtime().

...