Pythonに標準で付属するcollectionsモジュールには名前付きタプルをサポートするnamedtuple関数がある。これを使って名前付きタプルを使用する方法を紹介する。
# 通常のタプルで(名前, 身長, 体重)を表す
kawasaki = ('kawasaki', 168.9, 62.8)
print(kawasaki[0]) # kawasaki:名前の要素にアクセス
kawasaki[1] = 180.2 # TypeError
# namedtupleで(名前, 身長, 体重)を表す
from collections import namedtuple
Person = namedtuple('Person', ['name', 'height', 'weight'])
#Person = namedtuple('Person', 'name, height, weight')
#Person = namedtuple('Person', 'name height weight')
#Person = namedtuple('Person', 'name,height,weight')
kawasaki = Person('kawasaki', 168.9, 62.8)
print(kawasaki.name) # kawasaki:name要素にアクセス
# タプルなので要素の変更はできない
kawasaki.height = 180.2 # AttributeError
# 要素がミュータブルならその値は変更できてしまう点には注意
test = Person([0, 1, 2], 3, 4)
test.name[0] = 100
print(test.name) # [100, 1, 2]
# namedtuple関数はtupleの派生クラスを返す(Personはtupleを継承するクラス)
print(type(kawasaki)) # <class '__main__.Person'>
print(issubclass(Person, tuple)) # True
# 要素のデフォルト値を指定する
RGB = namedtuple('RGB', ['r', 'g', 'b'], defaults=[0, 0, 0])
black = RGB()
print(black) # RGB(r=0, g=0, b=0)
red = RGB(r=255)
print(red) # RGB(r=255, g=0, b=0)
# 要素名に予約語は指定できない
PartyMem = namedtuple('PartyMem', ['name', 'id', 'class']) # ValueError
PartyMem = namedtuple('PartyMem', 'name, id, class', rename=True)
mem0 = PartyMem('kawasaki', 0, 'fighter')
print(mem0) # PartyMem(name='kawasaki', id=0, _2='fighter')
# 既存の値(リストなど)から名前付きタプルを作成
rgb_values = [255, 255, 0]
yellow = RGB._make(rgb_values)
print(yellow) # RGB(r=255, g=255, b=0)
# 名前付きタプルを辞書に変換
y = yellow._asdict()
print(y) # {'r': 255, 'g': 255, 'b': 0}
# 名前付きタプルの要素の値を変更した新しい名前付きタプルを作成
white = yellow._replace(b=255)
print(white) # RGB(r=255, g=255, b=255)
print(yellow) # RGB(r=255, g=255, b=0)
# 名前付きタプルの要素の値を変更した新しい名前付きタプルを作成
from pathlib import Path
s = '0,foo,10,20\n1,bar,30,40'
Path('test.csv').write_text(s)
print(Path('test.csv').read_text())
DATA = namedtuple('DATA', 'id,name,v0,v1')
import csv
with open('test.csv') as fin:
reader = csv.reader(fin)
data = [DATA._make(line) for line in reader]
print(data)
# 出力結果:
#[DATA(id='0', name='foo', v0='10', v1='20'),
# DATA(id='1', name='bar', v0='30', v1='40')]
名前付きタプルとは、その要素をインデックスではなく、名前で指定できるタプルのことだ。Pythonではcollectionsモジュールのnamedtuple関数を呼び出すことで、名前付きタプルを表すクラスを作成できる。そのインスタンスは名前付きタプルとして扱える。
以下は通常のタプルを使って、(名前, 身長, 体重)という3つの値の組を表す例だ。
kawasaki = ('kawasaki', 168.9, 62.8)
print(kawasaki[0]) # kawasaki:名前の要素にアクセス
kawasaki[1] = 180.2 # TypeError
このタプルの第0要素(先頭要素)は名前を、第1要素は身長を、第2要素は体重を表す。各要素には角かっこ「[]」の中にインデックス位置を指定してアクセスする。よって、print関数呼び出しでは「kawasaki[0]」のようにしてタプルの第0要素である名前を出力している。また、タプルはイミュータブル(変更不可能)なので、「kawasaki[1] = 180.2」のように要素の値を変更しようとすると例外が発生する。
このようにインデックス位置で要素にアクセスするのではなく、要素に名前を付け、それを使ってアクセスすることで、コードの可読性が高まる。これを実現するのが名前付きタプルである。
Pythonではcollectionsモジュールからnamedtuple関数をインポートして、その関数に型名とタプルの要素名を指定することで、名前付きタプルを表すクラスを生成できる。以下は上の例と同じことをする名前付きタプル(Personクラス)を定義する例だ。
from collections import namedtuple
Person = namedtuple('Person', ['name', 'height', 'weight'])
#Person = namedtuple('Person', 'name, height, weight')
#Person = namedtuple('Person', 'name height weight')
#Person = namedtuple('Person', 'name,height,weight')
namedtuple関数の第0引数にはそのタプルのクラス名を、第1引数にはそのタプルの要素名を文字列を要素とするリストとして与える。もしくはコメントアウトされている行のようにカンマや空白文字で要素名を区切った単一の文字列として与えてもよい。ここでは第0要素の名前は「name」に、第1要素の名前は「height」に、第2要素の名前は「weight」にしている。
なお、代入文の左辺にある「Person」にはnamedtuple関数が返すクラスが代入され、これを呼び出すことで、namedtuple関数の第0引数に指定したクラス名のインスタンスが作成される(左辺の変数名を「get_person」のようにもできるだろうが、実際に代入されるのはクラスオブジェクトなので、第0引数に指定するクラス名と戻り値の代入先の変数名は一致している方が混乱を招かないだろう)。
このクラスを使って名前付きタプルを定義するには以下のようにする。
kawasaki = Person('kawasaki', 168.9, 62.8)
print(kawasaki.name) # kawasaki:name要素にアクセス
変数kawasakiには名前付きタプルが代入され、その第0要素(名前を表す要素)には「kawasaki.name」のようにしてアクセスする。通常のタプルと同様に「kawasaki[0]」のような形式でのアクセスも可能だ。
Personクラスのインスタンスは名前付きタプルなので、その要素の値は変更できない(ただし、発生するのがAttributeError例外となる)。
kawasaki.height = 180.2 # AttributeError
だが、その要素がミュータブルならその値は変更できてしまうのは、通常のタプルと同様だ。
test = Person([0, 1, 2], 3, 4)
test.name[0] = 100
print(test.name) # [100, 1, 2]
この例ではname、height、weightというタプルの要素名を一切考慮せずに任意の値を与えることも可能なことが分かる。それはともかく、上の例では、変数testが指すPersonオブジェクトのname要素がリストになっている。そのため、そのリストの要素は変更可能だ。
名前付きタプルはタプルの派生クラスとなっていることも確認しておこう。
print(type(kawasaki)) # <class '__main__.Person'>
print(issubclass(Person, tuple)) # True
最初の行では名前付きタプルとして定義したオブジェクトの型が__main__モジュールのPersonクラスであることが分かる(本稿では説明していないが、namedtuple関数で名前付きタプルを表すクラスを定義する際に、それが所属するモジュール名も指定可能だ)。
その次の行では、このPersonクラスがtupleクラスのサブクラスかどうかを調べているが、その結果はTrueとなる。つまり、名前付きタプルは通常のタプルが持つ特性(演算や上述したようなインデックスによる要素アクセスなど)を受け継いでいる。
また、namedtuple関数呼び出しの際には、各要素のデフォルト値も指定できる。以下の例ではRGB値を表す名前付きタプルRGBを定義しているが、r、g、bの各要素のデフォルト値を0としている。
RGB = namedtuple('RGB', ['r', 'g', 'b'], defaults=[0, 0, 0])
この例では3要素の名前付きタプルを定義し、全要素のデフォルト値を指定した。この名前付きタプルのインスタンスを作成する例を以下に示す。
black = RGB()
print(black) # RGB(r=0, g=0, b=0)
red = RGB(r=255)
print(red) # RGB(r=255, g=0, b=0)
最初の例では、全ての値の指定を省略したので、全要素の値は0となっている。次の例ではr要素の指定だけを行っているので、他の要素の値は0になっている。
なお、namedtuple関数の呼び出し時に、幾つかの要素だけデフォルト値を指定することも可能だ。
RGB = namedtuple('RGB', 'r, g, b', defaults=[0])
このようにすると、名前付きタプルを表すRGBクラスのインスタンス生成時にはr要素とg要素の値の指定は必須で、最終要素であるbについてはその値の指定を省略できる(そのデフォルト値は0)。
要素名を指定する際に、Pythonのキーワードは指定できない点にも注意されたい。
PartyMem = namedtuple('PartyMem', ['name', 'id', 'class']) # ValueError
この例では要素名の一つとして「class」を指定しているが、これはPythonのキーワードなので例外が発生する。そうした状況でも例外を発生させたくないときには、namedtuple関数の呼び出し時にrenameパラメーターにTrueを指定する。
PartyMem = namedtuple('PartyMem', 'name, id, class', rename=True)
mem0 = PartyMem('kawasaki', 0, 'fighter')
print(mem0) # PartyMem(name='kawasaki', id=0, _2='fighter')
このときには、要素名として扱えない名前が置き換えられる。上の例では「class」が「_2」に置き換えられている。
先ほども述べたが、namedtuple関数で定義する名前付きタプルを表すクラスは通常のタプルに加えて幾つかのメソッドや属性を持っている。以下では3つのメソッドを紹介する。
以下に例を示す。
RGB = namedtuple('RGB', ['r', 'g', 'b'], defaults=[0, 0, 0])
rgb_values = [255, 255, 0]
yellow = RGB._make(rgb_values)
print(yellow) # RGB(r=255, g=255, b=0)
# 名前付きタプルを辞書に変換
y = yellow._asdict()
print(y) # {'r': 255, 'g': 255, 'b': 0}
# 名前付きタプルの要素の値を変更した新しい名前付きタプルを作成
white = yellow._replace(b=255)
print(white) # RGB(r=255, g=255, b=255)
print(yellow) # RGB(r=255, g=255, b=0)
_makeメソッドを使うと、CSVファイルからデータを読み込んで、それをタプルを要素とするリストに変換するなどの処理を簡単に行える。
from pathlib import Path
s = '0,foo,10,20\n1,bar,30,40'
Path('test.csv').write_text(s)
print(Path('test.csv').read_text())
DATA = namedtuple('DATA', 'id,name,v0,v1')
import csv
with open('test.csv') as fin:
reader = csv.reader(fin)
data = [DATA._make(line) for line in reader]
print(data)
# 出力結果:
#[DATA(id='0', name='foo', v0='10', v1='20'),
# DATA(id='1', name='bar', v0='30', v1='40')]
Copyright© Digital Advantage Corp. All Rights Reserved.