Python

Python namedtupleとは?名前付きタプルの使い方とdataclass・dictとの違いを実測で解説

Python namedtupleとは?名前付きタプルの使い方とdataclass・dictとの違いを実測で解説

namedtuple(名前付きタプル)は、標準ライブラリ collections が提供する「フィールド名でアクセスできるタプル」です。p[0] と書かずに p.x と書けるようになり、タプルのままなのでアンパックもインデックスも従来どおり使えます。ただし「軽くて速い」という紹介のされ方は正確ではありません。生成コストは辞書リテラルの約3.8倍かかり、JSONに渡すとフィールド名が消えます。この記事では定義方法と専用APIを整理したうえで、Python 3.14.6で実際に測ったメモリ・速度の差と、2026年10月1日リリース予定のPython 3.15で入る破壊的変更までをまとめます。

まとめ

  • namedtuple は tuple のサブクラス。インスタンス辞書を持たないため、辞書の約半分のメモリで済む。
  • 速さは選ぶ理由にならない。生成は辞書リテラルの約3.8倍かかり、属性アクセスも辞書・dataclassより遅い。
  • 選ぶ理由はタプル互換性(アンパック・インデックス・既存APIへの受け渡し)とイミュータブル性。
  • 新規コードで型注釈を使うなら typing.NamedTuple のクラス構文。作られるのは collections.namedtuple と同じタプルのサブクラスだが、3.14以降はメソッド内の super() が使えない。
  • Python 3.15 で NamedTuple("NT", x=int) のキーワード引数構文が削除される。3.13以降 DeprecationWarning が出ているので、3.15前に書き換える。
  • Python 3.15 では namedtuple のサブクラスに任意の __slots__ を定義できるようになる(3.14までは TypeError)。
  • フィールドが後から増える、値を書き換える、バリデーションが要る、のいずれかに当たるなら dataclass か Pydantic を選ぶ。

namedtupleの基本:フィールド名で読める不変タプル

namedtuple() はクラスそのものではなく、タプルのサブクラスを生成するファクトリ関数です。第1引数に型名、第2引数にフィールド名を渡すと新しい型が返ります。フィールド名は ["host", "port"] のようなリストでも、空白やカンマ区切りの1つの文字列でも構いません。

from collections import namedtuple

Server = namedtuple("Server", "host port role")
s = Server("10.0.1.20", 5432, "primary")

print(s.host, s[0], s.port)
# => 10.0.1.20 10.0.1.20 5432

print(s)
# => Server(host='10.0.1.20', port=5432, role='primary')

print(s._fields)
# => ('host', 'port', 'role')

同じ値に s.host でも s[0] でも届く点が、通常のタプルとの実務上の差です。print() するとフィールド名付きで表示されるため、ログやデバッグ時に「3番目の要素が何だったか」を思い出す作業が消えます。タプルなので当然イミュータブルで、s.host = "10.0.1.21" と書けば AttributeError: can't set attribute。値を変えたいときは後述の _replace() で新しいインスタンスを作ります。

collections.namedtupleの定義方法と専用API

defaults・renameを指定した定義

シグネチャは namedtuple(typename, field_names, *, rename=False, defaults=None, module=None) で、3つのオプションはすべてキーワード専用引数です。defaults(Python 3.7で追加)に渡した既定値は右詰めで適用され、_field_defaults から確認できます。rename=True(Python 3.1で追加)を付けると、予約語や重複した不正なフィールド名が _1 のような位置名に自動で置き換わります。CSVのヘッダーなど、外部から来る名前をそのまま型にしたいときに効きます。

Server = namedtuple("Server", ["host", "port", "role"], defaults=[5432, "replica"])

print(Server("10.0.1.21"))
# => Server(host='10.0.1.21', port=5432, role='replica')
print(Server._field_defaults)
# => {'port': 5432, 'role': 'replica'}

Row = namedtuple("Row", ["id", "class", "id"], rename=True)
print(Row._fields)
# => ('id', '_1', '_2')

