2008年10月6日月曜日

Python におけるバックスラッシュ (\) の扱いと、row stirng と正規表現における注意点

1. エスケープシーケンスにより表現する文字

Python で、バックスラッシュを文字列として出力したい。

print r'\'

しかし、これではシンタックスエラーとなる。

他の言語と同様に、特定の文字の前にバックスラッシュ (\) を付けると、特殊な文字や機能を表わす。

例えば、「改行」を文字列の中に含むには、\n を記述する。

print 'hoge\npiyo'

文字列を出力させた結果は、

hoge
piyo

`\n’ は改行を表わし、これをエスケープシーケンスと言う。

エスケープシーケンス - Wikipedia とは、

コンピュータシステムにおいて、通常の文字列では表せない特殊な文字や機能を、規定された特別な文字の並びにより表したもの。狭義には、エスケープコード (0x1B, ESC) に始まる一連のバイト列のことをいう。

エスケープシーケンスとして、他に以下のような種類がある。

注意することは、

\’ \”

と書いた場合、文字列リテラルを表現するときの、引用符をエスケープしていることになる。よって、

print '\'
print "\"

は、エスケープシーケンスと解釈され、引用符が閉じられていないことになる。文字列リテラルとしては不正。

 

2. バックスラッシュを出力するには

バックスラッシュ (\) はエスケープシーケンスを表現するために用いられる。そのため、バックスラッシュを表現するには、

\\

と書く。

print '\\'     #=> \
print '\\\\'   #=> \\

 

 

2. row string を使ったバックスラッシュの表現

文字列リテラルの前に `r’ をつけると row string になる。

2.4.1 文字列リテラル によると、

接頭文字 "r" または "R" がある場合、バックスラッシュの後にくる文字はそのまま文字列中に入りバックスラッシュは全て文字列中に残されます

例えば、

print r'hoge\npiyo'   #=> hoge\npiyo

このような結果になるのは、

  1. バックスラッシュの後にくる文字 `n’ は、そのまま文字列に入り
  2. `n’ の前のバックスラッシュは文字列中に残される

Python におけるエスケープシーケンスは、

標準の C とは違い、認識されなかったエスケープシーケンスはそのまま文字列中に残されます。(同上より)

例えば、

print '\c'    #=> \c

だから、先ほど `n’ の前の文字列中に残されたバックスラッシュ (\) は、そのまま表示される。

 

バックスラッシュの場合

バックスラッシュを出力するために、row string を利用する。

print r'\\'     #=> \\
print r'\\\\'   #=> \\\\

このような結果になるのは、

  1. バックスラッシュの後ろにくる文字 `\’ はそのまま文字列に入り
  2. の前のバックスラッシュは文字列中に残される。

`r’ を文字列の前につけることにより、エスケープシーケンスの解釈を変化させる

最初に書いたように、以下の記述はエラーとなる。

print r'\'

なぜなら、そもそも \’ は一重引用符をエスケープしているため、全体として文字列リテラルとなっていないため。これにより、文字列リテラルの前にあることで意味を持つ `r’ が意味を持たなくなってしまっている。

同様に、以下の記述は、

print r'\\\'

最初の \\ が Python における \ と解釈されるが、最後の \’ によって文字列リテラルとならずエラーとなる。同じく接頭辞 `r’ は意味を持たない。

これは文字列リテラルの定義を見ると、明らかになる。

stringprefix の中にある `r’ は、shortstring, longstring という引用符に囲まれた文字列の前にあることによって意味をなす。よって、引用符で囲まれた文字列がないと意味を成さない。

上記の動作を考えると、

  1. 最初に \\ が \ と解釈され、
  2. 次に \’ \” のエスケープシーケンスがチェックされ、
  3. 文字列リテラルかがどうか確認された後に `r’ としての解釈が行われる

ということだろうか。

 

3. 正規表現

正規表現において、バックスラッシュ (\) は * や . を、特別な記号として解釈されないために用いられる。

バックスラッシュを表すには `\\’ と書く必要がある。

そのため、Python の文字列の中で

「正規表現のバックスラッシュ」

を表すには、

  1. 「正規表現」においてバックスラッシュ \ を、バックスラッシュでエスケープする。 \\
  2. 「Python」 では \ を表現するのに \\ を用いる。そのため、正規表現 \\ を文字列リテラルとして用いるには、 \\\\  とする。

つまり、「正規表現 → Python におけるリテラル表現」というように、二段階考える必要がある。

例えば、以下のコードは、

import re
print re.sub('\\\\', 'Hoge', '\\\\')   #=> HogeHoge

正規表現で言うなら、

文字列 \\ に対して、正規表現で \ に相当するものを Hoge で置き換える

という意味になる。(sub メソッド の第 1 引数は、正規表現のパターンを表現している。)

 

row string

正規表現を表す文字列に対して、row string を用いてみる。

print re.sub(r'\\', 'Hoge', '\\\\')   #=> HogeHoge

接頭辞 r により、 \ の解釈が変化し、\\ を表現していることになる。

これは正規表現において \ をエスケープしていることになるので、\ そのものが置換の対象となる。対象の文字列は \\\\ なので、Python の文字列リテラルにおいて \\ を表わす。よって、\\ に対して \ を Hoge で置き換えるという意味になる。

同様に考えれば、

print re.sub(r'\\', 'Hoge', r'\\\\')    #=> HogeHogeHogeHoge
print re.sub('\\\\', 'Hoge', r'\\\\')   #=> HogeHogeHogeHoge

正規表現のレベルで書けば、どちらも

「文字列 \\\\ に対してパターン \\ で Hoge に置き換える」

ということになる。つまり人間から見れば、\ を Hoge で置き換えるということ。

あ~、ややこしい~ (@_@;)