Python学習チャンネル by PyQ

Pythonのオンライン学習プラットフォームPyQのオフィシャルブログです

【Pythonお悩み解決】繰り返している単語を正規表現で1つにまとめたいのですが、消えてしまいました。何が違うのでしょうか?

こんにちは、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をつけ忘れると、エラーにならずに想定外の結果になることがあるので注意しましょう。

Copyright ©2017- BeProud Inc. All rights reserved.