フィールド名の制約は4つあります。有効な識別子であること、予約語でないこと、アンダースコア始まりでないこと、重複しないこと。実際によく踏むのは後ろの2つで、それぞれ ValueError: Type names and field names cannot be a keyword: 'class'、ValueError: Field names cannot start with an underscore: '_x' になります。アンダースコアが禁じられているのは、専用APIが _ 始まりの名前を使っているためです。いずれも型を定義した瞬間に落ちるので、実行時まで見逃すことはありません。ただし rename=True を付けた場合は例外を出さずに _0 のような名前へ黙って置き換わります。

_replace・_asdict・_make・_fieldsの使い分け

専用APIはアンダースコア始まりですが、非公開メソッドではありません。フィールド名との衝突を避けるための命名です。値の更新は _replace()、辞書化は _asdict()、既存のシーケンスからの生成は _make() が担当します(以下は前のコードブロックの続きです)。

import copy

print(s._replace(role="replica"))
# => Server(host='10.0.1.20', port=5432, role='replica')
print(s)
# => Server(host='10.0.1.20', port=5432, role='primary')  ← 元は変わらない

print(s._asdict())
# => {'host': '10.0.1.20', 'port': 5432, 'role': 'primary'}

print(Server._make(["10.0.1.22", 5432, "replica"]))
# => Server(host='10.0.1.22', port=5432, role='replica')

print(copy.replace(s, port=5433))
# => Server(host='10.0.1.20', port=5433, role='primary')

_replace() は元のインスタンスを書き換えず、新しいインスタンスを返します。存在しないフィールドを渡した場合、Python 3.13以降は TypeError: Got unexpected field names: ['z'](3.12までは ValueError なので、例外を捕まえている既存コードは要確認)。_asdict() の戻り値はPython 3.8以降ふつうの dict で、3.7までの OrderedDict ではありません。Python 3.13で追加された copy.replace() にも対応しているため、dataclassと同じ書き方で更新処理を共通化できます。各メソッドの仕様は公式ドキュメントの collections.namedtupleが一次情報です。

typing.NamedTupleとの違い:型注釈・ジェネリクス・継承制限

typing.NamedTuple は collections.namedtuple の型付き版で、作られるのは同じくタプルのサブクラスです。違いは書き方で、クラス構文とアノテーションでフィールドを宣言します。既定値はクラス本体で代入し、既定値のないフィールドを後ろに置くと TypeError: Non-default namedtuple field y cannot follow default field x。メソッドとdocstringを持てる(3.6.1以降)、ジェネリクスに対応する(3.11以降)という点が、関数呼び出し形式との実質的な差です。

from typing import NamedTuple

class Endpoint(NamedTuple):
    """APIエンドポイント定義."""
    url: str
    timeout: float = 3.0

    def with_timeout(self, sec: float) -> "Endpoint":
        return self._replace(timeout=sec)

e = Endpoint("https://api.example.com/v1/users")
print(e.with_timeout(10.0))
# => Endpoint(url='https://api.example.com/v1/users', timeout=10.0)

# 型パラメータ構文は Python 3.12以降。3.11 では
# class Page(NamedTuple, Generic[T]) と書く
class Page[T](NamedTuple):
    items: list[T]
    total: int

print(Page[int]([1, 2, 3], 3))
# => Page(items=[1, 2, 3], total=3)

制約はミックスインを併用できないことです。class T(NamedTuple, Mixin) と書くと TypeError: can only inherit from a NamedTuple type and Generic で弾かれます(Generic だけは例外的に許されるため、3.11での書き方が成立します)。共通処理を持たせたい場合は、継承ではなくメソッドをクラス本体に直接書くか、関数に切り出します。新規に書くコードでは typing.NamedTuple のクラス構文が素直です。mypyやPyrightがフィールドの型を見られますし、次章で説明する3.15の削除対象は typing.NamedTuple のキーワード引数形式だけで、クラス構文は影響を受けません(collections.namedtuple 自体にも廃止予定はありません)。

