---
title: "Python namedtupleとは？名前付きタプルの使い方とdataclass・dictとの違いを実測で解説"
url: "https://www.issoh.co.jp/tech/details/3370/"
published: 2024-08-22
updated: 2026-07-29
categories: ["Python"]
publisher: "株式会社一創"
---

# 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](https://docs.python.org/3/library/collections.html#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](https://docs.python.org/3/library/typing.html#typing.NamedTuple) のクラス構文が素直です。mypyやPyrightがフィールドの型を見られますし、次章で説明する3.15の削除対象は `typing.NamedTuple` のキーワード引数形式だけで、クラス構文は影響を受けません（`collections.namedtuple` 自体にも廃止予定はありません）。

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

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

### 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との違い・高速化の仕組みと実務での採用判断【実装目線】](/tech/details/13618/)）。

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

同じ環境で `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対応】](/tech/details/4838/)で扱うモデル、単に可変の構造体が欲しいだけなら `@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への受け渡し）と、イミュータブルであることです。

## 関連記事

- [Python 3.14の新機能まとめ｜JITコンパイラ・フリースレッド版(GIL撤廃)・t-stringを最新版で解説](/tech/details/9409/)
- [Pydanticとは？Pythonデータ検証ライブラリの使い方と基本【V2対応】](/tech/details/4838/)
- [スタックとキューの違い｜FIFO・LIFOと使い分け・Python実装を図解](/tech/details/4587/)
- [Polarsとは？pandasとの違い・高速化の仕組みと実務での採用判断【実装目線】](/tech/details/13618/)

---

出典: [Python namedtupleとは？名前付きタプルの使い方とdataclass・dictとの違いを実測で解説](<https://www.issoh.co.jp/tech/details/3370/>)（株式会社一創）
