🫔
Pythonで型定義にNamedTupleを使うのはやめたほうが良さそう
はじめに
pythonでもある程度しっかり型定義をしたくてTypedDictやdataclassを調べていました。
その過程で「NamedTupleでも行けるんじゃね?」と思ったのですが、ダメそうな挙動になったのでそのメモです。
TypedDictやdataclassによる型定義の概要
pythonで型を定義したいとき、TypedDictやdataclassを使うと次のように書くことができます。この2つの違いは一旦おいておきますが、どちらを使ってもPythonで独自の型を定義して使うことができます。
TypedDict
from typing import TypedDict
class TD1(TypedDict):
a: str
b: int
td1: TD1 = TD1(a='z', b=0)
print(td1)
output
{'a': 'z', 'b': 0}
dataclass
from dataclasses import dataclass
@dataclass
class DC1:
a: str
b: int
dc1: DC1 = DC1(a='z', b=0)
print(dc1)
output
DC1(a='z', b=0)
NamedTupleで型定義してみたらどうなるか
とりあえず定義してみる
from typing import NamedTuple
class NT1(NamedTuple):
a: str
b: int
nt1: NT1 = NT1(a='z', b=0)
print(nt1)
output
NT1(a='z', b=0)
TypedDictやdataclassと全く同じで問題なさそうに見えます。
しかし、、、
NamedTuple同士を比較してみる
次のコードと実行結果をごらんください。
from typing import NamedTuple
class NT1(NamedTuple):
a: str
b: int
class NT2(NamedTuple):
a: str
b: int
class NT3(NamedTuple):
b: int
a: str
class NT4(NamedTuple):
c: str
d: int
nt1: NT1 = NT1(a='z', b=0)
nt2: NT2 = NT2(a='z', b=0)
nt3: NT3 = NT3(a='z', b=0)
nt4: NT4 = NT4(c='z', d=0)
print('nt1 == nt2 : ', nt1 == nt2)
print('nt1 == nt3 : ', nt1 == nt3)
print('nt1 == nt4 : ', nt1 == nt4)
output
nt1 == nt2 : True
nt1 == nt3 : False
nt1 == nt4 : True
おかしいことにお気づきでしょうか。
-
nt1
とnt2
が同じものと判定されています。- クラスが違うので違和感を覚える人もいるかも知れませんが、中身が完全に一致いるので同じモノということで理解出来るかと思います。
-
nt1
とnt3
は中身が同じなのにFalseになっています。- さっきと結果が違うじゃないかと思ってしまいます。しかしよく見てください。
NT1
とNT3
はa
とb
の順番が違います。なので別物です。
- さっきと結果が違うじゃないかと思ってしまいます。しかしよく見てください。
-
nt1
とnt4
は、急にc
やd
が出てくるので別物にしか見えません。しかし、同じものとして判定されます。- 何故かというと両方とも1番目の要素が
z
で2番目の要素が0
だからです。
- 何故かというと両方とも1番目の要素が
これはどういうことかというと、NamedTupleはあくまでtuple
であり、比較の際named
部分は見ずにtuple
部分のみで比較しているということになります。[1][2]
応用(?)事例
これを応用すると、正しいけどヤバいコードを書くことができます。
from typing import NamedTuple
class User(NamedTuple):
first_name: str
last_name: str
age: int
email: str = ""
class Name(NamedTuple):
last_name: str
first_name: str
name = Name(first_name='太郎', last_name='山田')
user = User(*name, age=20)
print(user)
output
User(first_name='山田', last_name='太郎', age=20, email='')
名字と名前を逆になりました。
これはtupleの挙動として正しいですので、mypyの型チェックに引っかかりませんでした。
私はコレをコードレビューで見つけられる自信がありませんので、NamedTupleの利用は気をつけたほうが良いと思います。
型付けに使うなら、やっぱりTypedDictかdataclassが良さそうですね。
(おまけ)AIに出力結果を聞いてみた
全員間違えました!!
Duet AI
Amazon Q
Copilot Chat
NCDC株式会社( ncdc.co.jp/ )のエンジニアチームです。 募集中のエンジニアのポジションや、採用している技術スタックの紹介などはこちら( github.com/ncdcdev/recruitment )をご覧ください! ※エンジニア以外も記事を投稿することがあります
Discussion