Python 3: Deep Dive (Part 1 - Functional): タプル (セクション8-1/11)
タプルは不変でデータレコードの位置に意味を持たせるデータ構造として有効であり、リストや文字列と異なる特性を持ちます。
タプルはアンパックや拡張アンパックを活用することで効率的にデータを抽出・操作でき、軽量なデータ構造を提供します。
実践例ではタプルを使ったデータ集計やπの近似計算を紹介し、その表現力と柔軟性を示します。
Pythonでのタプルについて考えると、一般的にはただの不変(immutable)なリスト、つまり作成後に要素を変更できないコレクションとして捉えられがちです。確かにこれは技術的に正しいのですが、タプルの持つ可能性をほんの一部しか示していません。この投稿では、タプルの深い理解を目指して、そのデータ構造としての強力な使い方や、「読み取り専用リスト」以上のタプルの本当の姿に迫ります。
タプルの紹介
タプルは、Pythonの基本的なデータ型の1つで、要素のシーケンスを格納できる構造です。リストとは異なり、タプルは不変(immutable)であり、一度作成するとその要素を変更することはできません。この不変性は単なる制約ではなく、タプルをデータレコードとして使用する際に独自の利点をもたらします。
文字列を例に考えてみましょう。文字列は、順序と内容が重要な不変の文字のシーケンスです。同様に、タプルも不変のシーケンスであり、各位置に意味を持たせることができます。
タプル vs リスト vs 文字列
まず、タプルとリスト、文字列を比較して、それぞれの類似点と相違点を理解しましょう。
共通の特徴
コンテナ: 3つともコレクションの要素を格納するためのコンテナ型です。
順序の維持: 要素が格納される順序は保持され、重要です。
インデックスアクセス: インデックスを使って要素にアクセスできます。
イテラブル: 各要素をループ処理できます。
主要な違い
$$
\begin{array}{|c|c|c|c|} \hline
特徴 & タプル & リスト & 文字列 \\ \hline
変更可能性 & 不変 & 可変 & 不変 \\ \hline
異種データの保持 & 可能 & 可能(通常は不可)& 不可(同種のみ) \\ \hline
固定長 & はい & いいえ & はい \\ \hline
順序の固定 & はい & いいえ(順序変更可)& はい \\ \hline
インプレース修正 & できない & できる & できない \\ \hline
\end{array}
$$
タプルの不変性は、その長さと順序が常に一定であることを保証します。この特性は、各要素の位置が特定の意味を持つデータレコードを表現するのに最適です。
不変性とデータレコード
タプルは一度作成されると変更することができないため、データレコードとしての信頼性の高い構造を提供します。タプル内の各要素の位置に特定の意味を持たせることができ、これはデータベースのレコードのフィールドに似ています。
例:幾何学的な形状の表現
点 (Point): `(x, y)` ここで `x` は最初の要素、`y` は2番目の要素を示します。
円 (Circle): `(x_center, y_center, radius)` 各位置は特定の属性に対応します。
例:都市データの表現
london = ('London', 'UK', 8_780_000)
new_york = ('New York', 'USA', 8_500_000)
beijing = ('Beijing', 'China', 21_000_000)
これらのタプルでは:
最初の要素は都市名
2番目の要素は国
3番目の要素は人口
このようにタプルを使用することで、カスタムクラスのオーバーヘッドなしにシンプルで効率的なデータ構造を作成できます。
タプルからデータを抽出する
タプルはシーケンスであるため、インデックス、スライス、アンパックを使ってデータを抽出できます。
インデックスアクセス
city_name = london[0] # 'London'
country = london[1] # 'UK'
population = london[2] # 8780000
アンパック(分解)
タプルの各要素を一度に変数に割り当てることができます:
city, country, population = london
ダミー変数の使用
必要な要素のみを取得する場合、不要な値の位置にアンダースコア `_` を使います:
city, _, population = beijing
拡張アンパック
要素数が多いタプルの場合、`*` 演算子を使うことができます:
record = ('DJIA', 2018, 1, 19, 25987.35, 26071.72, 25942.83, 26071.72)
symbol, year, month, day, *_, close = record
この例では、`*_` が中間の不要な値をすべて集めます。
データ構造としてのタプルを使ったコーディング
ここでは、タプルを実際のコードでデータ構造として活用する方法を探っていきます。
タプルの作成と使用
タプルは括弧なしでもコンマで区切って作成できます:
point = 10, 20
ただし、関数への引数としてタプルを渡す場合、混乱を避けるために括弧が必要です:
def process_point(pt):
x, y = pt
# Do something with x and y
process_point((10, 20))
可変要素を含むタプル
タプル自体は不変でも、リストやカスタムクラスのインスタンスのような可変オブジェクトを含むことができます:
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
pt = Point2D(0, 0)
a = (pt,)
# a[0]を再割り当てすることはできませんが、ptを変更することは可能です
a[0].x = 10
ループとアンパック
タプルはループ内で直接アンパックできます:
cities = [london, new_york, beijing]
for city_name, country, population in cities:
print(f"{city_name} in {country} has a population of {population}.")
実践例:総人口の計算
total_population = sum(city[2] for city in cities)
print(f"Total population: {total_population}")
実践例:π(パイ)の近似
関数から複数の値をタプルで返す例:
from random import uniform
from math import sqrt
def random_point(radius):
x = uniform(-radius, radius)
y = uniform(-radius, radius)
is_inside = sqrt(x**2 + y**2) <= radius
return x, y, is_inside
num_attempts = 1_000_000
count_inside = 0
for _ in range(num_attempts):
_, _, is_inside = random_point(1)
if is_inside:
count_inside += 1
pi_approx = 4 * count_inside / num_attempts
print(f"Pi is approximately: {pi_approx}")
この例では、`random_point` から複数の値を返す際にタプルを利用し、必要な要素のみをアンパックしています。
結論
Pythonのタプルは、単なる不変リスト以上の存在であり、シンプルで効率的かつ信頼性の高いデータ構造を作成するための強力なツールです。不変性とシーケンス特性を活用することで、カスタムクラスのオーバーヘッドなしに複雑なデータレコードを表現することができます。タプルからデータを抽出して操作する方法を理解することで、よりクリーンで保守しやすいコードを書くことができるようになります。
次回のシリーズでは、タプルのシンプルさと位置に名前を付ける機能を組み合わせた名前付きタプルについて探っていきます。これにより、Pythonコードの表現力をさらに高めることができます。
ぜひ、コメントでご意見や質問をお寄せください。次回は名前付きタプルに関する投稿をお楽しみに!