2009-01-15 60 views
12

我在Python中發現的最大煩惱之一是re模塊無法在匹配對象中明確地保存其狀態。通常,需要解析行,如果他們符合某個正則表達式從同一個正則表達式中取出它們的值。我想這樣寫代碼:Python的重新模塊 - 保存狀態?

if re.match('foo (\w+) bar (\d+)', line): 
    # do stuff with .group(1) and .group(2) 
elif re.match('baz whoo_(\d+)', line): 
    # do stuff with .group(1) 
# etc. 

但不幸的是,這是不可能去上一次調用re.match的匹配對象,所以這是這樣寫的:

m = re.match('foo (\w+) bar (\d+)', line) 
if m: 
    # do stuff with m.group(1) and m.group(2) 
else: 
    m = re.match('baz whoo_(\d+)', line) 
    if m: 
    # do stuff with m.group(1) 

這是相當由於elif的列表變長,因此不太方便並變得非常笨重。

一個駭客解決的辦法是將re.match和re.search包裹在我自己的保持狀態的對象中。有沒有人使用過?你是否知道半標準的實現(在大型框架或其他)?

您可以推薦哪些其他解決方法? 或者,我只是濫用模塊,可以以更清潔的方式實現我的需求?

在此先感謝

+0

我希望模塊也有一個保持狀態的形式。但是我不認爲創建自己的保持狀態的對象是件難事。看起來像一個乾淨,本地化和明確的解決方案給我。 – 2009-01-16 15:35:39

回答

4

嘗試一些想法......

它看起來像你最好要具有副作用的表達。如果Python允許這樣做:

if m = re.match('foo (\w+) bar (\d+)', line): 
    # do stuff with m.group(1) and m.group(2) 
elif m = re.match('baz whoo_(\d+)', line): 
    # do stuff with m.group(1) 
elif ... 

......那麼你會清楚地乾淨地表達你的意圖。但事實並非如此。如果副作用在嵌套函數允許,你可以:

m = None 
def assign_m(x): 
    m = x 
    return x 

if assign_m(re.match('foo (\w+) bar (\d+)', line)): 
    # do stuff with m.group(1) and m.group(2) 
elif assign_m(re.match('baz whoo_(\d+)', line)): 
    # do stuff with m.group(1) 
elif ... 

現在,不僅是越來越難看,但它仍然不是有效的Python代碼 - 嵌套函數「assign_m」是不允許修改變量m在外部範圍內。我能想出的最好的是真的難看,用它允許副作用嵌套類:

# per Brian's suggestion, a wrapper that is stateful 
class m_(object): 
    def match(self, *args): 
    self.inner_ = re.match(*args) 
    return self.inner_ 
    def group(self, *args): 
    return self.inner_.group(*args) 
m = m_() 

# now 'm' is a stateful regex 
if m.match('foo (\w+) bar (\d+)', line): 
    # do stuff with m.group(1) and m.group(2) 
elif m.match('baz whoo_(\d+)', line): 
    # do stuff with m.group(1) 
elif ... 

但是,這顯然矯枉過正

您migth考慮使用一個內部函數,以允許本地範圍退出,它允許你刪除else嵌套:

def find_the_right_match(): 
    # now 'm' is a stateful regex 
    m = re.match('foo (\w+) bar (\d+)', line) 
    if m: 
    # do stuff with m.group(1) and m.group(2) 
    return # <== exit nested function only 
    m = re.match('baz whoo_(\d+)', line) 
    if m: 
    # do stuff with m.group(1) 
    return 

find_the_right_match() 

這允許您弄平嵌套=(2 * N-1)至嵌套= 1 ,但是你可能剛剛提出了副作用問題,嵌套函數很可能會混淆大多數Python程序員。

最後,有處理這個免費的副作用-方式:

def cond_with(*phrases): 
    """for each 2-tuple, invokes first item. the first pair where 
    the first item returns logical true, result is passed to second 
    function in pair. Like an if-elif-elif.. chain""" 
    for (cond_lambda, then_lambda) in phrases: 
    c = cond_lambda() 
    if c: 
     return then_lambda(c) 
    return None 


cond_with( 
    ((lambda: re.match('foo (\w+) bar (\d+)', line)), 
     (lambda m: 
      ... # do stuff with m.group(1) and m.group(2) 
     )), 
    ((lambda: re.match('baz whoo_(\d+)', line)), 
     (lambda m: 
      ... # do stuff with m.group(1) 
     )), 
    ...) 

