Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Pythonのパッケージ管理の中級者の壁を超える stapy#98

Pythonのパッケージ管理の中級者の壁を超える stapy#98

以下のstapy#98にて発表したスライドです
https://startpython.connpass.com/event/296755/

PythonのPackage Managerを深く知るためのリンク集
https://gist.github.com/vaaaaanquish/1ad9639d77e3a5f0e9fb0e1f8134bc06#file-python-package-manager-md

vaaaaanquish

October 12, 2023
Tweet

More Decks by vaaaaanquish

Other Decks in Technology

Transcript

  1. 自己紹介 Shunsuke Kawai (@vaaaaanquish) • エムスリー株式会社 VPoE • Google Cloud

    Champion Innovator (AI/ML) • 機械学習ライブラリでOSS活動 • PyCon APAC 2023 Patron • 過去Pythonパッケージ管理について 大きな記事を書いている
  2. 今日する話/しない話 • する話 󰢏 ◦ Pythonのパッケージ管理がどのように作られているか ◦ Pythonのパッケージ管理の何が大変なのか • 前提

    ◦ Pythonのパッケージ管理ライブラリ(pip, pipenv, poetry,…,etc)を開発で一定使っている ◦ ツールが沢山あり、それぞれ何をするものか/どのような機能があるかは大体知っている • しない話 󰢃 ◦ パッケージ管理何使えばいいの? ◦ Pythonでの開発のベストプラクティス ◦ 歴史的なお話
  3. エンジニア採用ページ https://jobs.m3.com/engineer/ プロダクト紹介ページ https://jobs.m3.com/product/ エムスリーテックブログ https://www.m3tech.blog/ 「聴きたい話じゃないかもな…」 と思ったら! - エムスリーをもっと詳しく

    - 社員インタビュー https://www.wantedly.com/companies/m3_inc VPoE登壇資料 fukabori.fm https://fukabori.fm/episode/59 https://fukabori.fm/episode/60 Connpass公式グループ https://m3-engineer.connpass.com/ エンジニア公式Twitter https://twitter.com/m3_engi neering エンジニア公式YouTube https://www.youtube.com/channel /UC_DkAOcwgmtQnJLDctci4rQ
  4. ここまでで言いたいこと • “Package Manager”と言いつつ人類は全てをそこに求めている ◦ 特に他の言語と比較したりされやすい • Pythonは小さいモジュールが機能毎にある ◦ マイクロかモノリシックか、に似たメリット/デメリットがある

    ◦ easyでもone wayでもなく”simple” ▪ PEP/PyPAが作るモジュールを使える ▪ Pythonの拡張容易性の高さを活かせる土台がある ▪ 一部の切り捨てもやりやすくarchivedやmergedなツールも多い ◦ このおかげで”現代の”ソフトウェア開発のベストプラクティスを 詰め込んだようなCargo(Rust)を意識したpoetryやryeが登場している ◦ 逆に2022年のPackaging Surveyでも多かった要望は 「絶対的なただ1つのPackage Manager」という事実もある 現代では大企業がバックに付くプログラミング言語も多い中 (Python Software Foundationがあるとは言え) ボランティアが多いPyPAを筆頭にコミュニティと合議的なPEP規格と OSS文化で30年以上続く土台を作っている。本当にすごい事。
  5. 依存解決をしてみよう Package A >=1.0.0 Package C <=2.0.0 Package AとPackage Cが

    requirements.txt (pyproject.tomlでも何でも) に書いてあるとしましょう
  6. 依存解決の無い世界線 Package A Package B <=2.0.0 Package C Package B

    >=1.0.0 1. Package Aをinstall a. Package Bは2.0.0が入る 2. Package Cをinstall a. Package Bは1.0.0が入る → 最終的に全て満たせていてよさそう!
  7. 依存解決の無い世界線でのアップグレード Package B >=3.0.0 1. Package Aをinstall a. Package Bは2.0.0が入る

    2. Package Cをinstall a. Package Bは3.0.0が入る 順番にinstallすると後からpackage Bを installした時にPackage Aは依存バージョン が満たせなくて困っちゃう💧 Package Cのバー ジョンを上げたら Bがv3.0.0以上 のみサポートに Collecting PackageB> =3.0.0 Using cached B.whl (7.6 kB) ERROR: PackageA x.x.x has requirement PackageB<=2.0.0, but you'll have PackageB 3.0.0 which is incompatible. Installing collected packages: … Successfully installed PackageD-x.x.x PackageB-3.0.0 これが実は昔のpip installの挙動 Package A Package B <=2.0.0 Package C Errorが出てるけどSuccessfully(pip checkで人間が解消したりしてた)
  8. 依存解決をしてみよう Package A ==1.0.0 Package C ==2.0.0 Package B >=1.5.0

    Package D >=1.0.0 Package E >=4.0.0 1. Package Aを1.0.0で一旦固定 a. AはB, Dに依存しているらしい b. Dも一旦1.0.0で固定 i. D 1.0.0はB 2.0.0以下が必要 c. Bは1.5.0以上 2.0.0以下で成り立ちそう d. Bを1.5.0で固定 i. 依存しているEは3.0.0以下が必要 2. Package Cを2.0.0で一旦固定 a. CはEに依存しているらしい b. C 2.0.0はEは4.0.0以上を必要としているが ①で得たEの情報とコンフリクト! 3. “戻って”Package Cを1.0.0で一旦固定 a. 同じくEに依存しているが3.0.0で成り立ちそう  (もう少し探索できるけど一旦ここでまとめると) • A==1.0.0、B==1.5.0、C==1.0.0 D==1.0.0、E==3.0.0  → requirements.txtやpoetry.lockが完成! Package B <=2.0.0 Package D ==1.0.0 Package B ==1.5.0 Package E <=3.0.0 Package C ==1.0.0 Conflict! Package E >=3.0.0 Package A >=1.0.0 Package C <=2.0.0
  9. Backtrackingによる依存解決 先程紹介した方法は(ほぼ)Backtrackingと呼ばれるアルゴリズム • 候補となるバージョンを決定 → 深さ優先探索 → バージョンがコンフリクトすればそれを解に → 再帰的に繰り返し • pipやpipenvが利用しているresolvelibではBacktrackingによる依存解決が行われている •

    課題:依存関係をバージョンごとに走査し直す必要があり 確認したいパッケージに対して指数関数的に時間が増加する  → 一定走査を諦めたりキャッシュを使うなど工夫されたBacktracking拡張ないし別アルゴリズムが存在  → 例えばpip 23.1でBacktrackingに導入されたBackjumpでは速度が大きく改善された Package A Package B Package C Package C Package C Package B Package A
  10. 依存地獄(Dependency Hell) • 素よりライブラリ依存は複雑なGraphである • 依存解決が可能か 対応する全てのバージョンにおける依存関係 はどのようになっているか 最終的にどのバージョンを選ぶと良いか •

    これらを計算するのが 『dependency resolver (solver)』 計算大変、例えば最近LLM周りで持て囃されているLangChain langchain = { version="*", extras=["all"] } → 288個のライブラリに依存   M2 Mac上のpipenvでは   425回PyPIにアクセスし、6522個の依存関係を確認   その依存解決に102秒かかる   1つのライブラリを入れたいだけなのに… ※これが普遍的に高速に解けるアルゴリズム思い付いた方DMでこっそり教えて下さい!
  11. lockファイルの生成 • 現代のPackage Managerの役割の1つ ◦ 依存解決で得られた各ライブラリの全てのバージョン、hash値が記されている ◦ 複数の環境で「同じパッケージ」をインストールするために必要 Package A

    >=1.0.0 Package B Package C ==1.5.0 requirements lock Package A ==1.0.0 Package B ==0.2.0 Package D ==3.2.1 requirements(dependency)ファイル だけではCやDのバージョンに差異が 生まれる可能性がある 手元の開発、他環境で”再現”するため に必須となっているファイル 一方「”同じ”とは何か」がPythonの 依存解決における一番大きな議論に なっている… lockファイルはPEPで標準化されていません でしたが、現代の開発においては非常に良く 使われる機能で標準化の議論も進んでいます
  12. [ここで質問] みなさんにとって “同じパッケージ” とはなんですか? • バージョンが同じ? • 中身のコードが同じ? • 依存先のパッケージのバージョンも同じ?

    • パッケージ内に含まれる全てのバイナリが同じ? • バイナリも外部の依存ツールも同じなら同じ? • バイナリも外部の依存ツールもOSもCPUも同じなら同じ?
  13. 依存解決で出てくるPythonならではの課題『再現性』 • packageの配布方法は大きく2種類 ◦ bdist(wheel): build済みのファイルが配布される ◦ sdist: install時にbuildされる ◦

    配布方法はパッケージの開発者が選択 • metadataを知る方法も異なる ◦ bdist ▪ PyPIではJSON APIに問い合わせると hash値や依存先の情報が返却される(※最新の適切なwheelの形式を取っていれば) ◦ sdist ▪ PC上でbuildしてみるまでhash値も依存先も”正確には”わからない       → 依存関係を知るために一部download, build必須問題       → バイナリを一意に識別するartifact hashが一部build必須問題      ※最適な.whlを選ぶ方法問題もあるが割愛
  14. 依存解決で出てくるPythonならではの課題『再現性』 • 「じゃあ全て適切なbdistにすれば良い?」 ◦ 過去のsdistはどうするのか ◦ 全てのプラットフォームでbuildするのは誰がやるのか ◦ スパコンみたいなエッジケースはどうするのか ◦

    PyPI以外のPackage Registryが絡む場合はどうするのか ◦ 監査上の理由から「バイナリのインストールはNG」という場合はどうするのか … • 再現性が依存解決に与える問題 ◦ 中身が同じである事を証明できないと人によってはlockファイルの価値がなくなる → 環境によって動作が違うということになる つまり完全に再現可能かを知るためには ”””依存解決中に一部packageをdownload/buildしてみる必要がある””” しかも必要性が想定される全てのパッケージ、バージョンで!!重い!! Pythonの利用ケースの多様性 30年以上の歴史の中で 順に生まれたsdist, egg, bdist, wheelの PEP規格という特性もある (例えばwheel PEP427採択は2012年)
  15. 進む再現性への対策 • 近年ではbdist(wheel)での公開が推奨/一般的になりつつある ◦ 例えばTensorflowのインストール ▪ かなり簡単になった、buildしたい場合はgit clone ◦ 先程のLangChainの依存パッケージだと10/288がsdist

    • sdistでもパッケージ作成者が適切に設定したmetadataに書かれた情報を信頼する方法 ◦ PyPIのAPIがartifact hash、依存情報を返す ◦ 完全な同一とは言えるのか議論があったが採択され実装が進む (PEP 658, 2023/5/10 Accept) • どこまでやるかだが、緩く「どうせ一緒でしょ」と捉える方法もありえる  → poetryなど3rd party package manager 個人的にはGitHub Actions等で CI/CDのテンプレート化が かなり進んだのも大きい PyPIダウンロード上位360のう ちsagemaker, future, pyspark を除いてすべてwheel配布 (2023/10/10) https://pythonwheels.com Package Manager視点では PyPI以外のPackage Registryや git等で考慮しないといけない点 まだあるが… これからは全てのwhlをPyPIに適 切に挙げてくれ… metadataを書いてくれ…
  16. つまり依存解決からパッケージ管理を見た時に言えること • パッケージ依存が解決出来ない場合はそれを知らせる必要がある • 効率的かつ高速な依存解決solver/resolverが搭載されている方が良い • lockファイル等で”パッケージの再現性”を担保する事が求められている • 再現性、パッケージが同じとは何か、について歴史と文化が伴う •

    再現性に関する歴史と文化によって依存解決も影響を受けている 2023年より立ち上がった python-packaging-strategy -discussionという公式 スレッドを”必ず”見てほしい Intelスポンサードの作成された pypackaging-nativeという ドキュメントに課題がまとまって いるので”必ず”見てほしい
  17. PubGrubによる依存解決 • Dart言語のパッケージマネージャーpubに導入された依存解決アルゴリズム (Dart>=2.0.0) ◦ Swift(spm, swift>=4.2.2)、Ruby(bundler>=2.4)、poetryのmixology ◦ 2012年に提案されたアプローチ ▪

    CDCLアルゴリズムをベースとしている • conflictした時の理由を覚えておき探索範囲を削る手法 • Go(dep)にはシンプルなCDCLが実装されている ▪ 覚えているconflictの理由を使って、別の競合の探索範囲も削る • Good ◦ 探索範囲が狭くなるのでdownload/buildしながら依存解決 しないといけない状況と相性が良い ◦ 「依存解決できない」理由も明確に示せる • Issue ◦ 探索範囲を大きく削っている分、出せない回答もある ※本スライド作成にあたり調査中「npmやcargoもpubgrubだよ」という記述を数回見たがコード見る限りはまだbacktracking、変更を進める議論はある(2023/10/08) ・WikipediaのConflict-driven clause learningのページと pubの公式READMEに視覚的に分かりやすい図がある ・OSC2021 OnlineFukuokaの村上涼さんのPubGrub解説が YouTubeに上がっている どちらもオススメ
  18. libmambaによる依存解決 • condaで使われる依存解決ライブラリ ◦ conda packageはsdist/bdistではない方法で配布 ▪ Anacondaリポジトリ(つまりPyPIミラー)内でhashを計算する ▪ artifact

    hash問題がほぼAnaconda任せにできる ◦ libsolvがベース ▪ 2007年からopenSUSEのコミュニティで開発されているbacktrackingリゾルバ ▪ 重み付き辞書によるソートにより高速化 • Good ◦ 再現性問題をAnacondaリポジトリが引き受け考える事を減らすことで 歴史ある安定して早いsolverのPythonバインディングを利用できている • Issue ◦ Hardlinkでの高速化も計っているのでpip等他ツールで環境が壊れやすい ◦ Anaconda一部営利企業で有償化、Package Registry運営は想像するだけで大変そう… mamba documentのpackage_resolutionのページ conda公式BlogのUnderstanding and Improving Conda’s performanceという記事にそれぞれ アルゴリズムの実行例があるのでオススメ 依存関係ビューア等も作ってい てすごい
  19. さらに深みに行く依存解決から見たパッケージ管理 • solverのアルゴリズム ◦ まだまだ沢山ある ◦ 「充足可能性問題」「Satisfiability Problem(SAT)」 ◦ 探索範囲削減による問題、セキュリティ課題、

    SATの並列化、統計的推論としての拡張…etc • ビルドキャッシュや外部のツール、ライブラリとの併走戦略 ◦ envやdockerとのやり取りはどうするのか ◦ bazel、pants等を用いた場合ビルドサーバの概念が入る • その他にもある余地 ◦ package registoryは本当に必要か? ◦ そもそも複数のバージョンが入って問題な時と問題でない時がないか? ▪ 例えばJavaScript(node)、goの依存に対する考え方は面白い ◦ 最近フロントエンド界隈で早いとされているBunではlockファイルをバイナリで扱う事 までしてパフォーマンスを上げていたりする ◦ multi-languageなパッケージ管理はどうか? ▪ conda packageの概念をうまく使ったpixi等がある
  20. 今日のまとめ • Pythonのパッケージ管理ライブラリは小さな実装、小さなPackageの集合体 ◦ easyでもone wayでもなく”simple” ◦ パッケージ管理に求められる要件年々増える中で工夫している • 特にPython特有の課題として依存解決が様々な所で障壁になっている

    ◦ sdist/bdist、artifact hash問題、「同じパッケージ」かどうか ◦ 様々な解消の方法があり継続して議論も進んでいる • 壁を超えた皆さんなら「俺の考えた最強のパッケージ管理ライブラリ」を作れる! こういった内容からPackage Managerを理解し 自作するなどしてみて実力をつけ PyPAの制作物や主要なPackage Managerへ コミットできる上級者になって頂く事を 期待しています
  21. PackagingCon 2023 in Berlin (10/26~28) PyPAやcondaの開発者、他言語やOSの Package Managerを作る巨人が集まる神イベ ント!YouTubeにArchivesもあるよ! donate.pypi.org

    PyCon 2023 Day2 (10/28) Packaging Toolの開発パターン別 プラクティス紹介 by Peacock Pythonのパッケージ管理 開発者にdonation! Packaging関連で参考にした URLや文献をまとめたgist 技術書典15(2023.11.12〜) エムスリーテックブック5 “自作PythonPackageManager入門” で書く予定です是非(なお進捗)