閉じる

Pythonのcollections.namedtuple入門 読みやすいタプルの作り方

タプルは軽量で便利ですが、要素が増えるほど「0番目は何だっけ?」と読みづらくなりがちです。

そこで役立つのがcollections.namedtupleです。

フィールドに名前を付けることでコードが自己説明的になり、デバッグや保守が楽になります。

本記事では、Python初心者の方向けに、定義から活用のコツまでていねいに解説します。

collections.namedtupleとは

読みやすいタプルにするメリット

タプルは位置(インデックス)で要素を参照するため、コードを読む人が文脈を知らないと意味が伝わりにくくなります。

名前付きの属性で参照できるようにすると、何を扱っているのかが一目で分かるため、バグの予防やレビュー効率の向上につながります。

また、namedtupleは通常のタプルと同等に軽量で、メモリ効率や生成コストの面でも有利です。

以下は緯度経度を普通のタプルとnamedtupleで比較した例です。

右側の方が圧倒的に読みやすく、保守しやすいことが分かります。

Python
# 位置(インデックス)で扱うふつうのタプル
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の概要

namedtuplecollectionsモジュールが提供するファクトリ関数で、属性名でアクセスできる不変(イミュータブル)なレコード型を生成します。

生成された型はタプルのサブクラスで、インデックスアクセスやアンパック(a, b = obj)もそのまま使えます。

用途としては、関数の返り値で複数の値を返すときの可読性向上や、軽量なデータ転送オブジェクトなどに向いています。

collections.namedtupleの使い方と定義

名前とフィールドを定義する

基本形はnamedtuple(型名, フィールド名の並び)です。

フィールド名はリストやスペース区切りの文字列で指定できます。

まずは最小例から見てみましょう。

Python
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のような安全な名前に置き換えてくれます。

Python
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です。

属性アクセスはドット記法で行います。

Python
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

アンパックとインデックスアクセス

タプルの良さはそのまま使えます。

アンパックやインデックスでのアクセスも可能です。

Python
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__を調整します。

Python
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()で包みます。

Python
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()で新しいインスタンスを得ます。

Python
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で取得できます。

動的処理やデバッグに便利です。

Python
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を使う

複数の値を返す関数では返り値を自己記述的にできます。

戻り値の構造を型で明示することで、利用側の読みやすさが大幅に向上します。

Python
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に属性を渡すだけです。

Python
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)]

attrgetterlambdaより見通しが良く高速なことが多く、属性に基づくソートでよく使われます。

初心者向けの注意点とコツ

イミュータブルなので直接は変更不可

namedtupleは不変なので属性の再代入はできません。

更新したい場合は_replace()で新しいインスタンスを作ります。

Python
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座標ならxyで十分です。

逆に業務ドメインで意味が曖昧になる場合は、誤読を避けるために多少長くても明確な語(例: birth_date)を選びます。

dictやクラスとの使い分けの目安

何を軸に選ぶかを表にまとめます。

迷ったら「値オブジェクトで変更しないならnamedtuple、柔軟に変更するならdictかクラス」を目安にしましょう。

選択肢可変性宣言コスト可読性
(属性名)
メモリ効率典型ユースケース
namedtuple不変高(ドット記法)軽量な値オブジェクト、関数返り値
dict可変なし中(キー文字列)柔軟なスキーマ、動的な追加変更
クラス(手書き)可変中〜高振る舞い(メソッド)を多く持つ
dataclasses.dataclass可変/不変選択可型注釈・デフォルト・比較などが豊富

不変の小さなレコードにはnamedtuple可変で進化するデータにはdictdataclassが向きます。

まとめ

collections.namedtupleは「タプルの軽さ」と「クラスの読みやすさ」を両立する道具です。

名前付き属性でコードが自己説明的になり、関数の返り値やデータの受け渡しが明確になります。

ポイントは次のとおりです。

デフォルト値はdefaults=で末尾から設定し、変更は_replace()で新インスタンスを生成します。

_asdict()はログやシリアライズに便利で、_fieldsは動的処理に役立ちます。

小さな不変レコードを扱う場面でまず検討し、読みやすく安全なPythonコードを書いていきましょう。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!