6

私は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 モジュールを使用して両方のコードの実行時間をテストしたところ、2 番目のコードの方が約 5 ~ 6 秒長くかかっていることがわかりました (デフォルトの繰り返し回数の場合)。

私の質問は次のとおり
です。1.よりPythonicと見なされるコードはどれですか?
2.この関数を書くのにもっとPythonicがありましたか?
3. Pythonicity とプログラミングの他の側面 (この場合は速度など) との間のトレードオフについてはどうですか?

psエレガントなコード用の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]
4

3 に答える 3

4

解析するたびに多くのラムダが作成されるようです。ラムダは本当に必要ありません。乗数だけが必要です。これを試して:

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ただし、処理を高速化するために、メソッドのローカル関数やメソッドを作成しないでください。

于 2011-01-17T14:39:45.840 に答える
3

(私はこれを計測していませんが) この関数を頻繁に使用する場合は、正規表現を事前にコンパイルする価値があるかもしれません。

あなたの機能に対する私の見解は次のとおりです。

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)

アップデート

この質問から、正規表現パターンを事前にコンパイルしても、Python がそれらをキャッシュして再利用するため、顕著なパフォーマンスの向上はもたらされないようです。何度も繰り返さない限り、キャッシュをチェックするのにかかる時間を節約するだけです。

update2

ご指摘のとおり、このソリューションはサポートしていませんinterval3("1h 30s" + "2h 10m")。ただし、timedeltaは算術演算をサポートしているため、 として表現できますinterval3("1h 30s") + interval3("2h 10m")

また、質問に関するいくつかのコメントで言及されているように、入力で「年」と「月」をサポートしないようにすることもできます。timedeltaがこれらの議論を支持しないのには理由があります。正しく処理することはできません (そして、正しくないコードはほとんど洗練されていません)。

これは別のバージョンで、今回は float、負の値、およびいくつかのエラー チェックをサポートしています。

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
于 2011-01-17T15:11:27.913 に答える
-1

はいあります。time.strptime代わりに使用してください:

形式に従って時刻を表す文字列を解析します。戻り値は、またはstruct_timeによって返されるです。gmtime()localtime()

于 2011-01-17T14:26:01.653 に答える