今更感があるけど Python 3.4.3 と 3.4.4 の間で namedtuple
の挙動が変わってたことを知ったので記録。
みんなだいすきな namedtuple
。
ほとんどタプル的な使い方をするけど、やっぱりクラスや辞書っぽく名前で参照したい時に使うやつ。
正しく namedtuple
を使っている場合、この変更でほとんど困ることはないはず。
変更のコミットとドキュメント
コミット
-
__dict__
をオーバーライド操作しなくなった。- それにともなって
__getstate__
もオーバーライドしない
- それにともなって
-
_asdict
は直接OrderedDict
を返すようになった。
ドキュメント
Python 3.4.3 と Python 3.4.4 の間のコミット。
ドキュメントについては、 3.4.X の最新が Python 3.4 Documentation として記述されているので、 Python 3.3 Documentation と比べる。
namedtuple
に対しての vars()
の記述が消えているのが分かる。
どういうことがおこるか
自分がこれでハマった稀なパターンとして namedtuple
を継承したサブクラスで __slots__
を定義しなくても _asdict
で正しく取得できるようになるため気が付かずにスルーしていて(この時点で誤りがある)、高いバージョンから低いバージョンに戻すときに何故か空の辞書が帰ってきて、初めてミスに気がついた。
サンプルコード
from collections import namedtuple
X = namedtuple('X', ('a', 'b'))
class Y(X):
__slots__ = ()
class Z(X):
# __slots__ = () を忘れた場合
pass
x = X(1, 2)
y = Y(1, 2)
z = Z(1, 2)
print(x._asdict())
print(hasattr(x, '__dict__'))
print(y._asdict())
print(hasattr(y, '__dict__'))
print(z._asdict())
print(hasattr(z, '__dict__'))
結果: Python 3.4.3 (以前)
OrderedDict([('a', 1), ('b', 2)])
True
OrderedDict([('a', 1), ('b', 2)])
True
{}
True
結果: Python 3.4.4 (以降)
OrderedDict([('a', 1), ('b', 2)])
False
OrderedDict([('a', 1), ('b', 2)])
False
OrderedDict([('a', 1), ('b', 2)])
True
仕組み
-
namedtuple が
__dict__
を持たなくなった。-
hasattr(target, '__dict__')
がFalse
になる。 - namedtuple で作った定義を継承して
__slots__ = ()
していれば__dict__
を間接的に使うvars()
を呼び出すとエラーになる。 - それまでは
__dict__
が存在していたのでエラーにはならない。
-
-
__slots__ = ()
を忘れてもちゃんと空でない辞書が帰ってくるようになった。-
__slots__
を利用する際の注意 にある通り、__slots__
をサブクラスで定義してない場合__dict__
に代入が行われる (もはやタプルでない) 。 - 3.4.3 までは
__dict__
が破壊されて取れなくなっていたということ。
-