SlideShare a Scribd company logo
正規表現リテラルは 
本当に必要なのか? 
Makoto Kuwata 
kwa@kuwata-lab.com 
http://www.kuwata-lab.com/ 
copyright© 2014 kuwata-lab.com all rights reserved 
PyConJP 2014 
ver 1.1 (2014-09-17): スライドを追加・加筆
発表の背景 
✓ 2013年末、プログラミング言語における「正規 
表現リテラルの必要性」が支持を集める 
http://togetter.com/li/603521 
http://blog.kazuhooku.com/2013/12/blog-post.html   
✓ Pythonは正規表現リテラルがないけど、別に困 
ってないよ? 
…と説明しても聞いちゃくれないPerler/Rubyist/JavaScripterと、 
正規表現リテラルがなくて困ってるJavaユーザと、どんなときでも 
叩かれるPHPerが入り乱れた、異種言語間お笑いバトル 
copyright© 2014 kuwata-lab.com all rights reserved
え|マジ 
正規表現 
リテラルないの? 
あれなしで許されるキモ|イ 
のはPHPまでだよね! 
キャハハハハハハ 
Pythonの評判
発表の目的 
✓ 正規表現リテラルは、あれば便利だけどなくても 
困らない(ライブラリでカバーできる)ことを説 
明する 
✓ Pythonの正規表現ライブラリが抱える問題点と 
その解決案を紹介する 
copyright© 2014 kuwata-lab.com all rights reserved
発表の対象者 
✓ Pythonのことをよく知らないPerlerやRubyist 
やJavaScripter  
✓ 他言語との違いが気になるPythonista 
✓ 二重バックスラッシュにイライラしてるJavaユー 
ザ 
copyright© 2014 kuwata-lab.com all rights reserved
第1部: 
正規表現リテラルに関する誤解 
copyright© 2014 kuwata-lab.com all rights reserved
第1部:正規表現リテラルに関する誤解 
✓ 誤解:正規表現リテラルがあったほうが書きやすい 
✓ 誤解:正規表現リテラルがあったほうが性能がよい 
✓ 誤解:正規表現のほうが文字列関数より学習コスト 
が低い 
copyright© 2014 kuwata-lab.com all rights reserved
第1部:正規表現リテラルに関する誤解 
✓ 誤解:正規表現リテラルがあったほうが書きやすい 
✓ 誤解:正規表現リテラルがあったほうが性能がよい 
✓ 誤解:正規表現のほうが文字列関数より学習コスト 
が低い 
copyright© 2014 kuwata-lab.com all rights reserved
正規表現リテラルがあると、正規表現は書きやす 
い? 
## 正規表現リテラルあり 
/[a-z]+/.exec(string) # JavaScript 
## 正規表現リテラルなし 
(new RegExp("[a-z]+")).exec(string) # JavaScript 
copyright© 2014 kuwata-lab.com all rights reserved
正規表現リテラルがなくても、正規表現を書きやす 
くできる 
## 正規表現リテラルなし 
function re(pattern) { # JavaScript 
return new RegExp(pattern); 
} 
re("[a-z]+").exec(string) 
関数やライブラリで解決できる 
copyright© 2014 kuwata-lab.com all rights reserved
二重バックスラッシュ問題を発生させないためには 
正規表現リテラルが必要? 
## 正規表現リテラルあり 
/^dd:dd$/ # JavaScript 
## 正規表現リテラルなし 
new RegExp("^dd:dd$") # JavaScript 
copyright© 2014 kuwata-lab.com all rights reserved
raw文字列リテラル (Python) やシングルクォー 
ト(JS, PHP)でも発生しない 
## JavaScript 
new RegExp('^dd:dd:dd$') 
## Python 
re.match(r"^dd:dd:dd$", timestr) 
## PHP 
preg_match('/^dd:dd:dd$/', timestr) 
copyright© 2014 kuwata-lab.com all rights reserved
ただしraw文字列相当のない言語、お前はダメだ 
## Java 
import java.util.regex.Pattern; 
import java.util.regex.Matcher; 
Pattern pat = Pattern.compile("^dd:dd$"); 
Matcher m = pat.matcher(timestr); 
二重バックスラッシュが辛いです ;( 
copyright© 2014 kuwata-lab.com all rights reserved
【問題点】 
Javaで正規表現が書きにくい 
(二重バックスラッシュがつらい) 
【解決策】 
正規表現リテラル 
【解決策】 
raw文字列リテラル 
導入すると、言語仕様が 
肥大化してしまう 
正規表現以外にも利用可能、 
かつ言語仕様が肥大化しない 
copyright© 2014 kuwata-lab.com all rights reserved
とはいえ、raw文字列リテラルを導入するには 
言語仕様の拡張が必要 
## Java 
Pattern pat = Pattern.compile(r"^dddd$"); 
Matcher m = pat.matcher(input); 
raw文字列リテラルがほしいけど、 
言語仕様を拡張しないと無理じゃん ;( 
copyright© 2014 kuwata-lab.com all rights reserved
そもそも、バックスラッシュでないとだめなん? 
## 先行事例: Cのprintf()やJavaのString.format() 
String.format("s=%s, n=%d", s, n); 
メタキャラクタとして「%」を使っている! 
二重バックスラッシュのような問題がない! 
copyright© 2014 kuwata-lab.com all rights reserved
バックスラッシュ以外を使えば、Javaでも正規表 
現が書きやすくなる! (最初は違和感あるけど慣れの問題) 
## http://kwatch.houkagoteatime.net/blog/2013/12/28/java-regex/ 
import static benry.rexp.Rexp.rexp; 
import benry.rexp.Matched; 
String pat = "^(`d`d`d`d)-(`d`d)-(`d`d)$"; 
Matched m = rexp(pat).match(str); 
if (m != null) { 
System.out.println(m.get(1)); 
} 
copyright© 2014 kuwata-lab.com all rights reserved 
「」のかわりに 
「`」を使ってる 
(変更も可能)
【問題点】 
Javaで正規表現が書きにくい 
(二重バックスラッシュがつらい) 
【解決策】 
バックスラッシュを 
やめる 
copyright© 2014 kuwata-lab.com all rights reserved 
【解決策】 
正規表現リテラル 
【解決策】 
raw文字列リテラル 
言語仕様の変更が必要なく、 
ライブラリだけで実現可能
✓ 正規表現リテラルのないJavaでは、 
正規表現が書きにくい 
✓ 正規表現リテラルのあるPerlやRubyでは、 
正規表現が書きやすい 
✓ だから、プログラミング言語に 
正規表現リテラルは必要だ! 
・問題:解決方法 = 1:N 
(*注) 
・one of themでしかない方法を、only oneな方法だと勘違いしてる 
(*注) もちろん、複数の解決方法の間では優劣が存在する。この場合なら、「正規表現リテ 
ラルが必要」と主張するには、それが他の方法より優れていることを説明する必要がある。 
copyright© 2014 kuwata-lab.com all rights reserved 
← わかる 
← わかる 
← その理屈はおかしい
ここまでのまとめ 
✓ 正規表現を書きやすくする言語機能は、ひとつで 
はない 
正規表現リテラル、raw文字列リテラル 
✓ 正規表現リテラルよりraw文字列リテラルのほう 
が望ましい 
正規表現以外にも利用可能だし、言語仕様も肥大化しない 
✓ そもそも、バックスラッシュを使わなければいい 
言語仕様の拡張が必要ないので、今すぐ使える方法 
copyright© 2014 kuwata-lab.com all rights reserved
第1部:正規表現リテラルに関する誤解 
✓ 誤解:正規表現リテラルがあったほうが書きやすい 
✓ 誤解:正規表現リテラルがあったほうが性能がよい 
✓ 誤解:正規表現のほうが文字列関数より学習コスト 
が低い 
copyright© 2014 kuwata-lab.com all rights reserved
正規表現リテラルなら、コンパイルは1度だけ 
## Ruby 
100.times do 
filename =~ /.(png|gif|jpe?g)$/ 
end 
100回コンパイルされたりはしない 
(ただし埋め込み式のある場合は別) 
copyright© 2014 kuwata-lab.com all rights reserved
正規表現リテラルがない場合はどうなる? 
## Python 
for _ in range(100): 
re.search(r'.(png|gif|jpe?g)', filename) 
re.search() の中で毎回コンパイル 
されてそうだから、遅いのでは? 
copyright© 2014 kuwata-lab.com all rights reserved
ライブラリがキャッシュすれば無問題 
## Python 
_cache = {} 
def _compile(patstr): 
## キャッシュがあればそれを返す 
try: 
キャッシュを活用 
return _cache[patstr] 
except KeyError: 
pass 
## なければコンパイルして 
## キャッシュする 
pat = sre_compile(patstr) 
_cache[patstr] = pat 
return pat 
def search(patstr, s): 
pat = _compile(patstr) 
return pat.search(s) 
def sub(patstr, rep, s): 
pat = _compile(patstr) 
return pat.sub(rep, s) 
注:実際の正規表現ライブラリ (re.py) では、正規表現フラグつきでキャッシュしたり、 
キャッシュが大きくなりすぎるとパージするなど、もっと複雑である。 
copyright© 2014 kuwata-lab.com all rights reserved 
他の関数は _compile() 
を呼び出す
どうしても気になる場合は、正規表現オブジェク 
トを変数で保持すればよい 
## Python 
rexp = re.compile(r'.(png|gif|jpe?g)') 
for _ in range(100): 
re.search(rexp, filename) 
キャッシュから取り出す 
オーバーヘッドがなくなる 
(通常は気にするほどではない) 
copyright© 2014 kuwata-lab.com all rights reserved
なおCPythonでは、正規表現ライブラリより 
文字列関数のほうがかなり速い 
copyright© 2014 kuwata-lab.com all rights reserved 
startswith() 
endswith() 
isdigit() 
文字列関数正規表現 
文字列関数の速度を100 
としたときのグラフ 
https://gist.github.com/kwatch/f923fb5a71da3f69eccb 
https://gist.github.com/kwatch/0132268e0c38741fe59a 
https://gist.github.com/kwatch/e1bc95fcc6cb75c60c94
また正規表現に対する高度な最適化は、リテラル 
の有無ではなく、処理系の評価戦略次第 
// Rust (http://doc.rust-lang.org/regex/) 
#![feature(phase)] 
#[phase(plugin)] 
extern crate regex_macros; 
extern crate regex; 
正規表現文字列をコンパイル時に評価 
→ 正規表現リテラルがなくても 
コンパイル時に間違いを検出 
→ 正規表現リテラルがなくても 
バイナリを生成可能 
fn main() { 
let re = regex!(r"^d{4}-d{2}-d{2}$"); 
assert_eq!(re.is_match("2014-01-01"), true); 
} 
copyright© 2014 kuwata-lab.com all rights reserved
ここまでのまとめ 
✓ キャッシュを使えば、正規表現が毎回コンパイル 
されることはない 
正規表現リテラルがなくても充分な性能は出せる 
✓ 正規表現より文字列関数のほうが高速 
少なくともCPythonではそう 
✓ リテラルがなくても正規表現のコンパイル時評価 
は可能 
「リテラルの有無」と「処理系の評価戦略」は、基本的に別個の話 
copyright© 2014 kuwata-lab.com all rights reserved
第1部:正規表現リテラルに関する誤解 
✓ 誤解:正規表現リテラルがあったほうが書きやすい 
✓ 誤解:正規表現リテラルがあったほうが性能がよい 
✓ 誤解:正規表現のほうが文字列関数より学習コスト 
が低い 
copyright© 2014 kuwata-lab.com all rights reserved
✓ そもそも、「正規表現リテラルは必要か?」と 
「正規表現と文字列関数はどちらが学習コストが 
低いか?」は別の話 
仮に「正規表現のほうが文字列関数よりわかりやすい」という結 
論になったとしても、それをもって「正規表現リテラルは必要」 
とはならない 
✓ そのうえで、あえて「どちらが学習コストが低い 
か?」を論じる。 
copyright© 2014 kuwata-lab.com all rights reserved
文字列関数で済む範囲であれば、正規表現より 
文字列関数のほうが読みやすい、わかりやすい 
## 正規表現 
re.match(r"^d+$", input) 
re.match(r"^http://", string) 
re.search(r".(png|gif|jpg)$", filename) 
毎日コード書いてる人なら 
覚えられるだろうけど・・・ 
## 文字列関数 
input.isdigit() 
string.startswith("http://") 
filename.endswith((".png", ".gif", ".jpg")) 
初級者でもわかりやすい! 
copyright© 2014 kuwata-lab.com all rights reserved
また正規表現は落とし穴も多いので、初級者には 
つらいことも 
## Ruby 
string =~ /.html$/ 
厳密には間違い 
(正解は /.htmlz/ ) 
## Ruby 
string.end_with?(".html") 
初級者でも間違えない! 
copyright© 2014 kuwata-lab.com all rights reserved
とはいえ、上達するにつれ、正規表現を避けるこ 
とはできない 
## 正規表現 
pat = r"^(d{4})-(dd)-(dd)[ T](dd):(dd): 
dd)$" 
re.match(pat, input) 
文字列関数でこれを書くのは 
つらい 
copyright© 2014 kuwata-lab.com all rights reserved
学習コストの内訳(ソース:個人的印象) 
文字列関数 
正規表現の学習コストに比べたら、 
文字列関数のそれは大したことない 
(単機能ばかりだから) 
正規表現正規表現 
正規表現だけ正規表現+文字列関数 
copyright© 2014 kuwata-lab.com all rights reserved
コア言語仕様を肥大化させて 
でも文字列関数を減らすこと 
がそんなに重要なの? 
それ正規表現リテラル 
じゃなくて正規表現の 
メリットじゃないの? 
“正規表現リテラルがあれば 
文字列関数を減らせるし、 
処理系も単純にできるよ!” 
誰にとってのメリットなの? 
ときどきしかコードを書かない 
ライトユーザにも嬉しいことなの? 
(科学者、統計学者、CGデザイナ、etc) 
そんな簡単な話ではないはず・・・ 
「/..../」のパースは単純なの? 
文字列関数が減るかわりに 
別の複雑さが増えてない? 
copyright© 2014 kuwata-lab.com all rights reserved
ここまでのまとめ 
✓ 文字列関数だけのほうが学習コストは低い 
学習コスト: 正規表現 >>> 文字列関数 
✓ とはいえ正規表現の勉強はどのみち必要 
学習コスト: 正規表現 < 文字列関数+正規表現 
✓ 正規表現やリテラルの得失は一概には言えない 
だれにとってのメリット?どのくらいのメリット? 
✓ そもそも「正規表現リテラルの得失」と「正規表 
現の得失」は別の話 
ちゃんと分けて議論しましょう 
copyright© 2014 kuwata-lab.com all rights reserved
第2部: 
Python正規表現ライブラリの 
問題点と解決案 
copyright© 2014 kuwata-lab.com all rights reserved
第2部:Python正規表現ライブラリの 
問題点と解決案 
✓ re.match()とre.search()の2つがある 
✓ ライブラリの使い方が2系統ある 
✓ 正規表現がいつもキャッシュされてしまう 
✓ 連続したマッチングとif文との相性が悪い 
copyright© 2014 kuwata-lab.com all rights reserved
第2部:Python正規表現ライブラリの 
問題点と解決案 
✓ re.match()とre.search()の2つがある 
✓ ライブラリの使い方が2系統ある 
✓ 正規表現がいつもキャッシュされてしまう 
✓ 連続したマッチングとif文との相性が悪い 
copyright© 2014 kuwata-lab.com all rights reserved
re.match()は先頭からのマッチングしかできない、 
re.search()なら途中からのマッチングも可 
## これはマッチする 
re.match(r"(d+)", "123abc") 
re.search(r"(d+)", "abc123") 
## これはマッチしない!(先頭にないので) 
re.match(r"(d+)", "abc123") 
re.match(r"pat", str) は re.search(r"^pat", str) で代用できる。 
re.match() は混乱のもとだし、いらないのでは? 
copyright© 2014 kuwata-lab.com all rights reserved
第2部:Python正規表現ライブラリの 
問題点と解決案 
✓ re.match()とre.search()の2つがある 
✓ ライブラリの使い方が2系統ある 
✓ 正規表現がいつもキャッシュされてしまう 
✓ 連続したマッチングとif文との相性が悪い 
copyright© 2014 kuwata-lab.com all rights reserved
追加スライド 
Pythonの正規表現ライブラリは、使い方が2系統 
存在する 
re.compile().xxxx() 系re.xxxx() 系 
大抵の正規表現操作が、モジュールレベルの関数 
と、 コンパイル済み正規表現のメソッドとして提 
供されることに注意して下さい。関数は正規表現 
オブジェクトのコンパイルを必要としない近道です 
が、いくつかのチューニング変数を失います。 
copyright© 2014 kuwata-lab.com all rights reserved 
“ 
” 
引用元: http://docs.python.jp/3.3/library/re.html
しかも、両者は似ているようで微妙に違う ;( 
これは困る 
## re.compile().xxxx() 系 
re.compile(pat, flags).match(string, pos, endpos) 
re.compile(pat, flags).sub(repl, string, count) 
## re.xxxx() 系 
re.match(pat, string, flags) 
re.sub(pat, repl, string, count, flags) 
re.sub() は、 Python2.6では正規表 
現フラグが指定できなかった 
copyright© 2014 kuwata-lab.com all rights reserved 
追加スライド 
開始位置と終了位置が、re.compile().match() 
では指定できるが re.match() ではできない
ところで re.xxxx() のやっていることは、内部で 
re.compile().xxxx() を呼び出しているだけ 
def compile(pattern, flags=0): 
return _compile(pattern, flags) 
def match(pattern, string, flags=0): 
return _compile(pattern, flags) 
.match(string) 
def sub(pattern, repl, string, count=0, flags=0): 
return _compile(pattern, flags) 
.sub(repl, string, count) 
copyright© 2014 kuwata-lab.com all rights reserved
だったら、全部 re.compile().xxxx() を使うように 
すれば、re.xxxx() をなくして一本化できるよね? 
re.compile()へのショートカット 
rx = re._compile 
## マッチング 
m = re.match(r"(d+)", "123abc") # before 
m = rx(r"(d+)").match("123abc") # after 
## 文字列置換 
re.sub(r".gif$", ".png", filename) # before 
rx(r".gif$").sub(".png", filename) # after 
copyright© 2014 kuwata-lab.com all rights reserved
一本化できれば、「似てるけど微妙に違う2系統」 
が共存しなくてすむ 
## re.compile().xxxx() 系 
re.compile(pat, flags).match(string, pos, endpos) 
re.compile(pat, flags).sub(repl, string, count) 
等価 (当然) 
## rx().xxxx() 系 
rx = re._compile 
rx(pat, flags).match(string, pos, endpos) 
rx(pat, flags).sub(repl, string, count) 
copyright© 2014 kuwata-lab.com all rights reserved 
追加スライド
また1つの関数に5~6個も引数があるくらいなら、 
2~3個の関数2つに分けたほうがわかりやすい 
引数が5個! 
## before 
re.sub(pattern, repl, string, count=0, flags=0) 
## after 
rx(pattern, flags=0).sub(repl, string, count=0) 
引数2個と引数3個 
copyright© 2014 kuwata-lab.com all rights reserved
第2部:Python正規表現ライブラリの 
問題点と解決案 
✓ re.match()とre.search()の2つがある 
✓ ライブラリの使い方が2系統ある 
✓ 正規表現がいつもキャッシュされてしまう 
✓ 連続したマッチングとif文との相性が悪い 
copyright© 2014 kuwata-lab.com all rights reserved
re.compile() は正規表現を必ずキャッシュする、 
けどキャッシュする必要がないときもある 
class HTMLHelper(object): 
_ESCAPE = re.compile(r"[&<>"']") 
クラス変数に保持しているので、ライブ 
ラリ側でキャッシュする必要はない 
(けど強制的にキャッシュされるので、 
キャッシュが必要以上に肥大化する) 
copyright© 2014 kuwata-lab.com all rights reserved
特に、たくさんの正規表現がデータとして与えられ 
ると、キャッシュが無駄に肥大化してしまう 
urlpatterns = patterns('', 
url(r'^posts/$', "..."), 
url(r'^posts/new$', "..."), 
url(r'^posts/(?P<id>d+)$', "..."), 
url(r'^posts/(?P<id>d+)/comments$', "..."), 
url(r'^posts/(?P<id>d+)/edit$', "..."), 
... 
copyright© 2014 kuwata-lab.com all rights reserved 
) 
コンパイルするとすべて強制的にキャッシュされる 
→ キャッシュする必要のないデータによって 
  キャッシュが肥大化する
Pythonの正規表現ライブラリは、キャッシュが肥 
えすぎるとすべてパージしてしまう! 
_cache = {} 
_MAXCACHE = 512 
キャッシュが肥大化する 
→ キャッシュがパージされる 
→ 性能低下 ;( 
def _compile(pattern, flags): 
...(snip)... 
p = sre_compile.compile(pattern, flags) 
if not bypass_cache: 
if len(_cache) >= _MAXCACHE: 
_cache.clear() 
_cache[type(pattern), pattern, flags] = p 
return p 
copyright© 2014 kuwata-lab.com all rights reserved
キャッシュせずにコンパイルする機能が、公式に用 
意されるとうれしい 
class HTMLHelper(object): 
_ESCAPE = re.sre_compile.compile(r"[&<>"']") 
これならキャッシュしないので、 
キャッシュの無駄な肥大化を防げる 
(しかしunofficialなので使用には注意すること) 
copyright© 2014 kuwata-lab.com all rights reserved
第2部:Python正規表現ライブラリの 
問題点と解決案 
✓ re.match()とre.search()の2つがある 
✓ ライブラリの使い方が2系統ある 
✓ 正規表現がいつもキャッシュされてしまう 
✓ 連続したマッチングとif文との相性が悪い 
copyright© 2014 kuwata-lab.com all rights reserved
複数の正規表現にマッチさせるとき、こう書きたい 
## ほんとはこう書きたい 
if m = re.match(pat1, text): 
x, y = m.groups() 
elif m = re.match(pat2, text): 
y, z = m.groups() 
elif m = re.match(pat3, text): 
z, x = m.groups() 
文法エラー: 
Pythonでは代入文は式ではないので、 
if文の条件式には書けない 
copyright© 2014 kuwata-lab.com all rights reserved
でもPythonではこう書くしかない 
m = re.match(pat1, text) 
if m: 
x, y = m.groups() 
copyright© 2014 kuwata-lab.com all rights reserved 
else: 
m = re.match(pat2, text) 
if m: 
y, z = m.groups() 
else: 
m = re.match(pat3, text) 
if m: 
z, x = m.groups() 
if文のネストが深くなる
関数+return や、while文+break という手も 
あるが、あまり嬉しくはない 
while 1: 
m = re.match(pat1, text) 
if m: 
x, y = m.groups() 
break 
m = re.match(pat2, text) 
if m: 
y, z = m.groups() 
break 
m = re.match(pat3, text) 
if m: 
z, x = m.groups() 
break 
copyright© 2014 kuwata-lab.com all rights reserved 
break 
if文のネストは減ったけど、 
トリッキーで間違えやすい
そこで、こういう機能はどうでしょう? 
マッチングの対象文字列とマッチング結果を 
保持するようなオブジェクトを用意すれば、 
m = re.matching(text) 
if m.match(pat1): 
x, y = m.groups() 
elif m.match(pat2): 
y, z = m.groups() 
elif m.match(pat3): 
z, x = m.groups() 
連続したマッチングが 
素直に書けるはず 
copyright© 2014 kuwata-lab.com all rights reserved
copyright© 2014 kuwata-lab.com all rights reserved 
実装はこちら 
class matching(object): 
def __init__(self, string): 
self.string = string 
self.matched = None 
http://bit.ly/matching_py 
def match(self, pattern, flags=0): 
self.matched = re.compile(pattern, flags) 
.match(self.string) 
return self.matched 
def groups(self, *args): 
return self.matched.groups(*args)
Questions? 
copyright© 2014 kuwata-lab.com all rights reserved
おまけ: benry.rexp 
https://pypi.python.org/pypi/benry 
from benry.rexp import rx 
## re.compile() へのショートカット 
m = rx(r'pat', rx.I).match(string, start, end) 
## キャッシュせずにコンパイル 
rexp = rx.compile(r'pat', rx.I) 
## 連続したマッチング 
m = rx.matching(string) 
if m.match(r'^(dddd)-(dd)-(dd)$'): 
Y, M, D = m.groups() 
else m.match(r'(dd)/(dd)/(dddd)$'): 
M, D, Y = m.groups() 
copyright© 2014 kuwata-lab.com all rights reserved
copyright© 2014 kuwata-lab.com all rights reserved 
おしまい

More Related Content

正規表現リテラルは本当に必要なのか?