tupleを見てきたが、その流れでnamed tupleを調べたので備忘録として。
list, tuple, dictionaryはindexかkeyでデータを選ぶ
list、tupleは基本的にindexで内包データを管理(参照(出力)、上書き(tupleは参照のみ))する。
dictionaryは基本的にkeyを元にデータを管理する。
# listはindexでデータを管理
>>> list1 = [1, 2, 3, 4, 5, 6]
>>> list1[3] # index が3 =前から4つめのデータを出力
4
>>> list1[4] = 10 # indexが4 =前から5つめのデータを10に上書き
>>> list1
[1, 2, 3, 4, 10, 6]
# dictionaryはKeyでデータを管理
>>> dic1 = {"a":1, "b":2, "c":3, "d":4, "e":5, "f":6}
>>> dic1["c"] # Keyが"c"のデータを出力
3
>>> dic1["e"] = 10 # Keyが"e"のデータを10に上書き
>>> dic1
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 10, 'f': 6}
listやtupleではKeyでデータの管理は出来ないし、dictionaryはindexでデータの管理は出来ない。
しかし、indexと名前を両方使って管理できるデータタイプがある。
それがnamed tuple。
collections.namedtuple
データの作り方
named tupleを使用するには通常より2手順必要。
モジュールcollectionsからnamedtupleをimportする手順と、class(下記ではntpl1 class)を作る手順。
classを作る手順でdictionaryのkeyに相当するname(下記では”a”, “b”〜)を設定する。これはclassを作る際に良く言われる設計図(blue print)。
新しい変数(下記では”alp”)にclassとそのvalueを代入する事でnamedtupleのデータが出来る。
>>> from collections import namedtuple
>>> ntpl1 = namedtuple("ntpl1", ["a", "b", "c", "d", "e", "f"])
>>> ntpl1
<class '__main__.ntpl1'>
>>> type(ntpl1)
<class 'type'>
>>> alp = ntpl1(1, 2, 3, 4, 5, 6)
>>> alp
ntpl1(a=1, b=2, c=3, d=4, e=5, f=6)
>>> alp[3] # indexでデータを得る場合
4
>>> alp.e # nameでデータを得る場合
5
alp[3]は場所(index)でデータを得、alp.eはnameでデータを得ている。
namedtupleはtuple/class/dictionaryの混合(hybrid)と言える。
nameでもindexでもデータを扱える様になり、nameがある分コードが読み易くなる、便利な仕組みと言える。
unpacking
namedtupleの要素数と同じ数の変数にそれぞれデータを放り込むのがunpacking。
>>> alp ntpl1(a=1, b=2, c=3, d=4, e=5, f=6) >>> s, t, u, v, w, x = alp # s~xの6つの変数にunpacking >>> s, t, u, v, w, x (1, 2, 3, 4, 5, 6)
データの加工
dictionaryやlistの様にデータが加工できるだろうか?
>>> alp[4] = 10 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'ntpl1' object does not support item assignment >>> alp.e = 10 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute
データの加工は受け付けないようだ。
メソッドを見てみる。
>>> dir(alp) ['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '_asdict', '_field_defaults', '_fields', '_make', '_replace', 'a', 'b', 'c', 'count', 'd', 'e', 'f', 'index']
a~fは作った時に設定したkeyとしてのname、こう見ると、tupleの流れだけに、基本的にcount、indexしかメソッドが無いのが分かる。
alpを新規代入する事によるデータの変更は可能だが、下記を見て分かる通り、最初のalpと新規で作ったalpのidは別のもの。別のnamedtupleが作られた、と言う事が分かる。
>>> id(alp) 4425727136 >>> alp = ntpl1(1, 2, 3, 4, 10, "b") >>> alp ntpl1(a=1, b=2, c=3, d=4, e=10, f='b') >>> id(alp) 4425726560
dictionaryに置き換える
named tupleはデータの形がkey:valueペアなので、”_asdict()”で新しいdictionary型データを作る事ができる。
>>> A = alp._asdict() # dictionary型としてAに代入
>>> A
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6}
>>> type(A)
<class 'dict'>
>>> A["c"]
3
>>> A["a"]
1
>>> A["b"] = 10 # dictionary型なのでmutable、上書きも可能
>>> A
{'a': 1, 'b': 10, 'c': 3, 'd': 4, 'e': 5, 'f': 6}
typing.NamedTuple
こちらはcollections.namedtuple の型付き版。python3.6から使える。
データの型書く事ができる。
作り方
typingからNamedTupleをインポートして、class “Alp”を作る。
alpはstr型、numはint型と設定、__repr__で出力時の書式を設定する。
>>> from typing import NamedTuple
>>> class Alp(NamedTuple):
... alp:str
... num:int
...
... def __repr__(self) -> str:
... return f'<Alp {self.alp}, num={self.num}>'
>>> alp = Alp("a", 1)
>>> alp
<Alp a, num=1>
>>> alp.__repr__
<bound method Alp.__repr__ of <Alp a, num=1>>
# indexでデータを取得
>>> alp[0]
'a'
>>> alp[1]
1
# nameでデータを取得
>>> alp.alp
'b'
>>> alp.num
2
Alp.alp(= alp.alp)に”a”、Alp.num(= alp.num)に1を入力。
indexでも、nameでもデータを得ることができる。
新しいデータを入れる(2022/6/6 タイトルを追記)
別のデータを入れる事もできるが、tupleなので新しいデータを作っている。
“a”, 1のalpと”b”, 1のalpはidが異なっているのが分かる。
>>> alp = Alp("a", 1)
>>> id(alp)
4494315968
>>> alp
<Alp a, num=1>
>>> alp = Alp("b", 2)
>>> id(alp)
4491927872
>>> alp
<Alp b, num=2>
注意:collections.namedtupleとの違い(当項目全文追記 2022/6/6)
ちなみに、「作り方」の項目でのコードにおいて、最初の
>>> class Alp(NamedTuple): ... alp:str ... num:int
の部分はcollections.namedtupleの以下と等価(equivalent)と言う理解。
Alp = collections.namedtuple("Alp", ["a"])
ここでcollections.namedtupleの時のように
Alp = collections.namedtuple("Alp", ["a", "b", "c", "d", "e", "f"])
とはしていない事に注意。
上記「新しいデータを入れる」の箇所で書いた通り、新しいname(Key的なもの)、valueを入れると、新しいデータが作られるだけで、dictionary化をしてもその時の組み合わせがdictionaryになるのみ。
>>> alp
<Alp a, num=1> # 最初のalp
>>> alp = Alp("c", 3) # alp = "c"、num = 3を入れた新しいalpを作る。
>>> A = alp._asdict() # alpをdictionary化する
>>> A
{'alp': 'c', 'num': 3} # 最初のa, 1は出て来ず、_asdict()を実行したときのalpが出てくる。
現時点でtyping.NamedTupleでこの様な多数のname(key)を扱う方法は理解できていない。
(この項2022/6/6 追記)
型設定について
型を設定できるとはいえ、制限を掛けられる訳ではなく、strとした箇所にintデータ4を入れても、intとした所にstrデータ”d”を入れても問題なく入る。
>>> alp2 = Alp(4, "d") >>> alp2 <Alp 4, num=d>
typing.NamedTupleにおける型設定は、コードの読み易さの為の機能、と言う理解。
参考文献
この記事は以下の情報を参考にして執筆しました。
[Learning Python, 5th Edition] Mark Lutz, O’Reilly Media (amazonのページはこちら)
[Python Tricks: A Buffet of Awesome Python Features] Dan Bader ←amazonのページにリンクしています
Python 公式チュートリアル


コメント