nikkie-ftnextの日記

イベントレポートや読書メモを発信

Pythonライブラリをpipで管理するとき、uninstallが私にはツラい

はじめに(なにか始まったぞ...)

(この物語は、Python 3.11.8で作った仮想環境でお届けします)

あるところにtransformersをインストールして開発を進めるPythonプロジェクトがありました。

% pip install transformers

そのプロジェクトではrouge-scoreも追加でインストールしました1

% pip install rouge-score

プロジェクトが進む中でrouge-scoreだけをアンインストールすることになりました。
さあどうしますか?

目次

単にpip uninstallしては?

Pythonチュートリアルではpip uninstall rouge-scoreが案内されます。
https://docs.python.org/ja/3/tutorial/venv.html#managing-packages-with-pip

pip installの逆操作ですね。
しかしこれだと不都合があります。

% pip uninstall rouge-score
Found existing installation: rouge_score 0.1.2
Uninstalling rouge_score-0.1.2:
  Would remove:
    /.../.venv/lib/python3.11/site-packages/rouge_score-0.1.2.dist-info/*
    /.../.venv/lib/python3.11/site-packages/rouge_score/*
Proceed (Y/n)? Y
  Successfully uninstalled rouge_score-0.1.2

pip uninstall rouge-scoreではrouge-scoreしかアンインストールされません
rouge-scoreが依存するライブラリも不要になっていますが、残っているのです。

transitiveな依存

以下の記事の中でtransitiveという語を使いました。

ルーツはこちらです。

今回のPythonプロジェクトの場合、直接(direct)の依存がtransformersとrouge-scoreです。
transformersとrouge-scoreが依存する数々のライブラリが(プロジェクトから見て)transitiveな依存となります。

rouge-scoreをアンインストールする場合、rouge-scoreが依存するライブラリ(プロジェクトから見てtransitiveな依存)もアンインストールしたいです。
しかしながら、rouge-scoreとtransformersとで共通の依存ライブラリは(transformersで使うので)アンインストールしたくはありません

箇条書きでまとめてみます

  • プロジェクトの直接の依存
    • transformers
    • rouge-score
  • rouge-scoreをアンインストールする
  • rouge-scoreの依存(プロジェクトから見てtransitiveな依存)のうち
    • rouge-scoreのみが依存するライブラリはアンインストール
    • transformersも依存ライブラリは残す(アンインストールしたら環境が壊れてしまうため)

transitiveな依存の一部を残すのがツラい

私の知っている方法を記録のために書きます(アップデートしたい!)

transitiveな依存を把握するためにツールを使います。

CLIから使うPythonライブラリなのでpipx2で入れました(pipx install)。

依存を木構造で書き出します。

% pipdeptree --python .venv/bin/python
pip==24.0
rouge_score==0.1.2
├── absl-py [required: Any, installed: 2.1.0]
├── nltk [required: Any, installed: 3.8.1]
│   ├── click [required: Any, installed: 8.1.7]
│   ├── joblib [required: Any, installed: 1.4.0]
│   ├── regex [required: >=2021.8.3, installed: 2024.4.16]
│   └── tqdm [required: Any, installed: 4.66.2]
├── numpy [required: Any, installed: 1.26.4]
└── six [required: >=1.14.0, installed: 1.16.0]
setuptools==69.5.1
transformers==4.40.1
├── filelock [required: Any, installed: 3.13.4]
├── huggingface-hub [required: >=0.19.3,<1.0, installed: 0.22.2]
│   ├── filelock [required: Any, installed: 3.13.4]
│   ├── fsspec [required: >=2023.5.0, installed: 2024.3.1]
│   ├── packaging [required: >=20.9, installed: 24.0]
│   ├── PyYAML [required: >=5.1, installed: 6.0.1]
│   ├── requests [required: Any, installed: 2.31.0]
│   │   ├── certifi [required: >=2017.4.17, installed: 2024.2.2]
│   │   ├── charset-normalizer [required: >=2,<4, installed: 3.3.2]
│   │   ├── idna [required: >=2.5,<4, installed: 3.7]
│   │   └── urllib3 [required: >=1.21.1,<3, installed: 2.2.1]
│   ├── tqdm [required: >=4.42.1, installed: 4.66.2]
│   └── typing_extensions [required: >=3.7.4.3, installed: 4.11.0]
├── numpy [required: >=1.17, installed: 1.26.4]
├── packaging [required: >=20.0, installed: 24.0]
├── PyYAML [required: >=5.1, installed: 6.0.1]
├── regex [required: !=2019.12.17, installed: 2024.4.16]
├── requests [required: Any, installed: 2.31.0]
│   ├── certifi [required: >=2017.4.17, installed: 2024.2.2]
│   ├── charset-normalizer [required: >=2,<4, installed: 3.3.2]
│   ├── idna [required: >=2.5,<4, installed: 3.7]
│   └── urllib3 [required: >=1.21.1,<3, installed: 2.2.1]
├── safetensors [required: >=0.4.1, installed: 0.4.3]
├── tokenizers [required: >=0.19,<0.20, installed: 0.19.1]
│   └── huggingface-hub [required: >=0.16.4,<1.0, installed: 0.22.2]
│       ├── filelock [required: Any, installed: 3.13.4]
│       ├── fsspec [required: >=2023.5.0, installed: 2024.3.1]
│       ├── packaging [required: >=20.9, installed: 24.0]
│       ├── PyYAML [required: >=5.1, installed: 6.0.1]
│       ├── requests [required: Any, installed: 2.31.0]
│       │   ├── certifi [required: >=2017.4.17, installed: 2024.2.2]
│       │   ├── charset-normalizer [required: >=2,<4, installed: 3.3.2]
│       │   ├── idna [required: >=2.5,<4, installed: 3.7]
│       │   └── urllib3 [required: >=1.21.1,<3, installed: 2.2.1]
│       ├── tqdm [required: >=4.42.1, installed: 4.66.2]
│       └── typing_extensions [required: >=3.7.4.3, installed: 4.11.0]
└── tqdm [required: >=4.27, installed: 4.66.2]

出力はファイルにリダイレクトして参照しました。
アンインストールしたいrouge-scoreについて、木構造にあたるライブラリを確認します。

  • rouge-scoreのみに依存するtransitiveな依存(=一緒にアンインストールできる)
    • absl-py
    • click
    • joblib
    • (葉ではないが nltk)
    • six
  • transformersにも依存するtransitiveな依存(=アンインストールしてはならない)

というわけで、rouge-scoreと一緒にアンインストールできるライブラリも消すコマンドは

% pip uninstall -y rouge-score absl-py click joblib nltk six

となります

終わりに

依存ライブラリをpipで管理すると、直接の依存をアンインストールするときに、一緒にアンインストールできるtransitiveな依存を把握するのが大変なことを書きました。

思うに、pip以外のツールが必要なタイミングなのだと思います。
Poetry、Pipenvなどなど色々なツールがありますよね。
直接の依存をaddしたらremoveはツールに任せられる、という理解です。

単一のスクリプトを開発する場合なら直接の依存の増減はあまりないのでpipで事足りてきたのではないかと思うのですが、より大きなアプリケーションの開発では直接の依存を変更しやすくするためにツールの使用を検討したいですね

登壇報告 | #vscodejp にて、VS Codeで文字列のちょっとした変換ができることと、その実装の正規表現の読み解きを発表しました

はじめに

VS Code Conference Japan 2024、ありがとうございました!
nikkieです。

このエントリは、タイトルが全てです。

目次

VS Codeで文字列のちょっとした変換ができるんです!〜実装まで覗くクイックツアー〜

VS Codeで文字列のちょっとした変換ができるんです!〜実装まで覗くクイックツアー〜 by nikkie | プロポーザル | VS Code Conference Japan 2024 #vscodejp - fortee.jp

資料類

アーカイブは2:19:06あたりからです。

発表中の様子

Togetterにまとめました(想い出バックアップ)

私のトークはpage=9〜10のあたりです。

いくつかお声を紹介します。

便利ですよね! 伝えられてよかったです🙌

>で行き来できるぞ〜!🙌

この発表の半分は正規表現で占められていました。
\p{L}Unicode文字クラスエスケープ
\d\wの一般化で、Unicode文字のプロパティで文字の集合を指定しています

質問に感謝

Q: 文字列変換のベースクラスがあるということだったが、"僕が考えた最強の文字列変換"を作りたいときはそのクラスを使って実装できるのか、まっさらなところから実装しないといけないのか (2:34:53)

質問ありがとうございます。
宿題とさせていただきます。

  • まっさらなところから、文字列変換コマンドを実装している例の1つが上☝️のツイート
  • 紹介したAbstractCaseActionでもできるのではないかという気がするので、宿題として検証します

発表後記

今回の発表の元は以下のエントリです。

VS Code拡張を作ったことがあったので1実装も少しは読めるぞと記事中で覗いたのを、今回さらに掘り進めました。

Unicode文字クラスエスケープを知ったときは知的興奮がやばかったですね。
知らないものを知れて、たーのしーー!
またJavaScript正規表現まわりでかなり理解が深まりました。

私はPythonが好きで、暇があったら標準ライブラリの実装を覗くくらい好きなんですが(だって好きな相手のことは全部知りたいじゃないですか!2)、VS Codeのこともかなり好きなんだなと認識しました。
VS Code Meetupは現在LTを募集するスタイル3のようなので、登壇駆動でVS Codeの実装を読み進めてみたいなと思います。

このたびは登壇の機会をいただき、また聞いていただき、質問までいただき、ありがとうございました!
私の発表は(数点tipsを共有しつつも)VS Codeの実装に使われている正規表現を突然読み解き始めるというカオスな時間だったかと思いますが、持って帰れる知見の共有とかおいておいて実装についてガーッと話した今回のひとときは発表者としてはとても楽しかったです。

P.S. 関連ブログエントリ


  1. どうかしてるぜ!
  2. 歩夢ちゃんを纏っています
  3. 今回からしばらくLTを中心に、2ヶ月おきに構成した開催を行います。VS Code Meetup #28 - connpass

イベントレポート | Bedrock Claude Night、Anthropic社Maggie VoさんのキーノートでClaudeを完全に理解した! #jawsug_aiml

はじめに

クロちゃんです!1 nikkieです

世はまさに大LLM時代。
OpenAIのGPTにうつつを抜かしていたところ、GeminiやClaudeも登場。
Claudeを高く評価する声を見かけて気になっていましたが、このたび開発元のAnthropicの話を聞く機会がありました。

目次

勉強会の概要

JAWS-UG AI/ML支部、東京支部の合同イベント。
目黒 & YouTube Liveのハイブリッドで開催されました

オンラインは限定公開ですが、後日アーカイブ公開されるようです。
Togetterにまとめてみました。

私の一推しポイントはこちら:

Anthropic エンジニアチームがビデオ出演!

以前OpenAI DevDayで一次情報を得られた経験が大きかった2ので、今回もAnthropicの方からお話を聞けるのを楽しみにしていました。
キーノートで印象的だった点をメモに残します

安全性のAnthropic

AIの安全性に重きをおいているんだなと知りました。
言われてみると、このスタンスはOpenAIやGoogleとの差別化ポイントかも

ジェイルブレイク3耐性を見て、有言実行しているという印象を受けました。
棒グラフはジェイルブレイクの成功率で低いほうが良いです。
Claude 2で0%、3でさらに改善しているとのこと

積ん読

Claude 3はどうして3つもモデルがあるの?

利用者に選択肢を提供してくれているという理解です。

Anthropicの立場として、one size fits all(どんなユースケースにも対応できる万能のモデル)は存在しないとのこと。
コストとインテリジェンス(性能)の2軸でトレードオフがあります。

https://www.anthropic.com/news/claude-3-family より画像

Haiku/Sonnet/Opusと3つあるのは、トレードオフを考慮して利用者が選択するためなんだと気づきました。
タスクごとにコスト(料金や処理時間)と性能の要求は異なりますもんね!

印象的だった例がこちら

数千のスキャンされたドキュメントデータ(画像)をHaikuで構造化しています。
大量データを捌くという観点でHaikuが選ばれているんでしょうね。
Haikuは2秒で1冊分読める速さとも紹介されており、納得の選択です(上のグラフで賢さがめちゃ落ちているわけでもないですし)。
恥を忍んで告白すると、最高性能のOpusで常に殴ればいいと考えていました...

その他印象的だった点

Claude 3はマルチモーダル(視覚も持つ)のデモでびっくりしたのがこちら。
エディタで2つのファイルをside-by-sideで開いたスクリーンショットと「ユニットテストを書いて」で、テストコードが出力されたんです!
Copilotのようにコードを見ているならまだしも、画像4からいけるのはすごすぎる!

手書きのホワイトボードも認識!
構造化してくれるとのこと

あとはClaude 3のモデル間のエージェントですね。
こんなことできるの!?

終わりに

Bedrock Claude Nightのキーノートの印象に残った点のレポートでした。
キャッチアップの手が回っていなかったClaude、今回のイベントで完全に理解した(気がする)!

  • 安全性に重きをおいて開発(ジェイルブレイク成功率 0%)
  • 開発者に選択肢を与える3つのモデル(トレードオフを考慮して選択)

キーノートではAnthropicのサイト内のさまざまなリソースも紹介され、(興味深いLTも目白押しで)お腹いっぱいです!
今回は貴重な機会をありがとうございました!


  1. https://dot.asahi.com/articles/-/14732?page=1 より。これは引用なんですが、ブログの始まり方がカオスですねw
  2. いくつか記事を書いています
  3. 参考:ChatGPTが答えられない質問でも強引に聞き出す「ジェイルブレイク」が可能になる会話例を集めた「Jailbreak Chat」 - GIGAZINE
  4. からあげさんの記事を過去に見ていました。 画像からユニットテストという今回の例は、私にはあまりにもやばいです

VS Code Conference Japan 2024 #vscodejp より、感想「GitHub Copilot活用、現場の声(経験者)パネルディスカッション!」

はじめに

サリーちゃん😭 nikkieです。

VS Code Conference Japan 2024に参加しました1
GitHub Copilotについてのパネルディスカッションを聞いて、「Copilotを使っていかなきゃ!」と思い立ち、一本書きます。

目次

GitHub Copilot活用、現場の声(経験者)パネルディスカッション!

内容紹介

本セッションでは、4人のGitHub Copilotのユーザに登壇いただき、パネルセッション形式でそれぞれの使い方や、その勘所を語っていただきます。(特設サイトのタイムテーブルより)

パネラーには以下を書かれたお二方もいらっしゃいました!

アーカイブ情報

アーカイブは 5:14:40 あたりから

Togetterはこのあたりです。
https://togetter.com/li/2353434?page=16

挙がった資料を確認

サイバーエージェントGitHub Copilot導入と 開発生産性

「Copilotを活用したコーディングのポイント」のスライド

実装を始める前にコメントで実装内容を記述

  • (斜体は感想)100点のコードを期待せず、ざっくり補完させるってことかな

生成されたコードをベースに実装を拡張していく

  • 自分で書くのが私にとって一番面白いから、生成コードを優先させ続けるのはちょっと受け入れがたいな〜
  • 実装を始める前に参考にしたいコードをコメントとして貼っておく

  • 参考にしたいコードの下にそれっぽい変数名を定義する

LLMが得意な方向にお膳立てしている感(一から自分で書くより結果的には速いのかな)

Copilotだけでなく、ChatGPTも使い倒していて2興味深かったです。

GitHub Copilotでできること一挙ご紹介

パネルディスカッションで「GitHubの田中さん3の資料」と挙がった資料です4

3つのパートからなっていました

  1. コード補完
  2. チャット
  3. エディタ機能への統合

1はCopilotの話で、「いかに適切な文脈を渡すか」を追求しています5

  • 関連するファイルは常に開いておこう
  • 名前(一貫性のある命名規則
  • コメントとして、欲しいコードについての説明を自然言語で書く
  • 自分でコードを書き始めてみる
    • =文脈となるコードを書く

積ん読です

2と3はCopilot Chatについて。
3つの概念を紹介

  • コンテキスト変数(#で始まる)
  • スラッシュコマンド(/で始まる)
  • エージェント(@で始まる)

Copilot Chatはエディタ機能への統合が進んでいて、インラインでチャットできるそうです。
Fix using Copilot」や、「Rename Symbol」7でCopilotの提案もあるんだとか!

次に読みたい「GitHub Copilot Patterns & Exercises」

上の資料から始めて情報収集する中で、1人の人物のお名前を何度も拝見しました。
その方の名は、GitHubのHattoriさん(@yuhattor)8
https://speakerdeck.com/yuhattor

Hattoriさんの資料の中に繰り返し登場するのが、「GitHub Copilot Patterns & Exercises」。
ご本人が翻訳されています

こんなにパターン化されているのですね。
積ん読にけって〜い!

終わりに

VS Code Conferenceを機に、GitHub Copilotを使っていこうと使い方を調べました。
CopilotはContext Is All You Need感、文脈重要。
ノウハウをインプットしながら、日々のコーディングで試していきます!

LLMの話題の中でCopilotをあまり追っていなかったのは、コーディングは一番楽しいところなので機械に譲り渡したくないという思いがあったんですよね。
このあたり完全に払拭されたわけではないのですが、Copilot(などのAI)によってコーディングのあり方は変化していることをパネルディスカッションで感じました。
受け身に回って変化を強いられるよりは、積極的に試していこうと思っています。

P.S. ことみん先生のお言葉

Copilot、使いこなすぞ!!9


  1. 登壇報告は改めて
  2. https://speakerdeck.com/kurochan/saihaesientonogithub-copilotdao-ru-to-kai-fa-sheng-chan-xing?slide=55
  3. システム運用アンチパターン』(など)を翻訳されている方でした
  4. 確認も取れています。https://twitter.com/Horie1024/status/1781637061008908498
  5. RAGのR(retrieval)の部分に対して、人ができることが多い事例なのかなと思います
  6. 重宝しています
  7. 過去のVS Code Meetupでも話されています(11:10〜)
  8. 素晴らしい研修資料だと思います

VS Codeで「Transform to Title Case」を担当するTitleCaseActionの実装の中の正規表現を読み解く

はじめに

エミリーちゃん、小文字にしたいな〜。nikkieです。

4/20(土)のVS Code Conference Japan 2024登壇1準備からアウトプットです。

目次

Transform to Title Case

VS Codeで文字列を選択して、コマンドパレットから「Transform to Title Case」でタイトルケースに変えられます。
タイトルケースとは、Pythonのドキュメントがわかりやすいと思います。
https://docs.python.org/ja/3/library/stdtypes.html#str.title

文字列を、単語ごとに大文字から始まり、残りの文字のうち大小文字の区別があるものは全て小文字にする、タイトルケースにして返します。

例:emily stewart -> Emily Stewart

過去の記事でも取り上げました。

この実装を見ていきます。

TitleCaseActionの持つ正規表現

https://github.com/microsoft/vscode/blob/1.88.1/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts#L1135

export class TitleCaseAction extends AbstractCaseAction {

    public static titleBoundary = new BackwardsCompatibleRegExp('(^|[^\\p{L}\\p{N}\']|((^|\\P{L})\'))\\p{L}', 'gmu');

    // 省略
}

BackwardsCompatibleRegExpクラスはgetメソッド呼び出しで、RegExpまたはnullを返します。
https://github.com/microsoft/vscode/blob/1.88.1/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts#L1116
backwards compatibleのこころは、後方互換性がないときはnullが返るってことなのかな?

この記事で見ていく正規表現RegExp('(^|[^\\p{L}\\p{N}\']|((^|\\P{L})\'))\\p{L}', 'gmu')です2

Unicode文字クラスエスケープを使った正規表現

この正規表現、ウッとなりますね。
呪文詠唱です。

'\\p{L}''\\p{N}'は文字列中だからバックスラッシュがついていて、\p{L}\p{N}と同じです。
これはUnicode文字クラスエスケープというものでした。
Unicode文字の集合を指定しています。

https://unicode.org/reports/tr18/#General_Category_Property より

  • LはLetterの短縮
  • NはNumberの短縮

この正規表現どんな文字列と一致するかを理解するのが今回のゴールです。
ここでテストデータをガイドにします。

RegExp('(^|[^\\p{L}\\p{N}\']|((^|\\P{L})\'))\\p{L}', 'gmu')をにらむと、第1引数の正規表現にはキャプチャグループのカッコ()3があることが分かります。
'(キャプチャグループ)\\p{L}'
※後述しますが、replaceメソッドにこの正規表現と関数を渡します。関数シグネチャは第1引数match(一致した部分文字列)4のみなので、キャプチャグループに対応する文字列は使われていないという理解です

キャプチャグループのカッコの内側は|で、いずれか(=または)となっていますね。

^|[^\\p{L}\\p{N}\']|((^|\\P{L})\')
  • ^(行頭)
  • 文字集合 [^\\p{L}\\p{N}\']
    • \p{L} \p{N} シングルクォート)でない(^)
    • =文字でも数字でもシングルクォートでもない
    • 半角スペースはマッチしそうです
    • シングルクォートはrock'n'rollのように単語の中で使うから指定していそうですね!
  • キャプチャグループ ((^|\\P{L})\')
    • 行頭または\P{L}(のグループ)にシングルクォートが続く(というグループ)
    • \P{L}は、Letter(=\p{L}でない(例えば半角スペースが該当)

ここまでの読み解きを元に、開発ツール(Firefox)のコンソールで手を動かします。
※gフラグを指定した正規表現のmatchでは、正規表現全体に一致した結果を返し、キャプチャグループは返しません5

'hello world'.match(new RegExp('(^)\\p{L}', 'gmu'));  // 行頭の1文字が取れる
// Array [ "h" ]

'hello world'.match(new RegExp('([^\\p{L}\\p{N}\'])\\p{L}', 'gmu'));  // 2語目以降のスペース+1文字目
// Array [ " w" ]

'hello \'world'.match(new RegExp('(((^|\\P{L})\'))\\p{L}', 'gmu'));  // 2語目以降のスペース+シングルクォート+1文字目。new RegExp('(((\\P{L})\'))\\p{L}', 'gmu') がマッチ
// Array [ " 'w" ]

'\'physician\'s assistant\''.match(new RegExp('(((^|\\P{L})\'))\\p{L}', 'gmu'));  // 行頭のシングルクォートと1文字目。new RegExp('(((^)\'))\\p{L}', 'gmu') がマッチ
// Array [ "'p" ]

どうやら単語の先頭の部分を取り出す正規表現みたいですね!

正規表現で先頭部分を取り出してreplace

この正規表現を指す変数名がtitleBoundaryというのはうまい名前だなと思います(タイトルケースの境界

// https://github.com/microsoft/vscode/blob/1.88.1/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts#L1152-L1154
return text
    .toLocaleLowerCase()
    .replace(titleBoundary, (b) => b.toLocaleUpperCase());

文字列(text)を一度小文字にしてから(toLocaleLowerCase)、取り出した先頭部分を大文字に変える(toLocaleUpperCase)ことでタイトルケースになります!

regex = new RegExp('(^|[^\\p{L}\\p{N}\']|((^|\\P{L})\'))\\p{L}', 'gmu');
// -> /(^|[^\p{L}\p{N}']|((^|\P{L})'))\p{L}/gmu

'hello world \'world'.match(regex);
// -> Array(3) [ "h", " w", " 'w" ]
'hello world \'world'.replace(regex, (b) => b.toLocaleUpperCase());
// -> "Hello World 'World"

'\'physician\'s assistant\''.match(regex);
// -> Array [ "'p", " a" ]
'\'physician\'s assistant\''.replace(regex, (b) => b.toLocaleUpperCase());
// -> "'Physician's Assistant'" 

終わりに

VS Codeの「Transform to Title Case」を担うクラスTitleCaseActionの実装で使われている正規表現を読み解きました。

  • 正規表現 RegExp('(^|[^\\p{L}\\p{N}\']|((^|\\P{L})\'))\\p{L}', 'gmu') は、タイトルケースの境界にマッチする
    • 行頭
    • (行頭ではない)単語の始まり(半角スペース + 1文字)
    • 英単語が含むシングルクォートも考慮
  • 境界には単語の先頭の1文字だけでなく、その前にある半角スペースやシングルクォートも含む
  • 一度小文字にしてから、タイトルケースの境界を大文字に変える
    • 半角スペースやシングルクォートはそのままで、含む1文字だけ大文字になる=タイトルケース

\p{L}は全角文字も含むので、全角アルファベットのタイトルケースもできますね!
EMILY stewart -> Emily Stewart

P.S. 読みやすくしたいな〜

こちらを思い出していました。


  1. 文字列のちょっとした変換について話します
  2. 第2引数のフラグは、グローバル検索(g)・複数行の検索(m)・Unicode文字クラスエスケープのためのunicode(u)です。gとmはこちらをどうぞ
  3. 一致した内容を記憶してくれます。グループと後方参照 - JavaScript | MDN
  4. String.prototype.replace() - JavaScript | MDN
  5. String.prototype.match() - JavaScript | MDN をどうぞ

JavaScriptの正規表現のコードに見た\pや\Pって何だ? Unicode文字クラスエスケープ!

はじめに

ほ!!! nikkieです

正規表現について、新しく知ったことがありました。
\p{L}ってなんだと思います?

目次

MDNより「Unicode 文字クラスエスケープ」

Unicode 文字クラスエスケープは文字クラスエスケープの一種で、Unicode プロパティで指定された一連の文字に一致します。

文字クラスエスケープとは、正規表現\d\w
これは見たことあります。

文字クラスエスケープは、文字の集合を表すエスケープシーケンスです。
文字クラスエスケープ: \d, \D, \w, \W, \s, \S - JavaScript | MDN

つまり\pや\Pは文字の集合を表すエスケープシーケンスというわけですね。

Unicode 文字クラスエスケープ」のドキュメントは次のように続きます。

これは Unicode 対応モードでのみ対応しています。

これはuフラグのことですね1
正規表現 - JavaScript | MDN

解説によると

すべての Unicode 文字には、それを記述する一連のプロパティがあります。

例えば、a という文字では、General_Category プロパティが Lowercase_Letter の値であり、Script プロパティが Latn の値です。

な、なんだってー! プロパティなんてものがあったのか

例えば、a は \p{Lowercase_Letter}(General_Category プロパティ名はオプション)と、\p{Script=Latn} によって一致させることができます。

\Pは\pの否定です

Unicode 文字クラスエスケープ」中の例

一般カテゴリー2\p{L}の例があります。
上で引いた「General_Category プロパティ名はオプション」を補足するような形になっています。

以下は正規表現リテラルとして同じ

  • /\p{L}/gu
  • /\p{General_Category=Letter}/gu(General_Categoryというプロパティ名を書いた)
  • /\p{Letter}/gu(General_Categoryを省略)

ブラウザ(Firefox)の開発ツールのコンソールで実行します。

const story = "It's the Cheshire Cat: now I shall have somebody to talk to.";
story.match(/\p{L}/gu);
Array(46) [ "I", "t", "s", "t", "h", "e", "C", "h", "e", "s", … ]

空白文字や記号を除いた文字にマッチしました!
story.match(/\p{General_Category=Letter}/gu);のように書き換えても同じ結果です。

Unicode Technical Standard #18 を参照

Unicode Technical Standard #18 Unicode Regular Expressions」に「General Category Property」があります(1.2.5)3
https://unicode.org/reports/tr18/#General_Category_Property

この表には、プロパティの値とその短縮名(Long formとAbb.)が記載されています。

  • Letter(短縮名はL)
  • Uppercase Letter(短縮名はLu)

大文字小文字やスペース、アンダースコアの有無など表記が揺れても、指す文字の集合としては同じとのことです。

any of the following should be equivalent: \p{Lu}, \p{lu}, \p{uppercase letter}, \p{Uppercase Letter}, \p{Uppercase_Letter}, and \p{uppercaseletter}

正規表現辞典』を紐解く

03-04-06 \p{...}、\P{...} Unicodeプロパティに基づく条件に合致する文字にマッチ

例が面白いなと思いました。

  • \p{Lu}(Uppercase Letter)は、例えば全角のGにもマッチ
  • \p{InHiragana}でひらがなにマッチ
"半角のG 全角のG".match(/\p{Lu}/gu);
Array [ "G", "G" ]

"半角のG 全角のG".match(/\p{InHiragana}/gu);

Uncaught SyntaxError: invalid property name in regular expression

でした。未サポートなんですかね?

Pythonではregex

標準ライブラリreの拡張。
Unicode codepoint propertiesもサポートしています!

>>> regex.findall(r"\p{Lu}", "半角のG 全角のG")
['G', 'G']
>>> regex.findall(r"\p{InHiragana}", "半角のG 全角のG")
['の', 'の']

Python 3.10.9、regex 2024.4.16で動かしています

終わりに

Unicode文字クラスエスケープを知りました。

  • \p{}\P{}{}中にUnicode文字のプロパティを指定できる
  • \dのように文字の集合を指定しているということ
    • 例えば\p{Lu}でUppercase Letterを指定。これは半角だけでなく全角などにもマッチする
  • JavaScriptではunicodeフラグを指定した正規表現リテラル

InHiraganaなど、Unicode文字クラスエスケープで指定するのは便利そうな気がします。

P.S. そもそもどこで\pを見かけた?

VS Codeの実装を覗いていて!4
https://github.com/microsoft/vscode/blob/1.88.1/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts#L1135

public static titleBoundary = new BackwardsCompatibleRegExp('(^|[^\\p{L}\\p{N}\']|((^|\\P{L})\'))\\p{L}', 'gmu');

JavaScript正規表現リテラルには、本文で紹介した/\p{Lu}/guの記法の他に、RegExpオブジェクトを使う方法があります5
後者の方法ではバックスラッシュを使って特殊文字エスケープします。
なので、'\\p'となると理解しました。
ref: 正規表現 - JavaScript | MDN

"半角のG 全角のG".match(new RegExp("\\p{Lu}", "gu"));
Array [ "G", "G" ]

  1. 過去にある正規表現を理解するために書いた記事でもフラグに言及しました
  2. プロパティ名 General_Category の日本語訳と思われます
  3. この記事で参照しているMDNドキュメントの「例」の「一般カテゴリー」で示されていました
  4. この記事は登壇準備の一環です
  5. https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions#%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE%E3%81%AE%E4%BD%9C%E6%88%90

argparseで解析した引数に型をもたせる(VS Codeで解析結果の属性の型がAnyになる問題の解消方法)

はじめに

ナムコ! nikkieです。

標準ライブラリの中でとりわけヘビーユースしているargparse1
Pythonスクリプトコマンドラインツールにできます2

argparseを使ったスクリプトVS Codeで開発するうえで、型の表示を改善する小ネタです。

目次

parse_argsが返すNamespaceの属性の型がAny問題

% python -V
Python 3.11.8
import argparse
from pathlib import Path

parser = argparse.ArgumentParser()
parser.add_argument("path", type=Path)
args = parser.parse_args()

print(args.path)

parse_args()Namespaceインスタンスを返します。
Namespaceとは「単に読みやすい文字列表現を持った object のサブクラス3

args.pathの部分にマウスカーソルを当てると、型はAnyです。

parse_argsには、namespace引数がある

VS Codeで対処法はないかな〜」と、Pylanceまわりを調べていたら、こんなコメントが!
https://github.com/microsoft/pylance-release/issues/628#issuecomment-730628740

@dataclass
class Args:
    x: int
...
parser.add_argument('--x', type=int)
parser.parse_args(['--x', '123'], Args())

「こんなことできるの!?」とドキュメントを見ると、

https://docs.python.org/ja/3/library/argparse.html#the-parse-args-method

namespace - 属性を代入するオブジェクト。デフォルトでは、新しい空の Namespace オブジェクトです。

空のNamespaceにparse_args()属性を代入して返していたんですね。

取り上げたコメントの例をVS Codeで書いたとしましょう。
parse_args()は、Argsクラスのインスタンスに属性xを指定して返します。
返り値の属性xArgsクラスの属性ですから(Anyではなく)intという型が見えます。

上で使ったスクリプトを書き直しましょう。

マウスカーソルを当てると、型はPath🙌

2回型を書くのがちょっと面倒 ー Pydanticを使ってみようかな?

上の例だとデータクラスArgsと、add_argument()で2回同じ型を書くのがちょっと面倒ですよね。
浮かんだアイデアが、Pydanticのデータクラスで型変換を使い、add_argument()に型を書かずに済ませる方法。
Pydanticはパースして出力を保証してくれる4ので、argparseでは型の変換までしないという発想です。

これも動きました!5
pipx run ./script.py path/to/awesome6

終わりに

parse_argsの返り値の属性は型がAny問題、私はすっかり慣れてしまっていたのですが、「つらくないですか?」という声をきっかけに対処法を見つけることができました。
namespace引数でクラスを渡せて、クラスのインスタンスとしてパースできるんですね!
これはますますヘビーユースしてしまいそうです。


  1. 技術同人誌を書くほどです
  2. コマンドラインツールの一歩目はこちらをどうぞ
  3. https://docs.python.org/ja/3/library/argparse.html#argparse.Namespace
  4. ロバストPython』で学びました
  5. Inline script metadataを使っています。
  6. Pydanticはpipxが管理する仮想環境に入るので、VS Code上でPylanceが型を示すうえではMissingImportとなります。それでもargs.pathはPath型ですが、pipxの代わりに自分で管理する仮想環境を作ってもよいかもしれません