too late amatuar programmer

[python] 一度に複数のパターンを置換する関数(正規表現を置換前文字列とできる版)

2010-10-30 by ebon | Lavel:

一度に複数のパターンを置換したい場合がある。
その場合、pythonクックブックの同名レシピが参考になる.

そのレシピでは、以下のように、1パスで対応できることに意義を置き、問題が定義されている.
単一の文字列の中の複数の文字列パターンを、同時に(1パス)で置換したい
それに対する解法・考察として、以下3パターンが書かれている.

  • シンプルな例
  • クロージャーを用いた例
  • オブジェクトを用いた例

よくある同問題について、問題と対策について分かりやすく記述されており、このレシピは大いに参考になると思う.




しかし、若干物足りない点があった.

そのレシピの例では、複数のパターンの置換前文字列をディクショナリのキーとして渡すのだが、そのキーについて正規表現を使えるようにはなっていない.
なので、使えるよう少しカスタマイズして作成した.


カスタム前のコード


まずはレシピの最初で書かれている、一度に複数のパターンを置換する関数の、最もシンプルなタイプの関数を紹介する.

  1. def multiple_replace(text,adict):  
  2.     """ 
  3.     一度に複数の文字列を置換する. 
  4.     - text中からディクショナリのキーに合致する文字列を探し、対応の値で置換して返す 
  5.     """  
  6.     # マッチさせたいキー群を正規表現の形にする e.g) (a1|a2|a3...)  
  7.     rx = re.compile('|'.join(map(re.escape,adict)))  
  8.     def one_xlat(match):  
  9.         return adict[match.group(0)]  
  10.     return rx.sub(one_xlat, text)  
  11.   
  12. # 使用例  
  13. text  = "Larry Wall is the creator of Perl"  
  14. adict = {  
  15.     "Larry Wall"      : "Guido van Rossum",             
  16.     "creator"       : "Benevolent Dictator for Life",  
  17.     "Perl" : "Python",                        
  18. }  
  19.   
  20. after = multiple_replace_re(text,adict)  
  21.   
  22. >>> after  
  23. 'Guido van Rossum is the Benevolent Dictator for Life of Python'  

カスタム後のコード


次に、一度に複数のパターンを置換する関数”正規表現を置換前文字列とできる版)”
を上げておく.

カスタムは上記シンプルなタイプについて行っている.
レシピでは、これを取っ掛かりとして、置換辞書の再利用などを考慮した先の例があるのだが、
ここではその扱いやすいシンプルな関数でカスタマイズしていった.

複数のパターンの置換前文字列をディクショナリのキーとして渡すことを可能としている.

  1. def multiple_replace_re(text,adict):  
  2.     """ 一度に複数のパターンを置換する関数 
  3.     - text中からディクショナリのキーに合致する文字列を探し、対応の値で置換して返す 
  4.     - キーでは、正規表現を置換前文字列とできる 
  5.     """  
  6.     rx = re.compile('|'.join(adict))  
  7.       
  8.     def dedictkey(text):  
  9.         """ マッチした文字列の元であるkeyを返す 
  10.         """  
  11.         for key in adict.keys():  
  12.             if re.search(key, text):  
  13.                 return key  
  14.   
  15.     def one_xlat(match):  
  16.         return adict[dedictkey(match.group(0))]  
  17.   
  18.     return rx.sub(one_xlat,text)  
  19.   
  20. # 実行例   
  21. text  = "Larry Wall is the creator of Perl"  
  22. adict = {  
  23.     "Lar.*all"      : "Guido van Rossum",             
  24.     "creator"       : "Benevolent Dictator for Life",  
  25.     "(?:Perl|Ruby)" : "Python",                        
  26. }  
  27.   
  28. after = multiple_replace_re(text,adict)  
  29. >>> after  
  30. 'Guido van Rossum is the Benevolent Dictator for Life of Python'  

カスタムした点
  • 正規表現のメタ文字を文字列として扱えるようにするメソッド:re.escapeをなくし正規表現をそのまま使用可能に
  • マッチした文字列の元であるkeyを返す関数dedictkeyを追加.これにマッチ後文字列を渡し、対応する置換後文字列を取得している.

しょぼい点
  • keyの正規表現にだぶってマッチする場合の対処をまったく考えていない
  • 他にもエラーの可能性をあまり考慮できていない

この辺りのカバーを使用方法に委ねるという全く持ってダメなプログラムとなっている...
置換内容がごく単純な場合のみに使いどころがあると言えそうだ.


終わりに


1パスで置換することにこだわらなければ、replaceメソッドを用い、置換処理を繰り返していくことでもっと単純なコードが書けるかもしれない.

また、ここでは単に関数そのものをベタにカスタムしている.
クックブックでは、別のカスタム方法がお勧めされており、サブクラスからのオーバーライドを使用したカスタマイズ方法が例に上げられている.

それら例のように、複数パターン置換というよくある例をとり、
クロージャーの使用どころ/オブジェクト化する意義などが簡潔に解説されている点は,
強調しておきたい.これら手法になじめるシンプルな例として、とても参考になる良いレシピだ.

私の例が良いものだとは思えないが、このレシピから出発し、自分の目的に適う置換関数を作成していけると思う.

0 comment:

Post a Comment