静かなる名辞

pythonとプログラミングのこと


【python】namedtupleはすごい(きもちわるい)

概要

 namedtupleは存在は知ってたけど、使い方を知ったら「すげえ(きめえ)」という感想にしかならなかったので、記事に書きました*1

namedtupleはクラスではない

 普通は、namedtupleというクラスがあると思いませんか? さにあらず、関数です。

>>> from collections import namedtuple
>>> namedtuple
<function namedtuple at 0x7f8c288800d0>

 お、おう。使い方を調べます。

名前付きフィールドを持つタプルのサブクラスを作成するファクトリ関数

>>> # Basic example
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(11, y=22)     # instantiate with positional or keyword arguments
>>> p[0] + p[1]             # indexable like the plain tuple (11, 22)
33
>>> x, y = p                # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y               # fields also accessible by name
33
>>> p                       # readable __repr__ with a name=value style
Point(x=11, y=22)

collections --- コンテナデータ型 — Python 3.7.4 ドキュメント

 あー、そういうものだったの。

 第一引数はクラス名、第二引数は「属性」の名前ですね。はい。こいつはクラスを返します。

 大切なことなのでもう一度書きます。namedtupleはクラスを生成して返す関数です。

 動的言語の面目躍如という感じ……。

一応タプルらしい

 タプルらしく、中身を変えられないし、hashableです。

>>> Hoge = namedtuple("Hoge", "a b")  # 空白orカンマ区切り文字列で属性リストにできる
>>> h = Hoge(a=1, b=2)
>>> h
Hoge(a=1, b=2)
>>> h[0] = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Hoge' object does not support item assignment
>>> h.a = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> {h}
{Hoge(a=1, b=2)}

 だいたい期待する性質は持っていると言っていいでしょう。いや、気持ち悪いですが。

でもしょせんただのクラスなので

 継承して好きなメソッドを定義したりできます。すごい!

>>> class Hoge2(Hoge):
...     def product_of_a_and_b(self):
...         return self.a * self.b
... 
>>> h2 = Hoge2(a=10, b=100)
>>> h2.product_of_a_and_b()
1000

 元のクラスが要らなければ、こういう書き方もあります。上のドキュメントに紹介されている例です。

>>> class Point(namedtuple('Point', ['x', 'y'])):
...     __slots__ = ()
...     @property
...     def hypot(self):
...         return (self.x ** 2 + self.y ** 2) ** 0.5
...     def __str__(self):
...         return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)

>>> for p in Point(3, 4), Point(14, 5/7):
...     print(p)
Point: x= 3.000  y= 4.000  hypot= 5.000
Point: x=14.000  y= 0.714  hypot=14.018

 理解はできるけど、すごく○○です……。

まとめ

 こういうものだとは知らなかったのと、発想が斜め上を行っているのと、いろいろな意味で驚きました。

 クラス定義のコンストラクタ(__init__)の記述を省略したりするのにも使えるそうです。言われるとなるほどと思いますが、pythonはほんと奥が深いですね……。

*1:そんなことも知らないでPython書いてたのかよって感じですが