タプルは軽量で便利ですが、要素が増えるほど「0番目は何だっけ?」と読みづらくなりがちです。
そこで役立つのがcollections.namedtuple
です。
フィールドに名前を付けることでコードが自己説明的になり、デバッグや保守が楽になります。
本記事では、Python初心者の方向けに、定義から活用のコツまでていねいに解説します。
collections.namedtupleとは
読みやすいタプルにするメリット
タプルは位置(インデックス)で要素を参照するため、コードを読む人が文脈を知らないと意味が伝わりにくくなります。
名前付きの属性で参照できるようにすると、何を扱っているのかが一目で分かるため、バグの予防やレビュー効率の向上につながります。
また、namedtupleは通常のタプルと同等に軽量で、メモリ効率や生成コストの面でも有利です。
以下は緯度経度を普通のタプルとnamedtuple
で比較した例です。
右側の方が圧倒的に読みやすく、保守しやすいことが分かります。
# 位置(インデックス)で扱うふつうのタプル
tokyo_tuple = (35.681236, 139.767125) # (lat, lon) のつもり
print("lat:", tokyo_tuple[0], "lon:", tokyo_tuple[1])
# 名前で扱える namedtuple
from collections import namedtuple
Location = namedtuple("Location", ["lat", "lon"])
tokyo = Location(35.681236, 139.767125)
print("lat:", tokyo.lat, "lon:", tokyo.lon)
lat: 35.681236 lon: 139.767125
lat: 35.681236 lon: 139.767125
collections.namedtupleの概要
namedtuple
はcollections
モジュールが提供するファクトリ関数で、属性名でアクセスできる不変(イミュータブル)なレコード型を生成します。
生成された型はタプルのサブクラスで、インデックスアクセスやアンパック(a, b = obj
)もそのまま使えます。
用途としては、関数の返り値で複数の値を返すときの可読性向上や、軽量なデータ転送オブジェクトなどに向いています。
collections.namedtupleの使い方と定義
名前とフィールドを定義する
基本形はnamedtuple(型名, フィールド名の並び)
です。
フィールド名はリストやスペース区切りの文字列で指定できます。
まずは最小例から見てみましょう。
from collections import namedtuple
# 文字列のスペース区切りでフィールドを定義
Person = namedtuple("Person", "name age")
# リストでフィールドを定義してもOK
# Person = namedtuple("Person", ["name", "age"])
alice = Person("Alice", 30)
print(alice) # 表示は自己記述的
print(alice.name) # 属性でアクセス
print(alice.age)
Person(name='Alice', age=30)
Alice
30
フィールド名のルール
フィールド名にはPythonの識別子として有効な名前を使います。
つまり、数字で始めてはいけませんし、予約語(class
など)は使えません。
重複も不可です。
もし入力データに不適切な名前が混ざる可能性があるなら、rename=True
を付けると無効な名前を自動で_0のような安全な名前に置き換えてくれます。
from collections import namedtuple
# 予約語(class)や数字ではじまる(1st)などを含むが、rename=Trueで安全にリネーム
Weird = namedtuple("Weird", "name class 1st", rename=True)
print(Weird._fields) # 置き換え後のフィールド名を確認
w = Weird("Bob", "IGNORED", "IGNORED")
print(w) # 自動的に _1, _2 のような名前になる
('name', '_1', '_2')
Weird(name='Bob', _1='IGNORED', _2='IGNORED')
実務では、短く一貫した英単語(例: id
, name
, score
)を用いると読みやすさが上がります。
インスタンス生成と属性アクセス
インスタンスは普通のクラスと同じ感覚で生成します。
位置引数でもキーワード引数でもOKです。
属性アクセスはドット記法で行います。
from collections import namedtuple
User = namedtuple("User", "id name is_admin")
u1 = User(1, "Alice", False) # 位置引数
u2 = User(id=2, name="Bob", is_admin=True) # キーワード引数
print(u1.id, u1.name, u1.is_admin)
print(u2.id, u2.name, u2.is_admin)
1 Alice False
2 Bob True
アンパックとインデックスアクセス
タプルの良さはそのまま使えます。
アンパックやインデックスでのアクセスも可能です。
from collections import namedtuple
Point = namedtuple("Point", "x y")
p = Point(10, 20)
# アンパック
x, y = p
print(x, y)
# 一部だけ使いたい場合(アンダースコアで捨てる慣習)
x_only, _ = p
print(x_only)
# インデックスアクセスも可能
print(p[0], p[1])
10 20
10
10 20
デフォルト値の設定
Python 3.7以降はdefaults=
引数で末尾のフィールドにデフォルトを与えられます。
より古いバージョンでは__new__.__defaults__
を調整します。
from collections import namedtuple
# Python 3.7+ の推奨方法: 右端から順にデフォルトが適用される
Point3D = namedtuple("Point3D", "x y z", defaults=(0,)) # z=0 をデフォルトに
a = Point3D(1, 2) # z は省略してOK
b = Point3D(1, 2, 5) # z に値を入れることも可能
print(a)
print(b)
# 参考: 旧来のやり方(Python 3.6以前向け互換)
Legacy = namedtuple("Legacy", "a b c")
Legacy.__new__.__defaults__ = (0, 0, 1) # 右端から a,b,c に対応してデフォルト適用
l1 = Legacy(9) # a=9, b=0, c=1
print(l1)
Point3D(x=1, y=2, z=0)
Point3D(x=1, y=2, z=5)
Legacy(a=9, b=0, c=1)
どのフィールドにデフォルトが入るかは右から適用される点に注意してください。
省略可能な値は基本的に末尾に置くのがコツです。
便利メソッドと活用例
_asdictで辞書へ変換
_asdict()
はフィールド名→値のマッピングを得るのに便利です。
表示やJSON化に使います。
表示を素の辞書にしたいときはdict()
で包みます。
from collections import namedtuple
User = namedtuple("User", "id name is_admin")
u = User(1, "Alice", False)
# dict風に扱えるマッピングへ変換
mapping = u._asdict()
print(mapping["name"]) # 名前で取り出し
print(dict(mapping)) # 表示用にふつうの dict へ
Alice
{'id': 1, 'name': 'Alice', 'is_admin': False}
データをログに出す・シリアライズするといった場面で特に役立ちます。
_replaceで値を差し替え
namedtuple
はイミュータブルなので、既存インスタンスを直接書き換えられません。
代わりに_replace()
で新しいインスタンスを得ます。
from collections import namedtuple
Account = namedtuple("Account", "id name plan")
acc = Account(10, "Alice", "free")
# plan をアップグレード(新しいインスタンスが返る)
pro = acc._replace(plan="pro")
print(acc) # 元はそのまま
print(pro) # 変更後
Account(id=10, name='Alice', plan='free')
Account(id=10, name='Alice', plan='pro')
部分的な更新を安全に行える点が、バグを防ぐのに効きます。
_fieldsでフィールド一覧を見る
フィールド名のタプルは_fields
で取得できます。
動的処理やデバッグに便利です。
from collections import namedtuple
Person = namedtuple("Person", "name age")
print(Person._fields)
# 一覧を使って表示
p = Person("Alice", 30)
for fname in Person._fields:
print(f"{fname} = {getattr(p, fname)}")
('name', 'age')
name = Alice
age = 30
関数の返り値にcollections.namedtupleを使う
複数の値を返す関数では返り値を自己記述的にできます。
戻り値の構造を型で明示することで、利用側の読みやすさが大幅に向上します。
from collections import namedtuple
Stats = namedtuple("Stats", "count mean minimum maximum")
def summarize(nums):
"""与えられた数値列の簡単な統計量を返す"""
n = len(nums)
m = sum(nums) / n if n else 0.0
return Stats(
count=n,
mean=m,
minimum=min(nums) if n else None,
maximum=max(nums) if n else None,
)
data = [10, 20, 30, 40]
result = summarize(data)
# 属性名があるので分かりやすい
print(result)
print("mean =", result.mean)
Stats(count=4, mean=25.0, minimum=10, maximum=40)
mean = 25.0
forループ・ソートでの使い方
リスト内のnamedtuple
は、属性名で読みやすく処理できます。
ソートもkey
に属性を渡すだけです。
from collections import namedtuple
from operator import attrgetter
Student = namedtuple("Student", "name score")
students = [
Student("Alice", 82),
Student("Bob", 91),
Student("Carol", 78),
]
# forループで読みやすく出力
for s in students:
print(f"{s.name}: {s.score}")
# スコアの降順にソート
ranked = sorted(students, key=attrgetter("score"), reverse=True)
print([s.name for s in ranked])
# 複合キー(スコア降順→名前昇順)
ranked2 = sorted(students, key=lambda s: (-s.score, s.name))
print([(s.name, s.score) for s in ranked2])
Alice: 82
Bob: 91
Carol: 78
['Bob', 'Alice', 'Carol']
[('Bob', 91), ('Alice', 82), ('Carol', 78)]
attrgetterはlambda
より見通しが良く高速なことが多く、属性に基づくソートでよく使われます。
初心者向けの注意点とコツ
イミュータブルなので直接は変更不可
namedtupleは不変なので属性の再代入はできません。
更新したい場合は_replace()
で新しいインスタンスを作ります。
from collections import namedtuple
User = namedtuple("User", "id name")
u = User(1, "Alice")
try:
u.name = "Bob" # 直接代入は不可
except AttributeError as e:
print("エラー:", e)
# 正しいやり方
u2 = u._replace(name="Bob")
print(u2)
エラー: can't set attribute
User(id=1, name='Bob')
「更新は新しいインスタンスで」というスタイルは、状態の予期せぬ変更を防ぎます。
フィールド名は短く分かりやすく
長すぎる名前は読みづらく、打鍵も増えます。
文脈で明らかな情報は省略し、一貫した命名を心がけましょう。
例えば2D座標ならx
とy
で十分です。
逆に業務ドメインで意味が曖昧になる場合は、誤読を避けるために多少長くても明確な語(例: birth_date
)を選びます。
dictやクラスとの使い分けの目安
何を軸に選ぶかを表にまとめます。
迷ったら「値オブジェクトで変更しないならnamedtuple
、柔軟に変更するならdict
かクラス」を目安にしましょう。
選択肢 | 可変性 | 宣言コスト | 可読性 (属性名) | メモリ効率 | 典型ユースケース |
---|---|---|---|---|---|
namedtuple | 不変 | 低 | 高(ドット記法) | 良 | 軽量な値オブジェクト、関数返り値 |
dict | 可変 | なし | 中(キー文字列) | 並 | 柔軟なスキーマ、動的な追加変更 |
クラス(手書き) | 可変 | 中〜高 | 高 | 並 | 振る舞い(メソッド)を多く持つ |
dataclasses.dataclass | 可変/不変選択可 | 中 | 高 | 並 | 型注釈・デフォルト・比較などが豊富 |
不変の小さなレコードにはnamedtuple
、可変で進化するデータにはdict
やdataclass
が向きます。
まとめ
collections.namedtupleは「タプルの軽さ」と「クラスの読みやすさ」を両立する道具です。
名前付き属性でコードが自己説明的になり、関数の返り値やデータの受け渡しが明確になります。
ポイントは次のとおりです。
デフォルト値はdefaults=
で末尾から設定し、変更は_replace()
で新インスタンスを生成します。
_asdict()はログやシリアライズに便利で、_fieldsは動的処理に役立ちます。
小さな不変レコードを扱う場面でまず検討し、読みやすく安全なPythonコードを書いていきましょう。