2012年5月16日水曜日

Python で unicode型を 継承した場合のはまりどころ



元ネタはこちらの記事です。

お題:


以下のようなクラスを定義しています。

class TestUnicode(unicode):
    def __init__(self, x):
        print x

このクラスは、キーワードを指定しないとインスタンスを作成できますが、
キーワードを指定してインスタンスを作成するとエラーになってしまうようです。

>>> a = TestUnicode('a')
a
>>> a
u'a'
>>> b = TestUnicode(x='b')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'x' is an invalid keyword argument for this function

なぜキーワードの指定の有無で挙動が変わってしまうのでしょうか?




答え:


unicode.__new__が、xというキーワード引数を受け取れないことが原因。


TestUnicode(x='a')とやった場合、
まず、TestUnicode.__new__(cls, x='a')が実行され、
その後で、TestUnicode.__init__(self, x='a')が実行されます。
http://www.python.jp/doc/release/reference/datamodel.html#object.__new__

ここで、TestUnicode.__new__は定義されていないので、
unicode.__new__が呼ばれることになりますが、
unicode.__new__の引数の定義は「string [, encoding[, errors]]」です。

つまり
unicode.__new__をオーバーライドしない限り、xというキーワードはダメで、stringを指定しなくてはいけない。
__init__と__new__の引数定義は揃えなくてはならない。
というわけです

したがって最初の例は、こんな感じに置き換えられます。

>>> class TestUnicode2(unicode):
...     def __init__(self, string, *vals, **kwds):
...         print string
...
>>> b = TestUnicode2(string = 'b')
b
>>> b
u'b'

dictやlistとかだと、__new__は、ほとんど何もしない(いちおう空の辞書やリストは作る)ので、__new__の存在は忘れがちですね。

1 件のコメント:

  1. なるほど、こういうことでしたか。
    実は私自身、自分の結論では__ini__がオーバーライドできないはずなのにキーワードを指定していなければ"print x"が動作する理由がわからないなと思いながら書いたのでやっと納得がいきました。
    ありがとうございます。

    返信削除