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 公式チュートリアル
コメント