【ITニュース解説】Comprehension in Python (5)
2025年09月28日に「Dev.to」が公開したITニュース「Comprehension in Python (5)」について初心者にもわかりやすく解説しています。
ITニュース概要
Pythonの内包表記は、リスト、タプル、ジェネレータ、セット、辞書などのデータ構造を効率的に生成する機能だ。特に辞書の内包表記は、1次元から3次元の複雑な構造を簡潔なコードで作成できる。forループを使うよりも短く、読みやすいコードで同じ処理が実現できるため、開発効率が向上する。
ITニュース解説
Pythonのプログラミングにおいて、コードをより簡潔に、そして読みやすくするための強力な機能の一つに「内包表記(Comprehension)」がある。これは、既存のデータから新しいデータ構造(リスト、セット、辞書など)を効率的に作成するための記法である。特に、キーと値のペアでデータを管理する「辞書(Dictionary)」を扱う際に用いられる「辞書内包表記」は、システム開発の現場で頻繁に利用され、複雑なデータ変換処理をシンプルに記述できるため、その理解はシステムエンジニアを目指す上で非常に重要だ。
辞書内包表記は、従来のforループを使って辞書を生成する処理を、一行で記述できる点が最大の特徴である。例えば、sampleという数値のリスト[0, 1, 2, 3, 4, 5, 6, 7]がある場合、それぞれの数値をキーとし、その数の2乗を値とする辞書を作成したいと考える。通常のforループでこれを行うには、まず空の辞書v = {}を用意し、その後for x in sample:のループ内でv.update({x:x**2})のように、キーと値のペアを一つずつ追加していく必要がある。しかし、辞書内包表記を使えば、v = {x:x**2 for x in sample}と記述するだけで、同じ辞書{0: 0, 1: 1, 2: 4, 3: 9, ...}を生成できる。これは、sampleリストの各要素xに対して、xをキー、xの2乗(x**2)を値とするペアを生成し、それらをまとめて一つの辞書として構築するという意味だ。この簡潔さは、コードの記述量を減らし、意図を明確にする上で非常に有効である。
辞書を作成する際に、リストの要素だけでなく、その要素がリストの中で何番目にあるかを示す「インデックス」を利用したい場合もよくある。Pythonにはenumerateという便利な関数があり、リストなどの反復可能なオブジェクトの要素を、そのインデックスと値のペアとして同時に取り出すことができる。このenumerate関数を辞書内包表記と組み合わせることで、インデックスをキーとして使用する辞書も容易に作成できる。例えば、前述のsampleリストに対し、インデックスをキー、各要素の2乗を値とする辞書を作成したい場合は、v = {i:x**2 for i, x in enumerate(sample)}と記述する。ここでiはインデックス、xは要素を指し、結果として{0: 0, 1: 1, 2: 4, 3: 9, ...}という辞書が生成される。従来のforループで同じことをしようとすると、for i, x in enumerate(sample): v.update({i:x**2})のように書く必要があり、やはり内包表記の方が一行で完結するため、より直感的で読みやすい。
さらに、辞書は値として別の辞書を持つことも可能であり、これを「入れ子になった辞書」や「多次元辞書」と呼ぶ。複雑なデータを構造化する際に、このような多次元辞書は非常に有用だ。辞書内包表記は、このような多次元辞書を作成する際にもその真価を発揮する。複数のforループを組み合わせる代わりに、辞書内包表記を入れ子にすることで、コードを劇的に簡潔にできる。
例えば、sampleがタプルのタプル((0, 1, 2, 3), (4, 5, 6, 7))のような2次元のデータ構造である場合を考える。このデータから、外側のタプルをキーとし、そのタプルの各要素をキー、その要素の2乗を値とする内側の辞書を持つ2次元辞書を作成したいとする。この場合、v = {x: {y:y**2 for y in x} for x in sample}と記述する。外側の内包表記{... for x in sample}がsample内の各タプルxを取り出し、それを外側の辞書のキーとする。そして、その値として、内側の内包表記{y:y**2 for y in x}で作成された辞書が割り当てられる。これにより、{(0, 1, 2, 3): {0: 0, 1: 1, ...}, (4, 5, 6, 7): {4: 16, 5: 25, ...}}のような2次元辞書が生成される。
ここでもenumerate関数は強力なツールとなる。もし外側の要素のインデックスをキーに、内側の要素のインデックスを内側の辞書のキーとしたい場合は、v = {i: {j:y**2 for j, y in enumerate(x)} for i, x in enumerate(sample)}のように、それぞれの階層でenumerateを適用すればよい。これにより、{0: {0: 0, 1: 1, ...}, 1: {0: 16, 1: 25, ...}}のような辞書が生成される。従来のforループを二重に記述し、それぞれでupdateメソッドを呼び出す煩雑なコードと比較すると、内包表記の簡潔さが際立つだろう。
さらに深い階層、例えば3次元辞書を作成する場合でも、この原則は変わらない。sampleが(((0, 1), (2, 3)), ((4, 5), (6, 7)))のようなタプルのタプルのタプルである場合、v = {x: {y: {z:z**2 for z in y} for y in x} for x in sample}と三重に内包表記を重ねることで、目的の3次元辞書を効率的に生成できる。各階層でenumerateを使用するパターンも同様に適用可能で、v = {i: {j: {k:z**2 for k, z in enumerate(y)} for j, y in enumerate(x)} for i, x in enumerate(sample)}と記述することで、インデックスベースの複雑な多次元辞書を簡潔に作成できる。これにより、従来の複数のforループと辞書操作を組み合わせたコードと比較して、記述量が大幅に削減され、かつデータ構造の生成ロジックが一目で理解しやすくなる。
最後に、辞書のキーとして使用できるデータ型には重要な制約があることを理解しておく必要がある。辞書のキーは「ハッシュ可能(hashable)」なオブジェクトでなければならない。これは、その値が不変(immutable)であることを意味する。具体的には、数値(整数、浮動小数点数)、文字列、そしてタプルは不変であるため、辞書のキーとして使用できる。しかし、リスト、セット、そして別の辞書といったデータ型は、要素を追加したり変更したりできる「可変(mutable)」なオブジェクトであるため、辞書のキーとして使用できない。これは、キーの値が途中で変更されてしまうと、辞書が内部的に要素を管理するためのハッシュ値が変わり、正しい値を見つけられなくなるためだ。この原則は、辞書を扱う上で非常に基本的な知識であり、覚えておく必要がある。
内包表記は、辞書だけでなく、リスト(リスト内包表記)、セット(セット内包表記)、そしてジェネレーター(ジェネレーター内包表記)といった他のデータ構造でも利用できるPythonの強力な機能群である。これらの内包表記を習得することで、Pythonを使ったプログラミングがより効率的で、読みやすく、そしてパワフルになるだろう。