collections

【Python】namedtupleで名前付きタプルを使用する(collections.namedtuple)

【Python】namedtupleで名前付きタプルを使用する(collections.namedtuple)
naoki-hn

名前付きでタプルにアクセスできる collections モジュールの namedtuple について解説します。

namedtuplecollectionsモジュール)

Python の代表的な型であるタプル (tuple) では、アクセスする場合に[]で位置を指定してアクセスしたり、アンパック代入で変数に代入して使ったりすることができます。

しかし要素数が多くなってくると、どの位置に何の情報が入っているのかが分かりにくくなってしまうため、各値に名前付きでアクセスできると便利な場合があります。

このような場合には、名前付きタプルである namedtuple が使用できます。namedtuple は、collections モジュールに用意されています。

本記事では、collections モジュールの namedtuple について定義方法や使い方、namedtuple の使いどころや特徴といった点について説明します。

namedtuple の定義と使い方

namedtuple の定義と使い方を、以下の簡単な例で定義や使い方について見ていきましょう。

import collections

# named tupleの定義
Point = collections.namedtuple("Point", ["x", "y"])

# インスタンス化
p1 = Point(10, 20)
p2 = Point(x=30, y=40)
print(f"p1 = {p1}")
print(f"p2 = {p2}")

# フィールド名で値へアクセス
print(f"p1.x = {p1.x}, p1.y = {p1.y}")
print(f"p2.x = {p2.x}, p1.y = {p2.y}")

# アンパック代入
x1, y1 = p1
x2, y2 = p2
print(f"x1 = {x1}, y1 = {y1}")
print(f"x2 = {x2}, y2 = {y2}")
【実行結果】
p1 = Point(x=10, y=20)
p2 = Point(x=30, y=40)
p1.x = 10, p1.y = 20
p2.x = 30, p1.y = 40
x1 = 10, y1 = 20
x2 = 30, y2 = 40

上記のサンプルプログラムについてポイントを説明します。

import collections

まず、namedtuple は以下のように collections モジュールのインポートが必要です。

# named tupleの定義
Point = collections.namedtuple("Point", ["x", "y"])

namedtuple の定義では、タイプの名前とフィールドの名前といったものを定義します。上記の例では、"Point" という名前のタイプを定義し、フィールドとして"x", "y"という名前のフィールドをリストで指定して定義しています。

# インスタンス化
p1 = Point(10, 20)
p2 = Point(x=30, y=40)
print(f"p1 = {p1}")
print(f"p2 = {p2}")

実際に定義した namedtuple をインスタンス化するためには上記のように記載します。"x", "y" に該当する値を順に引数に指定してもよいですし、x=, y= のように設定したフィールド名のキーワード引数として指定することも可能です。

# フィールド名で値へアクセス
print(f"p1.x = {p1.x}, p1.y = {p1.y}")
print(f"p2.x = {p2.x}, p1.y = {p2.y}")

値にアクセスする場合には、インスタンス化した変数に対して「.」(ドット)でアクセスすることができます。

# アンパック代入
x1, y1 = p1
x2, y2 = p2
print(f"x1 = {x1}, y1 = {y1}")
print(f"x2 = {x2}, y2 = {y2}")

また、namedtuple は名前付きのタプルですので通常のタプルと同様にアンパック代入によって値を取り出してデータを扱うことも可能です。

以上が、namedtuple の基本的な使い方になります。以降では、namedtuple で使用できる便利な各種メソッドについて紹介します。

namedtuple の各種メソッド

namedtuple では便利な各種メソッドが用意されています。namedtuple のメソッド名は、公式ドキュメントに記載がありますが、フィールド名との衝突を避けるために「_(アンダースコア)」で始まる名前となっています。

一般的には _(アンダースコア) がついた変数やメソッドは、慣例としてクラス内のみで参照・使用される変数やメソッドというプライベートの意味があります(ただし、外部からのアクセスは可能です)が、namedtuple は少し異なっていますね。

_make で新しい namedtuple を作成する

既存リストから namedtuple を作成する場合には、以下のように _make メソッドを使用します。

import collections

Point = collections.namedtuple("Point", ["x", "y"])

data = [10, 20]
p_data = Point._make(data)
print(f"p_data = {p_data}")
print(p_data.x, p_data.y)
【実行結果】
p_data = Point(x=10, y=20)
10 20

Point という定義を作る点は基本的な使い方で紹介した通りですが、上記例では data というリストで定義されている値をもとにして、namedtuple のインスタンス p_data を作成しています。

_asdictnamedtuple を辞書に変換する

作成した namedtuple を辞書に変換して使用したい場合は、以下のように _asdict メソッドを使用します。

import collections

Point = collections.namedtuple("Point", ["x", "y"])

