namedtupleで美しいpythonを書く！（翻訳）

namedtupleの解説記事です。

# この記事の想定読者
- namedtupleの基本を知りたい人
- namedtupleの存在意義がよく分からない人
- namedtupleの活用場面を知りたい人
- そこそこ多量のデータを上手に管理する方法を探している人

【2019年10月4日追記】
結構な数のいいねをいただいたため、きちんとした記事にしなければと改めて思っております。
見直しますと、`typing.NamedTuple`の利用方法など、重要な情報が不足していると思いましたので（執筆当時の知識不足によります。申し訳ありません）、本日追記して記述を補いました。

# はじめに

あまりメジャーではないかもしれませんが、`python`には`namedtuple`というデータ型があります。標準ライブラリの`collections`モジュールからインポートできます。

私自身は`namedtuple`に関しては、

- dictionaryのようにキーバリュー的に値を格納できる
- ドットアクセスできる。あの面倒な`dic["key"] `を書かないでいい！！
- 基本的にtupleである。なのでイミュータブルである
- tupleなので軽量だったり早かったりするのではないか？

というくらいの認識でした。

私はついついパフォーマンスが気になるし、イミュータブルと言われると安心を感じるほうなので、`namedtuple`はなんだか気になる存在でした。ただ、ネットで検索してみてもあまりすっきりした解説に出会うことはなく、自己流でもやもやしていました。

そんなある日、`namedtuple`はとても「安全」なんじゃないかと思い、**"namedtuple safe"**というキーワードでググってみました。そこで以下の記事を発見しました。

