3 ポイント 投稿者 GN⁺ 2024-03-21 | 1件のコメント | WhatsAppで共有

Python正規表現における "$" 文字の動作の理解

  • Pythonの re モジュールを使う際、^ は「文字列の先頭」を意味し、$ は「文字列の末尾」を意味すると一般に知られている。
  • しかし $ は常に「文字列の末尾」だけを意味するわけではなく、プラットフォームによって動作が異なる場合がある。
  • Pythonではマルチラインモードが無効なとき、$ 文字は文字列の末尾、または文字列末尾の改行文字の直前にマッチすることがある。

文字列末尾と改行文字へのマッチの違い

  • マルチラインモードが無効な状態で、Pythonで改行文字なしに文字列の末尾へマッチさせたい場合、$ だけを使ってはいけない。
  • 文字列の末尾にマッチさせるには \z\Z を使うことができる。
  • Pythonで re.MULTILINE を使うと、$ は文字列の末尾と各行の末尾(改行文字の直前)にマッチする。

さまざまなプラットフォームでの正規表現の動作比較

  • 複数のプラットフォームで cat\n に対するパターン一致の可否を比較した表から、改行文字を含めた一致が許容されるなら、マルチラインモードで $ を使うのが一貫した動作になることがわかる。
  • 改行文字を含めずにマッチさせたい場合、PythonとECMAScriptを除くすべてのプラットフォームでは \z を使い、PythonとECMAScriptではそれぞれ \Z またはマルチラインモードなしの $ を使う必要がある。

GN⁺の見解

  • この記事は、正規表現を使う開発者に対して、Pythonにおける $ 文字の予想外の動作への注意を促す内容になっている。
  • 正規表現は文字列処理において非常に強力だが、プラットフォームごとに異なる動作を示すことがあるため注意が必要であることを強調している。
  • 開発者はこうした違いを認識し、クロスプラットフォームアプリケーションを開発する際に互換性の問題を避けるため、追加のテストを行う必要がある。
  • 類似の機能を提供する他の正規表現ライブラリとしては、Javaの java.util.regex、.NETの System.Text.RegularExpressions などがあり、これらもそれぞれのプラットフォームに合わせた動作の違いを理解したうえで使う必要がある。
  • 新しい正規表現の構文や動作を導入する際には、既存コードとの互換性、性能への影響、そしてチーム内での学習コストを考慮し、その変更がもたらす利点とコストを十分に評価する必要がある。

1件のコメント

 
GN⁺ 2024-03-21
Hacker Newsの意見
  • 正規表現に慣れている人なら、^ が「文字列の先頭」を意味し、$ が「文字列の末尾」を意味することを知っているはず。だが個人的には、これらを「行の先頭」と「行の末尾」と考えている。ほとんどの場合、テキストは1行ずつ扱うので結果は同じだが、これらの演算子を考えるときの見方は変わらない。おそらく grep を通じて正規表現に初めて触れ、主に入力を「行」として考えるからだろう。

    • POSIX正規表現とPythonの正規表現は異なる。一般には、使っている実装の正規表現ドキュメントを参照すべきであり、文法は普遍的ではない。
    • POSIX第9章によれば、正規表現は通常テキスト処理に関連しており、文字列終端を表すNULで終わる文字列に対して動作する。一部のユーティリティは処理を行単位に制限する。$ は文字列の末尾にも行の末尾にも対応しうるが、これはユーティリティ(またはモード)によって定義される。一般的なユーティリティの多く(grep、sed、awk、Pythonなど)は、デフォルトでは行の末尾として扱う。
    • 単一の普遍的な正規表現文法は存在しない。使っている言語とオプションがわからなければ、正規表現を信頼して読んだり書いたりすることはできない。
  • Robert Elder を紹介する絶好の機会だ。彼は YouTube とブログのコンテンツを作っていて、正規表現に関するシリーズを持っており、さまざまなツールの挙動の違いを深く掘り下げている。

    • 彼の最新コンテンツもすばらしい: https://www.youtube.com/watch?v=ys7yUyyQA-Y
    • HNユーザーが興味を持ちそうなコンテンツも数多くあり、たとえばコンサルティングの現実や苦労などがある。
  • 正規表現は、Perl を最初に学んだときに本当に腹落ちした最初のものの一つだった。(Perl は今でも「Camel」本のおかげで心の中に温かい場所を占めている)

    • 今日いちばん重要なのは、実装ごとに違うと知ることであり、自分が作業している対象のリファレンスを引く習慣を身につけることだ。
    • たとえば Emacs の正規表現では、文字クラスとして \w の代わりに \s_-(あるいはリファレンスなしでは画面に何かが出るもの)を使うが、Emacs は文書化と発見しやすさの点で最高だ。
    • 括弧のエスケープを要求するユーティリティもあれば、そうでないものもある。こうした動作が設定可能なこともあれば、そうでないこともある。
    • 混乱、いら立ち、不条理の段階をすべて経験してきて、今ではただ受け入れている。概念はどこでも同じだが、味付けは変わる。
  • ダメな採用担当者たちが、「正規表現で文字列の末尾にどうマッチさせる?」を、彼らの「はっ! お前はひっかけを知らないな!」質問リストに加える様子が目に浮かぶ。

  • 正規表現の話で Perl を一覧から外すのは妙だ。

    • perlre のドキュメントでの $ の説明: 文字列の末尾にマッチする(または文字列末尾の改行文字の前、あるいは /m を使う場合はすべての改行文字の前)
  • Raku(旧 Perl 6)は、文字列の先頭と末尾を表すために ^ と $ を選び、行の先頭と末尾を表すために ^^ と $$ を導入した。複数行モードは利用できないか、必要ない。

    • 完全な棚卸し/書き直しの利点の一つは、以前の挙動が人を驚かせていたという事実から学べることだ。
  • 正規表現が標準化されていると思っている人なんているのか? 新しい文脈に移るたびに、いつも学び直しだ。

  • 文字列と行について混乱がある。文字列は文字の連続であり、行は二つの異なる意味を持ちうる。改行文字を行終端とみなすなら、行は改行文字を含む非改行文字の連続だ。改行文字がなければ完全な行ではない。これはPOSIXが採用している考え方だ。改行文字を行区切りとみなすなら、行は非改行文字の連続だ。どちらの場合でも、行の内容は改行文字の前で終わる。改行文字が行を終端しているか、次の行と区切っているからだ。

    • ^ と $ の意味は、単一行モードか複数行モードかにかかわらず、行に基づいている。文字列ベースの意味――ファイルを扱うときはファイル全体として考えることもある――では、\A\Z、またはそれに相当するものを使う。
  • Ruby ベースのアプリでいくつか深刻なバグにつながった。常に \A\z を使っている。