Python 3.14・3.15で変わるnamedtupleの仕様

Python 3.15はPEP 790のスケジュールで2026年5月7日のbeta 1(機能凍結)を通過済みで、2026年8月4日にrc 1、2026年10月1日に最終リリースが予定されています。namedtuple周辺には削除と緩和の両方が入るため、3.14系で動いているコードでも事前確認が要ります。Python 3.14の新機能まとめ|JITコンパイラ・フリースレッド版(GIL撤廃)・t-stringを最新版で解説と併せて移行計画に組み込んでください。

Python 3.14でのsuper()禁止

すでに影響が出ているのが3.14の変更です。typing.NamedTuple のサブクラスのメソッド内で super() を使うと TypeError: uses of super() and __class__ are unsupported in methods of NamedTuple subclasses になります。3.13以前で __repr__ をオーバーライドして super().__repr__() を呼んでいたコードは、3.14へ上げた時点で落ちます。回避策は親を明示することで、tuple.__repr__(self) のように書き換えます。collections.namedtuple 側のサブクラスでは従来どおり super() が動くため、両者は完全な等価ではありません。

Python 3.15でのキーワード引数構文の削除

Python 3.13から非推奨だった NamedTuple("NT", x=int) という書き方が、3.15で使えなくなります。CPython 3.15ブランチの Lib/typing.py では定義が def NamedTuple(typename, fields, /) に変わっており、3.14の def NamedTuple(typename, fields=_sentinel, /, **kwargs) にあったキーワード引数の受け口自体が消えています(gh-133817)。フィールドを省略する NamedTuple("NT") と NamedTuple("NT", None) も同時に不可になります。

from typing import NamedTuple

# 3.13〜3.14: DeprecationWarning が出る書き方
# NT = NamedTuple("NT", x=int)
# => Creating NamedTuple classes using keyword arguments is deprecated
#    and will be disallowed in Python 3.15.
#    Use the class-based or functional syntax instead.

# 移行先1: 関数形式(フィールドは (名前, 型) のリスト)
Point = NamedTuple("Point", [("x", int), ("y", int)])

# 移行先2: クラス構文(新規コードはこちら)
class Point2(NamedTuple):
    x: int
    y: int

この DeprecationWarning は __main__ で実行したときは既定で表示されますが、import 先のライブラリやモジュール内で呼んでいる場合は既定では出ません。python -W error::DeprecationWarning やテスト実行時の警告設定で洗い出すのが確実です。0フィールドの型が必要なら class NT(NamedTuple): pass か NamedTuple("NT", []) に書き換えます。

Python 3.15での__slots__制限の緩和

逆に緩和されるのがサブクラス側です。Python 3.14までは、namedtupleを継承したクラスに空でない __slots__ を付けると定義時点で失敗しました。

Point = namedtuple("Point", "x y")

class Cached(Point):
    __slots__ = ("_hypot",)
# Python 3.14.6 => TypeError: nonempty __slots__ not supported
#                  for subtype of 'Point'

そのため従来は __slots__ = () を付けてプロパティを足す使い方しかできず、計算結果をインスタンスにキャッシュしたい場合はdataclassへ乗り換える必要がありました。3.15ではtuple派生クラスに任意の __slots__ を定義できるようになります(gh-41779)。ただし3.14以前では同じコードが TypeError になるので、サポート範囲に3.14を含むライブラリでは使えません。

dict・dataclassとの実測比較(Python 3.14.6)

メモリ:200,000インスタンスあたりの実測値

「namedtupleは軽い」の根拠は、インスタンス辞書(__dict__)を持たないことです。macOS(Apple Silicon)のCPython 3.14.6で、2フィールドの型を200,000個生成し、tracemalloc のトレース増分をインスタンス数で割った実測値が次の表です(値オブジェクト1個分と、保持リストの参照1スロット分を含みます)。