而且現在的代碼勉強甚至看起來像Python,更不用說理解的Python程序員(是Lisp的?) 。

我認爲這個故事的寓意是Python並沒有爲這種成語優化。你確實需要做一些細節處理,並且在其他條件下使用較大的嵌套因子。

5

你可能會喜歡this module它實現你正在尋找的包裝。

+0

謝謝,這是我的想法 – 2009-01-15 18:33:59

+0

感謝您的指針!我喜歡與食譜相關的基本概念,但如果您使用的是更新版本的Python,則可以改進它。我嚴格的2.5+,所以我現在要去玩黑客攻擊。 – 2009-01-15 18:33:59

1

您可以編寫一個實用程序類來執行「保存狀態並返回結果」操作。我不認爲這是駭人聽聞的。這是相當容易實現:

class Var(object): 
    def __init__(self, val=None): self.val = val 

    def set(self, result): 
     self.val = result 
     return result 

,然後用它作爲:

lastMatch = Var() 

if lastMatch.set(re.match('foo (\w+) bar (\d+)', line)): 
    print lastMatch.val.groups() 

elif lastMatch.set(re.match('baz whoo_(\d+)', line)): 
    print lastMatch.val.groups() 
+0

這是一個有趣的概念。嗯,它可以處理很多Python無法在表達式中使用賦值的情況。 – 2009-01-16 06:34:52

1
class last(object): 
    def __init__(self, wrapped, initial=None): 
    self.last = initial 
    self.func = wrapped 

    def __call__(self, *args, **kwds): 
    self.last = self.func(*args, **kwds) 
    return self.last 

def test(): 
    """ 
    >>> test() 
    crude, but effective: (oYo) 
    """ 
    import re 
    m = last(re.compile("(oYo)").match) 
    if m("abc"): 
    print("oops") 
    elif m("oYo"): #A 
    print("crude, but effective: (%s)" % m.last.group(1)) #B 
    else: 
    print("mark") 

if __name__ == "__main__": 
    import doctest 
    doctest.testmod() 

last也適合作爲一個裝飾。

認識到,我努力使自我測試和工作在2.5,2.6和3.0,我有點模糊了真正的解決方案。重要的行標記爲#A和#B,其中您使用相同的對象進行測試(將其命名爲matchis_somename)並檢索其最後一個值。容易濫用,但也容易調整,如果不是推得太遠,得到令人驚訝的清晰的代碼。

1

基於對這個問題的很好的回答,我炮製了以下機制。它看起來像是解決Python「無條件限制」限制的一般方法。重點是透明度,通過無聲的代表團來實現:

class Var(object): 
    def __init__(self, val=None): 
     self._val = val 

    def __getattr__(self, attr): 
     return getattr(self._val, attr) 

    def __call__(self, arg): 
     self._val = arg 
     return self._val 


if __name__ == "__main__": 
    import re 

    var = Var() 

    line = 'foo kwa bar 12' 

    if var(re.match('foo (\w+) bar (\d+)', line)): 
     print var.group(1), var.group(2) 
    elif var(re.match('baz whoo_(\d+)', line)): 
     print var.group(1) 

在一般情況下,這是一個線程安全的解決方案,因爲你可以創建自己的Var實例。當線程化不是問題時,爲了更易於使用,可以導入和使用默認的Var對象。這裏有一個模塊持有瓦爾類:

class Var(object): 
    def __init__(self, val=None): 
     self._val = val 

    def __getattr__(self, attr): 
     return getattr(self._val, attr) 

    def __call__(self, arg): 
     self._val = arg 
     return self._val 

var = Var() 

而這裏的用戶代碼:

from var import Var, var 
import re 

line = 'foo kwa bar 12' 

if var(re.match('foo (\w+) bar (\d+)', line)): 
    print var.group(1), var.group(2) 
elif var(re.match('baz whoo_(\d+)', line)): 
    print var.group(1) 

雖然不是線程安全的,對於很多簡單的腳本,這提供有用的快捷方式。

0

也許最簡單的解決方案是儘早返回,以便返回創建變量而不需要立即進行測試。

def get_results(line): 
    m = re.match('foo (\w+) bar (\d+)', line) 
    if m: 
     # do stuff with .group(1) and .group(2) 
     return result 
    m = re.match('baz whoo_(\d+)', line) 
    if m: 
     # do stuff with .group(1) 
     return other_result 
    # etc. 

這樣可以避免過多的嵌套。