LoginSignup
6
3

More than 5 years have passed since last update.

Python 3.4.3 と 3.4.4 の間で namedtuple の挙動が微妙に変わっていた件

Posted at

今更感があるけど 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__ が破壊されて取れなくなっていたということ。
6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3