p1 = Point(10, 20)
p_dict = p1._asdict()

print(type(p_dict))
print(p_dict)
print(p_dict["x"])
print(p_dict["y"])
【実行結果】
<class 'dict'>
{'x': 10, 'y': 20}
10
20

_asdict() の返却値を type で確認していますが、dict となっていることが分かるかと思います。このように dict へ変換して扱うことが可能です。

_asdict() の返却値は、バージョン 3.1 で dict の代わりに OrderedDict を返すようになりましたが、その後バージョン 3.8 から dict に戻っています。

これはバージョン 3.7 で、通常の dict が順序付けられることが保証されたためです。OrderedDict の追加機能が必要な場合は、OrderDictOrderedDict(x._asdict()) といった形でキャストするように勧められています。

この内容は公式ドキュメントのこちらに記載があります。

_replace で一部の値を変更した新しい namedtuple を作成する

作成した namedtuple をもとに一部の値を変更した新しい namedtuple のインスタンスを作成する場合には、以下のように _replace メソッドを使用します。

import collections

Point = collections.namedtuple("Point", ["x", "y"])

p1 = Point(10, 20)

# p1のxの値を置き換える
print(p1._replace(x=50))
# p1の値が書き換わるわけではないので注意
print(p1, "\n")

# 値を書き換えた場合は別のインスタンスに代入する
p2 = p1._replace(x=50)
print(f"p1 = {p1}")
print(f"p2 = {p2}")
【実行結果】
Point(x=50, y=20)
Point(x=10, y=20)

p1 = Point(x=10, y=20)
p2 = Point(x=50, y=20)

タプルはイミュータブルで変更不可なものなので、_replace メソッドは既存の namedtuple そのものの値を置き換えるわけではなく、値を置き換えた namedtuple のインスタンスの変数を返却することに注意してください。

そのため、使用する場合には別の変数に代入して使用する必要があります。

_fields によりフィールド名を取得する

namedtuple のフィールド名を取得するには、以下のように _fields メソッドを使用します。

import collections

Point = collections.namedtuple("Point", ["x", "y"])

p1 = Point(10, 20)
print(p1._fields)
【実行結果】
('x', 'y')

上記のように返却値は、フィールド名が列挙されたタプルで返却されます。

namedtuple の使いどころ

CSV ファイルの読み込みに使用する

namedtuple の使いどころとしてよく紹介される CSV ファイル値取得での利用について例を使って紹介します。

import collections
import csv

# CSVファイルを作成して保存する
with open("temp.csv", "w", newline="", encoding="UTF-8") as csv_write:
    fields = ["index", "value1", "value2", "value3"]
    writer = csv.DictWriter(csv_write, fieldnames=fields)

    writer.writeheader()
    for i in range(1, 6):
        writer.writerow(
            {
                "index": i,
                "value1": f"val1-{i}",
                "value2": f"val2-{i}",
                "value3": f"val3-{i}",
            }
        )

# CSVファイルをnamedtupleに読み込む
with open("temp.csv", "r", encoding="UTF-8") as csv_read:
    csv_reader = csv.reader(csv_read)

    # namedtupleを定義する(CSVの1行目を列名として定義)
    record = collections.namedtuple("record", next(csv_reader))
    # CSVのレコードを順次読み込む (namedtupleで1行を読み込む)
    data = [record._make(row) for row in csv_reader]

print(data)
# 1行目のデータに列名でアクセスする
print(data[0].index)
print(data[0].value1)
print(data[0].value2)
print(data[0].value3)
【実行結果】
[record(index='1', value1='val1-1', value2='val2-1', value3='val3-1'), record(index='2', value1='val1-2', value2='val2-2', value3='val3-2'), record(index='3', value1='val1-3', value2='val2-3', value3='val3-3'), record(index='4', value1='val1-4', value2='val2-4', value3='val3-4'), record(index='5', value1='val1-5', value2='val2-5', value3='val3-5')]
1
val1-1
val2-1
val3-1

CSV ファイルを読み込む際には、CSV ファイルの列名を namedtuple で定義しておき、データを読み込む際に namedtuple に設定することで、その後データに列名で参照することができます。

この例では、まずサンプルの CSV を作成して temp.csv で書き込み、作ったデータを namedtuple に読み込んでいます。

next(csv_reader) の部分で、CSV のヘッダーの列名情報を取得できるため、その値を使って "record" という namedtuple を定義しています。

その後データを読み込む際には、リスト内包表記を使って _make メソッドで namedtuple にしつつ data というリストを作成しています。

データへのアクセスは、列名である「index」「value1」「value2」「value3」を用いて直接それぞれにアクセスできていることが分かると思います。このようにしておくことで読み込んだデータ列を指定してデータを扱うことが容易になります。