Namedtupleでクリーンなpythonを書く
[Writing Clean Python With Namedtuples](https://dbader.org/blog/writing-clean-python-with-namedtuples)

読んでみたところ、`namedtuple`の入門に必要な内容がきちんと書かれており、更に長所や使いどころも整理されており、私的にはスッキリな記事でした。
`namedtuple`に関しては、日本語の解説記事でほどよいものを見た記憶がなかったので、この際、記事を和訳して投稿してみようと思い立ちました。

なお、翻訳については著者の了解を得ています。
それではお楽しみください。


# namedtupleでクリーンな`python`を書く（翻訳）

`python`には `namedtuple`という特別なコンテナがありますが、その力が十分に評価されていないようです。`python`が持つ素晴らしい機能なのに、みんなに見過ごされているものの１つです。

クラスを自分で定義するかわりに`namedtuple`を使うことが非常に有効です。そして`namedtuple`には他にも面白い特徴があります。この記事でそれを紹介したいと思います。


さて、`namedtuple`とはなんでしょうか。また、何が特別なのでしょうか。
よい方法があります。組み込みのデータ型である`tuple`の拡張だと考えてみるのです。

`python`の`tuple`はシンプルなデータ型で、任意のオブジェクトをグループにします。また、`tuple`はイミュータブルです。一旦作成されると変更できません。

```python

>>> tup = ('hello', object(), 42)
>>> tup
('hello', <object object at 0x105e76b70>, 42)
>>> tup[2]
42
>>> tup[2] = 23
TypeError: "'tuple' object does not support item assignment"
```

プレーンな`tuple`には弱点があります。格納したデータに、整数のインデックスでしかアクセスできないことです。`tuple`に格納された個々のプロパティに名前を付けることはできません。これはコードの可読性を損ないます。

また、`tuple`の構造は毎回変わります。２つの`tuple`が、同じ数のフィールドと、同じプロパティを持っている保証はありません。フィールドの順番をうっかり取り違えてバグを起こしがちです。

## namedtupleが解決！

`namedtuple`の狙いは、この２つの問題を解決することです。
何よりも、`namedtuple`は通常のtupleと同じようにイミュータブルです。一旦何かを格納すると、もう変更することはできません。

それと同時に`namedtuple`は・・・、そう、namedな`tuple`なのです。
`namedtuple`に格納されたオブジェクトには、ユニークな（人間に読みやすい）名前でアクセスすることができます。数値のインデックスを覚える必要はありません。インデックスで使うために数値の入った定数を定義するような、その場しのぎも必要ありません。

`namedtuple`はこんな風に使います。

```python
>>> from collections import namedtuple
>>> Car = namedtuple('Car' , 'color mileage')
```

`namedtuple`を使うためには、`collections`モジュールをインポートします。Python2.6で標準ライブラリに追加されました。上の例では、Carというシンプルなデータ型を定義しました。`Car`には`color`と`mileage`の２つのフィールドがあります。

書き方が少し妙に見えるかもしれません。なぜフィールドを"color mileage"という文字列で渡すのでしょう。
それは`namedtuple`のファクトリ関数が、この文字列に対して、`split()`を呼び出すからです。つまり、以下の書き方の略記になっています。

```python
>>> 'color mileage'.split()
['color', 'mileage']
>>> Car = namedtuple('Car', ['color', 'mileage'])
```

もちろん、そのほうが好みなら、リストでそのまま渡すこともできます。このやり方のメリットは、コードを複数行に分けるときに簡単に書き直せることです。

```python
>>> Car = namedtuple('Car', [
...     'color',
...     'mileage',
... ])
```

いずれにしても、これで`Car`ファクトリ関数によって、新しい`car`オブジェクトを作ることができるようになりました。これは、`Car`クラスを自分で定義し、`color`と`mileage`の値を受け付けるコンストラクタを作ったのと同じ振る舞いになります。

（【2019年10月4日追記：訳注】python3.6.1より、`typing.NamedTuple`を使う読みやすい記法が実装されています（後述します）。）


```python
>>> my_car = Car('red', 3812.4)
>>> my_car.color
'red'
>>> my_car.mileage
3812.4
```

アンパック代入や、可変長引数（*）も利用できます。

```python
>>> color, mileage = my_car
>>> print(color, mileage)
red 3812.4
>>> print(*my_car)
red 3812.4
```

`namedtuple`の値には名前でアクセスできる上に、インデックスでも変わらずアクセスできます。このため、`namedtuple`はプレーンな`tuple`をいつでも置き換えることができます(drop-in replacement)。

```python
>>> my_car[0]
'red'
>>> tuple(my_car)
('red', 3812.4)
```

特別な設定なく、いい具合の文字表示まで使えるようになります。タイプ量も冗長さもおさえられます。

```python
>>> my_car
Car(color='red' , mileage=3812.4)
```

プレーンな`tuple`と同様に、`namedtuple`はイミュータブルです。フィールドを上書きしようとすると、`AttributeError`になります。

```python
>>> my_car.color = 'blue'
AttributeError: "can't set attribute"
```

`namedtuple`オブジェクトは、内部的に、pythonの通常のクラスとして実装されます。メモリ効率に関しては、通常のクラスよりも良好であり、プレーンな`tuple`と同水準です。

こう考えるとよいでしょう。`namedtuple`はメモリ効率がよいイミュータブルなクラスです。そして、クラス定義のショートカットになります。



## namedtupleをサブクラス化する
`namedtuple`は通常のクラスをベースにしているため、`namedtuple`のクラスにメソッドを追加することもできます。たとえば、他のクラスと同様に継承することができますし、メソッドやプロパティも同様に追加できます。こんな具合です。

```python
>>> Car = namedtuple('Car', 'color mileage')
>>> class MyCarWithMethods(Car):
...     def hexcolor(self):
...         if self.color == 'red':
...            return '#ff0000'
...         else:
...             return '#000000'
```

`MyCarWithMethods`クラスを作り、`hexcolor()`メソッドを呼び出しました。期待通りに動作します。

```python
>>> c = MyCarWithMethods('red', 1234)
>>> c.hexcolor()
'#ff0000'
```

少し簡単すぎたかもしれませんね。イミュータブルなプロパティのあるクラスが欲しいときに、こうするとよいかもしれません。でもずっこけたことをやってしまうものです。
たとえば、新たにイミュータブルなフィールドを追加するのは、`namedtuple`の実装からすると少し難しいです。`namedtuple`のクラス階層を作る一番簡単な方法は、基底となる`tuple`の `._fields`プロパティを使うことです。

```python
>>> Car = namedtuple('Car', 'color mileage')
>>> ElectricCar = namedtuple(
...     'ElectricCar', Car._fields + ('charge',))
```

これで期待通りになります。

```python
>>> ElectricCar('red', 1234, 45.0)
ElectricCar(color='red', mileage=1234, charge=45.0)
```

## 組み込みのヘルパーメソッド
`_fields`プロパティのほかにも、`namedtuple`のインスタンスにはいくつかのヘルパーメソッドがあります。便利に使えるかも知れません。

ヘルパーメソッドの名前は全てアンダースコアから始まります。
アンダースコアは普通はそのメソッドやプロパティがプライベートであることを意味し、クラスやモジュールのパブリックインターフェースを意味しません。
`namedtuple`でアンダースコアが使われるのは別の意味があります。これらのヘルパーメソッドやプロパティは、`namedtuple`のパブリックインターフェースになっています。ユーザーが定義するフィールド名との衝突を避けるために、アンダースコアがついています。

`namedtuple`のヘルパーメソッドが便利に活用できるシナリオをいくつか紹介します。最初は`_asdict()`です。`namedtuple`の中身を`dict`で返します。

```python
>>> my_car._asdict()
OrderedDict([('color', 'red'), ('mileage', 3812.4)])
```
`json`を出力するときにタイポしないですみますね。たとえばこんな具合です。

```python
>>> json.dumps(my_car._asdict())
'{"color": "red", "mileage": 3812.4}'
```

次の便利なヘルパーは`_replace()`関数です。`tuple`のシャローコピーを返し、選んだフィールドを置き換えます。

```python
>>> my_car._replace(color='blue')
Car(color='blue', mileage=3812.4)
```

最後に、`_make()`クラスメソッドはシーケンスやイテレーターから、新しいインスタンスを作るために使えます。

```python
>>> Car._make(['red', 999])
Car(color='red', mileage=999)
```

## どんなときにnamedtupleを使うか

`namedtuple`は、コードをきれいに、読みやすくする簡単な方法になります。データにきちんとした型を与えられるからです。

たとえば、定型フォーマットにしたディクショナリ（そのたびに型が変わるものです）を、`namedtuple`に変えることで、自分の意図がよりはっきり表現できます。このリファクタリングをやってみると、目下の問題に素晴らしい解決策をたちまち考えつくことがしばしばです。

構造の無いタプルやディクショナリでなく`namedtuple`を使うことで、同僚たちの仕事も楽になります。データがある程度セルフドキュメンティングな形で渡されるからです。

一方、よりクリーンに、読みやすく、メンテナンスしやすいコードにするために役立たないなら、`namedtuple`は使わないようにしています。過ぎたるは及ばざるがごとしです。
しかし意識して使えば、`namedtuple`はまちがなく、あなたのpythonコードをより良く、そして、表現力豊かなものにしてくれます。

## 覚えておこう！
- `collection.namedtuple`は自分でイミュータブルなクラスを定義するショートカットになり、しかも、メモリ効率がよい。
- `namedtuple`はデータに理解しやすい型を与えることで、コードをクリーンにしてくれる。
- `namedtuple`には便利なヘルパーメソッドがある。アンダースコアから始まるが、パブリックインターフェースだ。これを活用するのもよい。


# 投稿者より
私自身は`namedtuple`であれこれ試している段階です。
これまではちょっとしたデータの格納にはいつも`dict`を使っていたのですが、それを積極的に`namedtuple`に置き換えるようになりました。


## データコンテナとして
よく考えてみると、型（キー）が固定しているデータって結構よくあるのですね。固定していることが**期待される**というべきでしょうか。
そういうデータは、キーが本質的に不定な`dict`よりも、`namedtuple`であるほうが自然だと思います。

それと、イミュータブルという点が魅力です。
ありませんか？　謎のバグが起きて、必死でトレースバックを追ってみると、「うわ、ここでdictの値が書き換わってた・・・」みたいなことが。

ごく短いスコープで使うならばまだしも、クラスや関数間で受け渡すような場合は、どうもミュータブルは不安になります。データ用のクラスもミュータブルだと不安です。
こういうのを`namedtuple`で置き換えてみると、すっきり安心になりました。

最近使った例では、

- ディレクトリのパスを格納するため
- ８個セットの引数を受け渡すため
- システム内で変更されることがない基本データの格納のため

といった感じです。

パスの格納はこんなイメージです。ファクトリとセットにしています。

```python
import os
from collections import namedtuple

PathContainer = namedtuple("PathContainer","root document exe")

def generate_PathContainer(root):
    document = os.path.join(root,"document")
    exe = os.path.join(root,"hoge","fuga.exe")
    return PathContainer(root,document,exe)
```

勝手に書き換わることが決してない！と思うと、すごく安心して寄りかかれます。

５個の関数の間でミュータブルな引数を次々と渡していくとかちょっと怖かったですが、`namedtuple`にしてから大丈夫だと思えるようになりました。

【2019年10月4日追記】
この記事を書いた後も、筆者はご機嫌で`namedtuple`を活用しています。その後の知見として、関数の戻り値として使うというのは有力だと思います。

pythonはこんな風にして、関数の戻り値として、簡単に`tuple`を返せます。

```python
def analyze_text(text):
    # some_process
    name = "analyzed_name"
    age = processed_number
    return name,age
```

ただこれが意外と曲者で、考え無しに使っていると、似た関数で`name`だけ返していたり、`age,name`を返したり、`name,address`を返したり、、といった具合に戻り値が混沌としていくことがあります。

こういう場合、戻り値の型を`namedtuple`で定義してしまえば、一気にすっきりします（この記法はすぐあとで説明します）。

```python

from typing import NamedTuple

class ResultAnalyze(NamedTuple):
    name: str 
    age: int 

def analyze_text(text):
    # some_process
    name = "analyzed_name"
    age = processed_number
    return ResultAnalyze(name,age)
```

こうしておけば、戻り値がバラバラになるなんてことは起きなくなります。

本体記事にもあったように、`namedtuple`は数値インデックスでのアクセスもできるなど、`tuple`と同じインターフェースを持っています。ですので、既存の関数をこういう風にリファクタしても、既存のコードに影響を与えることなく動きます。
【追記終わり】

## ドットアクセス
それと、ドットアクセスはやはり魅力です。見やすい、書きやすいですよね。

```python
path_container["exe"]

VS

path_container.exe
```

更にドットアクセスならば、エディターによる入力補完がききやすいです。

ちなみに引数を動的にしたいときには、`getattr`を使えばOKです。

```python
path_container = generate_PathContainer("ROOTPATH")
key = "exe"
getattr(path_container, key)

# ROOTPATH\hoge\fuga.exe
```

## デフォルト値を設定する
`namedtuple`はデフォルト値を設定することもできます。
`__new__.__defaults__`を使えばOKです。これで便利に活用できますね。

```python
Point = namedtuple("Point","name x y")
Point.__new__.__defaults__ = ("name",0,0)

# tupleを作れば良いので、こうやってNoneで埋める手も
Point.__new__.__defaults__ = tuple([None]*3)
```

## typing.NamedTupleによる記述法【2019年10月4日追記】
この記事を書いた当時は把握できていなかったのですが、python3.6.1より、`typing.NamedTuple`を用いた記法が実装されています。以下のような書き方です。

```python
from typing import NamedTuple

class PathContainer(NamedTuple):
    root: str
    document: str
    exe: str

path_container = PathContainer("root_path","document_path","exe_path")
```


`root: str`などと書いてあるのは、型ヒントです（pythonで型を指定するための仕様です）。この記法では、型ヒントが必須となっています。

行数は少し伸びますが、`PathContainer = namedtuple("PathContainer","root document exe")`よりもわかりやすいと感じる方もおられるのではないでしょうか。

更に以下の方法で、簡単にデフォルト値が設定できます。

```python
from typing import NamedTuple

class PathContainer(NamedTuple):
    root: str = "root_path"
    document: str = "document_path"
    exe: str = "exe_path"

path_container = PathContainer()

print(path_container)
# output => PathContainer(root='root_path', document='document_path', exe='exe_path')
```

筆者自身は、この記法を把握してからは、namedtupleは全てこの書き方で書くようになりました。それと、筆者はPyCharmを使っているのですが、型方式で書くとPyCharmがnamedtupleをきちんとclassとして認識してくれるので、入力補完がばっちりききます。この点も大きな魅力です。

[公式の説明はこちら](https://docs.python.org/ja/3.6/library/typing.html#typing.NamedTuple)です。

【追記終わり】

## パフォーマンス（速度）
速度面のパフォーマンスですが、`sqlite`と併用すると、初期化が早いというメリットがあります（後記）。
ただ、それ以外の場面でははっきりしたメリットは見つかりませんでした。
計測してみたところ、キーによるデータアクセスは、だいたい`dict`の２分の１倍速（＝２倍の時間がかかる）です。通常のクラスと比較しても低速でした。
インスタンス化の速度もdictより何割か低速です。
実用上問題のあるレベルではないですが、ヘビーなループがあるときなどは、dictを選ぶかもしれません。


## sqliteのデータコンテナに活用
`sqlite`からデータを読み出して、リストに格納することがありますよね。
ここで`namedtuple`を使うのはおすすめできそうです。
せっかくですので、少し丁寧に解説します。

### 書き方
まず結論です。こういう書き方になります。

```python
import sqlite3
from contextlib import closing
from collections import namedtuple

DBPATH = r"C:\xxxxx\yyyyy\dbfile.sqlite"
with closing(sqlite3.connect(DBPATH)) as conn:
    cursor = conn.cursor()
    SQL = "select * from table1"
    cursor.execute(SQL)
    fieldname_list = [field[0] for field in cursor.description]
    RowNamedtuple = namedtuple("RowNamedtuple", fieldname_list)
    rows = [RowNamedtuple._make(row) for row in cursor]    #Rownamedtuple(row)はダメ！
```

`cursor.execute(SQL)`でデータを読み出します。その結果から、フィールド名を取得し、それをもとに`namedtuple`のファクトリを作成します。そして、ファクトリの`._make`を使って、各レコードを初期化する、という流れです。

`[field[0] for field in c.description]`がちょっと分かりにくいかもしれません。
この部分は、`select`結果に含まれるフィールド名をリストにして取り出しています。そして、`namedtuple`の第２引数にそれを渡して、テーブルと同じフィールドを持つ`namedtuple`を作っています。

`c.description`は、`select`されたフィールド名を格納しています。
じゃあ、`field[0]`の`[0]`はなんだということになるのですが、実は`c.description`の中身は 
`( ("filedname1",None,None,None,None,None,None), ("filedname2",None,None,None,None,None,None),... )`
という形式のタプルになっているのです。これはPython DB APIとの互換性を保つためだそうです。`field[0]`でこの0番だけを読み取っています。

仕様についてはドキュメントに解説がありますので参考にしてください。
[sqlite3 cursor.description](https://docs.python.org/2/library/sqlite3.html#sqlite3.Cursor.description)


**最後は`RowNamedtuple._make(row)`となることに注意してください。**`RowNamedtuple(row)`では初期化できません。私はここで３０分くらいハマったことがあります。
（デフォルトでは、`cursor`をイテレートすると、`tuple`が返ってくるんですね。`namedtuple`は、引数を１個ずつ渡す必要があるので（可変長引数スタイル）、`tuple`をそのまま渡しても正しく初期化できないです。
こういう風に、「そのまま渡したい」ときが、まさに`_make`の出番になります。）

なお、`RowNamedtuple(*row)`と書くこともできますが、パフォーマンスは少し低下します。

### メリット
同じ場面で、`sqlite3.Row`のまま使ったり、更に`dict`に変換することもできますが、`namedtuple`には以下のような特徴があります。

- ドットアクセスができる
- `dict`よりも初期化が早い
- イミュータブルである

１番目、２番目は、はっきりうれしい点です。３番目は場面によってありがたいです。

### パフォーマンス
サンプルコードを兼ねて、初期化速度の計測結果を掲げておきます。
計測に用いたsqliteファイルは15000レコードのものです。

コードはこれです。

```python

def read_by_plain():
    with closing(sqlite3.connect(DB)) as conn:
        cursor = conn.cursor()
        cursor.execute(SQL)
        return [row for row in cursor]    #プレーンタプルが返る

def read_by_Row():
    with closing(sqlite3.connect(DB)) as conn:
        conn.row_factory = sqlite3.Row
        cursor = conn.cursor()
        cursor.execute(SQL)
        return [row for row in cursor]     #sqlite3.Row型が返る

def read_by_Row_to_dict():
    with closing(sqlite3.connect(DB)) as conn:
        conn.row_factory = sqlite3.Row
        cursor = conn.cursor()
        cursor.execute(SQL)
        return [dict(row) for row in cursor]    #dictが返る

def read_by_plain_to_ntp():
    with closing(sqlite3.connect(DB)) as conn:
        cursor = conn.cursor()
        cursor.execute(SQL)
        RowNamedtuple = namedtuple("RowNamedtuple", [field[0] for field in cursor.description])
        return [RowNamedtuple._make(row) for row in cursor]    #namedtupleが返る

```

それぞれ、

1. 特に設定せずに、プレーンな`tuple`を取得する 
2. `sqlite3.Row`型を取得する
3. `dict`にして取得する
4. `namedtuple`にして取得する

という場面を想定しています。
`sqlite3.Row`についてはあとで説明します。

計測結果は以下の通りです。%timeitで10回ループしました。

```python
%timeit -r 10 read_by_plain()
1 loop, best of 10: 96.7 ms per loop

%timeit -r 10 read_by_Row()
1 loop, best of 10: 100 ms per loop

%timeit -r 10 read_by_Row_to_dict()
1 loop, best of 10: 150 ms per loop

%timeit -r 10 read_by_plain_to_ntp()
1 loop, best of 10: 106 ms per loop
```

`Row`はほとんどオーバーヘッドが無いようです。そして、`namedtuple`も大差無い速度になっています。
対して、`dict`はかなり差が付きました。

ただし、前記のようにキーアクセスの速度は`dict`のほうが早いですので、パフォーマンスについては一長一短があります。
個人的には、格納にウェートがある場合は、`namedtuple`のほうが扱いやすいです。たびたび編集するような用途なら`dict`のほうがもちろん便利です。


### 補足：sqlite3.Rowについて
`sqlite3.Row`というのはsqlite専用のデータ型です。これを`conn.row_factory`に渡しておくと、読み出し結果が`sqlite3.Row`で返ってきます。
`sqlite3.Row`は高度に最適化されているとのことで、パフォーマンスは確かに良好です。更に、キーでもインデックスでもアクセスできますの、`namedtuple`と似たところがあります。
単に読めればよいという場合、`sqlite3.Row`だけでもよいかもしれません。
ただし、機能は限定されていますし、ドットアクセスもできません。私の感覚では、少し物足りない感じはあります。
たとえば、`namedtuple`は`_replace`ができるあたりなど嬉しいですね。

詳しくは
[ドキュメント row_factory](https://docs.python.org/3.7/library/sqlite3.html#sqlite3.Connection.row_factory)
[ドキュメント sqlite3.Row](https://docs.python.org/3.7/library/sqlite3.html#sqlite3.Row)
を参照してください。



## 最後に
`namedtuple`にはなんだか魅力を感じます。もう少し使い方を研究してみたいです。

なお、投稿者は完全独学プログラマーのため、内容とか、訳とか、言葉使いとか色々間違っているところがあるかもしれません。遠慮なくご指摘いただければ幸いです。

# 参考情報
いくつか`namedtuple`の関連リンクを挙げておきます。

[Pythonのdictをnamedtupleに変換してベンチ取ってみた](http://nekoya.github.io/blog/2014/06/02/config-namedtuple/)
※ベンチマークがあります。

[namedtupleのドキュメント](https://docs.python.org/3.7/library/collections.html#namedtuple-factory-function-for-tuples-with-named-fields)

[namedtuple(名前付きタプル)で簡易クラスを作る](https://qiita.com/tag1216/items/19fbc1a4f6a24dd7861b)

[おっぱいそん！ namedtuple](http://oppython.hatenablog.com/entry/2015/09/22/162108)

[スタックオーバーフロー](https://stackoverflow.com/questions/2970608/what-are-named-tuples-in-python)

[Pythonメモ-07 (collections.namedtuple, 名前付きのフィールドを持つタプル)](https://devlights.hatenablog.com/entry/2017/05/10/125125)