我的一個同事提到,他錯過了 Ruby 的正規表示式的語法糖。我沒有使用過 Ruby 的正規表示式,但是以我對 Python 的足夠了解知道 API 是缺少足夠的語法糖。
首先,從正規表示式檢索捕捉組需要兩個步驟。第一步,你需要呼叫 match()
或是 search()
,並將結果分配給一個變數。然後,你需要檢查結果是否為 None
(表明沒有發現匹配)。最後,如果匹配存在,你可以安全的提取捕獲組。下面是一個示例:
1 2 3 4 5 6 7 8 9 10 |
>>> import re >>> match_obj = re.match('([0-9]+)', '123foo') >>> match_obj # What is `match_obj`? <_sre.SRE_Match object at 0x7fd1bb000828> >>> match_obj.groups() ('123',) >>> match_obj = re.match('([0-9]+)', 'abc') >>> match_obj None |
還可以更好,在我看來,類似下面:
1 2 3 4 5 6 |
>>> re.get_matches('([0-9]+)', '123foo') ('123',) >>> re.get_matches('([0-9]+)', 'abc') None |
我經常遇到的另外一件事情就是 re.sub
的混合引數,它可以執行正則的查詢和替換。要求的引數,按照順序,是 pattern
,replacement
, search_string
。無論出於何種原因,對我來說,更直觀的是在替換之前使用 search_string
。
不幸的是,改編這些引數會導致“看起來正確”的結果。下面是一個例子,這裡的目標是使用單詞 bar
替換單詞 foo
。
1 2 3 4 5 6 |
>>> re.sub('foo', 'replace foo with bar', 'bar') 'bar' >>> re.sub('foo', 'bar', 'replace foo with bar') 'replace bar with bar' |
其中關於
re.sub
的用法可以參考這篇文章,很詳細 http://www.crifan.com/python_re_sub_detailed_introduction/
在第一個例子中,我們可能會假設輸入的字串只是“foo”。
語法糖
為了好玩,我把一個小小的增加了一些語法糖的 Python 正規表示式庫的幫助類放在一起。我不建議任何人都使用它,但好玩的是,也許它可以為你提供一些提高其他庫的語法的想法。
再我向你展示這個實現之前,這裡有我設計的一個 API 的示例。
尋找匹配的單步操作:
1 2 3 4 5 6 7 8 |
>>> def has_lower(s): ... return bool(R/'[a-z]+'/s) >>> has_lower('This contains lower-case') True >>> has_lower('NO LOWER-CASE HERE!') False |
檢索捕獲組也是非常容易的:
1 2 3 |
>>> list(R/'([0-9]+)'/'extract 12 the 456 numbers') ['12', '456'] |
最後你可以使用字串插值來實現替換:
1 2 3 |
>>> R/'(foo|bar)'/'replace foo and bar' % 'Huey!' 'replace Huey! and Huey!' |
你怎麼認為?是不是很有趣?
實現
這個實現是非常簡單的,依賴於 Python 的魔術方法提供的 API。是否有一個整潔的技巧,本質上,它是使用一個元類來實現類方法的操作符過載。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import re class _R(type): def __div__(self, regex): return R(regex) class R(object): __metaclass__ = _R def __init__(self, regex): self._regex = re.compile(regex) def __div__(self, s): return RegexOperation(self._regex, s) class RegexOperation(object): def __init__(self, regex, search): self._regex = regex self._search = search def search(self): match = self._regex.search(self._search) if match is not None: return match.groups() def __len__(self): return self._regex.search(self._search) is not None def __mod__(self, replacement): return self._regex.sub(replacement, self._search) def __iter__(self): return iter(self._regex.findall(self._search)) |
通過一步步的操作,希望它可以闡明幕後的知識。
呼叫 R /
將呼叫 _R
類的 __div__
方法。它是一個建立 R 例項的工廠方法。
1 2 |
>>> R/'foo' <rx.R at 0x7f77c00831d0> |
然後,在最新建立的 R 物件上呼叫 __div__
方法,我們會得到一個 RegexOperation
例項,因此 R.__div__
是另外一個工廠方法。
1 2 3 |
>>> r_obj = R/'foo' >>> r_obj / 'bar' <rx.RegexOperation at 0x7f77c00837d0> |
最後的物件,RegexOperation
實現了一些魔法方法,允許我們檢索匹配,執行替換,以及測試匹配是否存在。