nikkie-ftnextの日記

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

いったいいつから異種コレクションとしての辞書が僕の中で自然になってしまったのだろうか

はじめに

じっしょ じっしょ じー1、nikkieです。

ロバストPython』きっかけで考えたことをアウトプットです。
私はこれまで、同じ型の要素を入れた辞書も、違う型の要素を入れた辞書もどちらもよく使ってきたのですが、後者にはより向く方法があると知ったんですよね。
違う型の要素を入れた辞書って使いづらさもありつつ、「なんでこれまで自然に使ってきたんだろう」と気になったので、二人の出会いから遡って語っていきたいと思います。
今回は技術ポエムな感じです!

目次

前提:異種コレクションとしての辞書とは

「異種コレクション」は、『ロバストPython』で知った概念です。

コレクションとは、任意の数のデータを格納する型のことで、例えば辞書やリストです。
要素のデータ型

  • 揃っている:同種コレクション
  • 揃っていない異種コレクション

と分けられます。

ロバストPython』(の5章)ではコレクションへの型ヒントの書き方が指南されます。
異種コレクションになった辞書は型ヒントが煩雑になっていきます2
詳細は過去の読書ログに譲り、このエントリは辞書を異種コレクションとしても使うという視点について掘り下げます。

思い返せばはじめて出会ったあなたは同種コレクションでしたね

私はPyNyumonという入門者向けハンズオンでPythonを始めたのですが、そこで導入された辞書は同種コレクションでした。

pynyumon/1_python_basics.md at 9d3a9cfc33b78043ea79bd00d4507eb1abea7402 · pynyumon/pynyumon · GitHub

>>> fruits = {
...     'apple': 100,
...     'orange': 50,
... }

型ヒントを書くならdict[str, int]
果物の価格(整数)を要素とした辞書(同種コレクション)です。

同種コレクションの辞書は入門者向けテキストにしか出てこないわけではありません。
別の例を出すと、私が比較的経験のある自然言語処理でも、単語をIDに変換するのに同種コレクションの辞書が使われます。

https://github.com/oreilly-japan/deep-learning-from-scratch-2/blob/484c6e0b45435b1240d3c51f20b6e8357e096fdd/common/util.py#L8-L23

word_to_id: dict[str, int]

なので、同種コレクションの辞書はいたはずなんです。
なのになぜ、異種コレクションの辞書も私の中で自然になってしまったのか...

いつから異種コレクションの辞書が自然になってしまったのでしょう

我が身を振り返ってみると、きっかけはJSON形式と思われます。
何らかの設定を書いたJSONファイルだったり、Web APIの返り値(JSON形式)だったりです。

{
    "name": "awesome_name",
    "hidden_size": 768,
    "dropout_prob": 0.1
}

異種コレクションの例として、自然言語処理のモデルのconfigファイルの一部を持ってきました3
型ヒントを書くとdict[str, Union[str, int, float]](うーん)
異種コレクションなので、Unionを使った煩雑な型ヒントになっています。

異種コレクションの辞書は、ちょっとしたデータを表す上で、私が書くコードに頻繁に登場するようになりました。
そうすると型ヒントにUnionを使うことになるのですが、書くのを大変に感じていた私にとって、型エイリアスを使った読みやすい型ヒントは発見でした。

(『ロバストPython』まで読んだ私はTypedDictがしっくり来ています)

異種コレクションとして想定されているのは、タプル

FAQ なぜタプルとリストという別のデータ型が用意されているのですか? によると4、タプルは

型が異なっても良い関連するデータの小さな集合

です。
つまりタプルは異種コレクションとしてデザインされているということだと思います。

ですが、異種コレクションとしてのタプル、インデックスがマジックナンバーでちょっと扱いづらいですよね。
t[0]はname、t[1]はhidden_size、と対応を覚えておくのは大変です。

ここで、整数インデックスから文字列インデックスへ発想の転換は十分に想像できます。
そう、異種コレクションとしての辞書の登場です。

これは私の意見ですが、異種コレクションとしての辞書は簡単な方法だと思います。
他の選択肢としては

  • typing.NamedTuple
  • @dataclasses.dataclass(frozen=True)

などがありえます。
ですが、これらを使うためにはある程度知識が要りますし、異種コレクションの辞書と比べたら書くコードも増えます。

異種コレクションという概念を知り、TypedDictという解決策を知った私の気持ちとしては、「異種コレクションとしての辞書はシステム外部とのやり取りに限りたい」となります。
根拠ですが、『ロバストPython』では異種コレクションであれば選択肢はクラス5かデータクラス(未読)です。

IMO:Pythonは組み込みのコレクション型が"簡単"に使えすぎるのではないか

Pythonは非常に私の手に馴染む言語(とっても大好き)です。
(愛を持って書いていきますが、)異種コレクションとしての辞書について考える中で、「簡単に使えすぎかもなあ」という引っかかりもあります。

  • 異種コレクションに辞書を使ったときに、めちゃめちゃ困るということがない
    • 多様なユースケースをサポートするこの設計はかなりすごいと思う
    • デメリットとして、より適切な技術(データクラスやクラス、ほかにも名前付きタプルなど)を使う機会に気づきにくい
  • 他の例としてはリスト
    • リストよりもジェネレータが広範に適すると理解している6が、リストを使ったプログラムを多く見かける

ここで「簡単」と言っているのは、他の言語で知ったeasy(簡単)とsimpleの話7を念頭に置いています。
Pythonの辞書やリストはeasyで、適所でなくてもそこそこ使えてしまいますが、より適切な技術を使ったほうがsimpleにできるのではないかと私は考えています。

終わりに

Pythonの辞書との馴れ初めをポエミーに語りました。
同種コレクションとして出会っていたのに、異種コレクションとしての辞書、全然普通になっちゃってたんですよ!

このエントリは私の技術への立ち位置も盛り込まれていたかなと思います。

  • easyよりsimpleを追求したい
  • 技術にはそれぞれ使いどころがあり、カチッとハマるとsimpleになると考えている
    • 新しく知って使いこなすために頭を使うが、simpleにできるならそんなに苦とは感じない

私は異種コレクションとしての辞書の使用は最小限にしていきますが、「Python使いはみんなそうすべき」と主張する気はありません。
大事なのは技術を使って価値が提供できていることであり、異種コレクションとしての辞書が頻出であっても最小限であっても価値が出せているのであれば、過程における好みの違いにすぎず、統一する必要はないと思うのです。

というわけで、それぞれの持ち場で happy hacking!


  1. 元ネタは、せーの! にっこ にっこ にー
  2. TypedDictを使った解決策は必見ですよ
  3. BERTの事前訓練をColabで動かしてみました(『Transformerによる自然言語処理』3章写経) - nikkie-ftnextの日記サンプルコードより
  4. ファーストクラスコレクションのPythonでの実装例を見て考えたこと - nikkie-ftnextの日記 でも取り上げました
  5. 読書ログ | 『ロバストPython』10章「クラス」、不変式を維持せよ!と声高に叫ぶ章(議論する前の理解のまとめ) - nikkie-ftnextの日記
  6. リストはメモリにすべて展開するが、ジェネレータは1要素ずつ処理なので、メモリ使用量の観点からジェネレータに軍配が上がるという理解です
  7. Clojureと「Simple Made Easy」 - 紙箱