こんにちは、PyQサポートです。
今回は「エラーにはならないが、想定外の結果になってしまう」という質問への回答を紹介します。
内容としては、正規表現や文字列リテラルについて説明します。
質問: 繰り返している単語を正規表現で1つにまとめたいのですが、出力されません。何が違うのでしょうか?
同じ単語が2つ並んでいるときに、単語を1つに変換するコードを書きましたが、出力されませんでした。何が違うのでしょうか?
import re print(re.sub(r"([a-z]+) \1", "\1", "skip skip"))
実行結果(空の出力)
回答: 置換文字列の書き方を直す必要があります。
re.sub()
は、正規表現を使って対象文字列のパターンに一致する部分を置換文字列に置換する関数ですね。
re.sub(パターン, 置換文字列, 対象文字列)
やりたかったことの確認
質問のコードは、本来、次のような処理をしたいと考えられます。
"skip skip"
を変換前の対象文字列とする- パターンで、繰り返されている2つの単語(skip)をキャプチャする
- 置換文字列として、2つの単語の並びを1つの単語に置き換える
- 結果として変換後の
"skip"
を出力する
出力の可視化
まず、何が起きているか確認しましょう。下記を実行してみます。
print(repr(re.sub(r"([a-z]+) \1", "\1", "skip skip")))
実行結果
'\x01'
repr()
は、「オブジェクトの印刷可能な表現を含む文字列」を返します。repr()
を使うことで、文字コード「1」が出力されました。
参考: docs.python.org
何も出力しないように見えてましたが、実は、文字コードの「1」が出力されてました。この'\x01'
は、"\1"
と書くこともできます。何が起きているかわかりかけて来ましたね。置換文字列の"\1"
が間違っていました。正しくは、r"\1"
でした。
"\1"
とは
"\1"
は1文字で、その文字コードは1です。この文字はprint()
しても見えません。"\1"
はPythonの文字列リテラルの書き方です。バックスラッシュは特殊文字で、続く文字と合わせて別の文字になります。
r"\1"
とは
対して、r"\1"
は2文字で、バックスラッシュ(\
)と「数字の1」の2文字からなります。これは正規表現で、1つ目にキャプチャされた文字列(グループ)を意味します。このように文字列リテラルにr
をつけると、raw文字列になります。raw文字列は通常の文字列とほぼ同じですが、バックスラッシュが特殊文字でなくなります。
参考: docs.python.org
パターンの説明
原因がわかったところで、もう一度パターンを詳しく見ていきましょう。
r"([a-z]+) \1"
"[a-z]"
は、小文字のa
からz
までのいずれかの1文字です。
"+"
は直前の文字の1回以上の繰り返しです。したがって、"[a-z]+"
が小文字のアルファベットの1回以上の繰り返し(すなわち単語)です。
括弧はキャプチャするグループを指定します。したがって、"([a-z]+)"
で単語を1つ目のグループにキャプチャします。
次の" "
は空白1文字です。
最後のr"\1"
が、「1番目のグループと同じ文字列」です。これは正規表現のルールです。
このことから、"skip skip"
がパターンにマッチして、"skip"
が1つ目のグループになります。
置換文字列はr"\1"
なので、"skip skip"
が"skip"
に置換されます。
import re print(re.sub(r"([a-z]+) \1", r"\1", "skip skip"))
実行結果
skip
補足
"([a-z]+)"
は単語の途中からもマッチします。たとえば、下記は"askip"
になります。
print(re.sub(r"([a-z]+) \1", r"\1", "askip skip"))
もし、単語の途中からマッチさせたくない場合は、"([a-z]+)"
の代わりに、r"(\b[a-z]+)"
を使うと良いでしょう。
r"\b"
は、単語の境界を意味します。
下記を実行しても何も変わりません。
print(re.sub(r"(\b[a-z]+) \1", r"\1", "askip skip"))
これは、最初の2文字"as"
の間に、r"\b"
がマッチしないためです。
参考: docs.python.org
まとめ
- 文字列リテラルに
r
をつけるとraw文字列になります。raw文字列はバックスラッシュが特殊文字になりません。 - 正規表現で
r"\1"
は、1つ目のグループになります。 - 単語の境界を指定したいときは、
r"\b"
を使います。
raw文字列にすべきところでr
をつけ忘れると、エラーにならずに想定外の結果になることがあるので注意しましょう。