データ構造 1インスタンスあたり dict比
dataclass(slots=True) 88 バイト 0.39
tuple 104 バイト 0.46
namedtuple 112 バイト 0.50
dataclass(既定) 128 バイト 0.57
dict 224 バイト 1.00

辞書に対しては半分で済みますが、最小なのは slots=True を付けたdataclassです。namedtupleのメモリ優位は「辞書と比べたとき」に限られ、スロット付きクラスには24バイト分負けます。なお sys.getsizeof() はタプルもnamedtupleも同じ64を返し、上表の8バイト差は200,000件をまとめて確保したときの実測差分として現れます。数百万件をメモリ上に並べる用途なら、行単位のオブジェクト化をやめて列指向の構造に寄せたほうが削減幅は大きくなります(Polarsとは?pandasとの違い・高速化の仕組みと実務での採用判断【実装目線】)。

速度:生成コストとアクセスコストの内訳

同じ環境で timeit(2,000,000回×9セットの最小値)を取った結果です。絶対値はマシンの負荷状況で1.5倍程度ぶれますが、比率は繰り返し測っても安定していました。

区分 操作 所要時間
生成 namedtuple(位置引数) 261 ns
生成 namedtuple(キーワード引数) 399 ns
生成 namedtuple(_make・タプル入力) 246 ns
生成 dataclass 103 ns
生成 dict リテラル 69 ns
生成 tuple リテラル 8 ns
アクセス namedtuple(.x) 26 ns
アクセス namedtuple([0]) 22 ns
アクセス dict([‘x’]) 18 ns
アクセス tuple([0]) 14 ns
アクセス dataclass(.x) 11 ns

生成が辞書リテラルの約3.8倍かかるのは、__new__ がPythonレベルの関数として定義されており、いったん中間タプルを作ってから tuple.__new__(cls, ...) を呼ぶ二段構えになっているためです。キーワード引数で書くと399nsまで伸び、_make() にタプルを渡した場合が246nsで最も軽くなりました(リストを渡した場合は264nsで、位置引数とほぼ同じです)。100万件をループで組み立てる処理なら、namedtupleは辞書より0.2秒ほど余分にかかる計算になります。読み取り側もdataclassの2倍以上で、速さを理由に選ぶ場面はありません。判断基準は単純です。生成回数が支配的なら辞書かdataclass、タプル互換性とイミュータブル性が要るならnamedtuple。

namedtupleを避けるべき場面

JSON直列化でのフィールド名消失

namedtupleはタプルなので、json.dumps() は配列として直列化します。フィールド名は出力に残りません。

import json

print(json.dumps(s))
# => ["10.0.1.20", 5432, "primary"]

print(json.dumps(s._asdict()))
# => {"host": "10.0.1.20", "port": 5432, "role": "primary"}

APIのレスポンスを組み立てる箇所でこれをやると、受け手には位置依存の配列が届きます。フィールドを1つ挿入しただけでクライアント側が壊れるため、外部へ出す直前で _asdict() を挟むか、そもそもレスポンスモデルにnamedtupleを使わない方針にします。

型をまたいだ等価比較の落とし穴

比較はタプルの比較なので、型は考慮されません。フィールド名も無関係です。

from collections import namedtuple

Point = namedtuple("Point", "x y")
Size = namedtuple("Size", "w h")

print(Point(1, 2) == Size(1, 2))
# => True
print(Point(1, 2) == (1, 2))
# => True

cache = {Point(1, 2): "point"}
print(cache.get((1, 2)))
# => point

座標とサイズのように「同じ2要素だが意味が違う」型を並行して扱うコードでは、取り違えをテストで検出できません。辞書のキーに使った場合も、素のタプルと衝突します。型で区別したいなら、既定でクラスが違えば等しくならないdataclassに置き換えるのが確実です。

可変・検証が必要なデータでの代替(dataclass・Pydantic)