CSV入出力やリスト内包表記については以下も参考にしてください。

関数の戻り値で使用する

関数の戻り値として複数の戻り値を返却する場合は、返却値が編集されてしまわないようにイミュータブルなタプルに返却値に設定するのが基本です。その際に、どれが何の値なのかを明示するために namedtuple を使うと便利です。

以下の例の calculate_stat 関数は、入力された数値リストの最小値、最大値、平均、分散、標準偏差を計算して返却する関数です。

※ 本来このままだど data に数値以外が入ってくるとエラーになるため問題がありますが、namedtuple の返却例ということで細かなチェック等を割愛しています。

import collections
import statistics


def calculate_stat(data):
    min_v = min(data)
    max_v = max(data)
    mean_v = statistics.mean(data)
    var_v = statistics.variance(data)
    std_v = statistics.stdev(data)

    # 返却値をnamedtupleで定義
    Stat = collections.namedtuple("Stat", ["min", "max", "mean", "var", "std"])
    stats = Stat(min_v, max_v, mean_v, var_v, std_v)
    return stats


def main():
    values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
    result = calculate_stat(values)
    print(
        f"min:{result.min}, "
        f"max:{result.max}, "
        f"mean:{result.mean}, "
        f"var:{result.var}, "
        f"std:{result.std}"
    )


if __name__ == "__main__":
    main()
【実行結果】
min:1, max:15, mean:8, var:20, std:4.47213595499958

上記例では返却値を "Stat" というタイプの namedtuple にまとめて設定して返却しています。

通常のタプルで返却すると、何番目に最小値が入ってくるかといったことを覚えていないといけませんが、namedtuple を使用することで、呼び出した側は最小値なら min、最大値なら max といったように直感的に返却値を扱うことができます。

このように関数の戻り値として namedtuple を使用することがあります。

namedtuple の特徴

namedtuple の定義や使い方について見てきました。ここでは namedtuple の特徴について紹介します。

namedtuple と辞書(dict)の違い

これまでの使用例で、namedtuple がPython標準のタプル(tuple)よりも理解しやすく、扱いやすいということはご理解いただけたかと思います。しかし、同じことはPython標準の辞書(dict)でも可能です。

namedtuple と辞書(dict)で異なる点は以下のような点です。

  • namedtuple はタプルの一種のため、変更不可(イミュータブル)であり、内部の長さは必要サイズに固定されている。一方で、辞書(dict)は必要な領域を余分に確保しているためメモリ使用効率は namedtuple の方が良い。
  • namedtuple は、添え字でもアクセス可能でこの操作は高速に実行できる。辞書(dict)は内部構造としてハッシュテーブルの探索が必要となり最悪のケースでは遅くなる可能性がる。

パフォーマンスが要求されるようなコードの部分では添え字のインデックスを使ってアクセスすることで応答性を確保し、その他の場面では属性名でアクセスできる利便性を享受するといったことができる点が namedtuple の特徴です。

まとめ

名前付きでタプルにアクセスできる collections モジュールの namedtuple について解説しました。

名前付きタプルは、タプルの各要素に名前でアクセスできるため、関数の戻り値で使用したり、CSVの読み込みに使用すると便利です。各種メソッドも用意されており、本記事で代表的なメソッドを紹介しています。

namedtupleは、辞書でキーにアクセスすることと似ていますが、namedtuple はタプルの一種のためイミュータブルで内部のメモリ使用効率が良くなります。また、namedtuple は添え字によるアクセスも可能でこの操作はアクセスが高速なため状況によって使い分けるといったこともできます。

名前付きでアクセスできる namedtuple は便利な面が多いため、是非状況によって使用を検討してもらえればと思います。

namedtuple の公式ドキュメントはこちらを参照してください。

ソースコード

上記で紹介しているソースコードについては GitHub にて公開しています。参考にしていただければと思います。

あわせて読みたい
【Python Tech】プログラミングガイド
【Python Tech】プログラミングガイド
ABOUT ME
ホッシー
ホッシー
システムエンジニア
はじめまして。当サイトをご覧いただきありがとうございます。 私は製造業のメーカーで、DX推進や業務システムの設計・開発・導入を担当しているシステムエンジニアです。これまでに転職も経験しており、以前は大手電機メーカーでシステム開発に携わっていました。

プログラミング言語はこれまでC、C++、JAVA等を扱ってきましたが、最近では特に機械学習等の分析でも注目されているPythonについてとても興味をもって取り組んでいます。これまでの経験をもとに、Pythonに興味を持つ方のお役に立てるような情報を発信していきたいと思います。どうぞよろしくお願いいたします。

※キャラクターデザイン:ゼイルン様
記事URLをコピーしました