namedtupleが向くのは、フィールドが固定で、値を書き換えず、位置とキーの両方でアクセスしたいデータです。この条件から外れるなら無理に使いません。フィールドを1つ足すたびに位置引数で生成している箇所を全部確認する羽目になりますし、要素が増えるほど Server(a, b, c, d, e) の可読性は落ちます。入力値の検証や型変換が必要なら Pydanticとは?Pythonデータ検証ライブラリの使い方と基本【V2対応】で扱うモデル、単に可変の構造体が欲しいだけなら @dataclass(slots=True) が、メモリ・速度の両面でも上回ります。

よくある質問

namedtupleと通常のタプルの違いは何ですか?

フィールド名でアクセスできる点と、repr に名前が出る点です。Server(host='10.0.1.20', port=5432) のように表示されるため、ログから内容を読み解く手間が減ります。メモリは実測で112バイト対104バイト(2フィールド・200,000件の平均)とほぼ同じで、インデックスアクセスもアンパックもタプルと同じように使えます。

namedtupleの値を変更するにはどうすればよいですか?

イミュータブルなので直接の代入はできません(AttributeError: can't set attribute)。s._replace(role="replica") で、指定フィールドだけを差し替えた新しいインスタンスを作ります。Python 3.13以降は copy.replace(s, port=5433) でも同じ結果です。存在しないフィールド名を渡すと、3.13以降は TypeError になります。

typing.NamedTupleとcollections.namedtupleはどちらを使うべきですか?

新規コードでは typing.NamedTuple のクラス構文です。フィールドの型が静的解析ツールに伝わり、ジェネリクス(3.11以降)やメソッド定義にも対応します。collections.namedtuple は、フィールド名を実行時に組み立てる場合や、メソッド内で super() を使っている既存コードとの整合を取る場合に残ります。なお NamedTuple("NT", x=int) のキーワード引数形式はPython 3.15で削除されるため、その書き方だけは使わないでください。

namedtupleを辞書に変換するには?

s._asdict() がフィールド名をキーにした dict を返します。Python 3.8以降は OrderedDict ではなくふつうの dict です。逆方向は Server(**d) で、辞書のキーがフィールド名と一致していればインスタンスを生成できます。

namedtupleとTypedDictはどちらを使うべきですか?

実体を辞書のままにしたいなら TypedDict、不変オブジェクトにしたいなら NamedTuple です。TypedDict は既存の辞書に型情報だけを付ける仕組みで、実行時の値はふつうの dict(JSONとの往復や既存の辞書ベースAPIに手を入れずに済む)。NamedTuple はタプルのサブクラスという実体が作られ、書き換えを禁止できます。

namedtupleとdataclassはどちらが速いですか?

Python 3.14.6の実測では、生成はdataclassが103ns、namedtupleが261nsでdataclassの勝ちです。属性アクセスもdataclassが11nsに対してnamedtupleは26ns、メモリも slots=True を付けたdataclassが88バイトでnamedtupleの112バイトを下回ります。namedtupleを選ぶ理由は速度ではなく、タプルとして扱える互換性(アンパック、インデックス、既存のタプル前提APIへの受け渡し)と、イミュータブルであることです。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.07 テックブログ ファインズの不正アクセスと153万件の予約者情報|予約システムを使う店舗の報告と通知の手順
  2. 2026.10.19 テックブログ コミックシーモア不正アクセスで会員情報流出:再開時期・パスワード再設定と運営側のログ点検
  3. 2026.10.16 テックブログ バーガーキング情報漏洩どうしたらいい?約1,118万件の対象確認・補償・退会とメールの真偽
  4. 2026.10.15 テックブログ 銀のさら不正アクセス:最大650万件の対象確認と窓口、パスワード変更・退会の手順
  5. 2026.10.14 テックブログ ブックオフ不正アクセス:約638万人の対象確認と本物メールの見分け方

RELATED POSTS 関連記事

目次