銀月の符号

Python 使い見習いの日記・雑記

__new__ の調査便乗

RRF言語研究所さんの『http://d.hatena.ne.jp/rockRicefield/20090915/1253002685』に惹かれて。 Python 暦浅いはずなのにもう __new__ の魔窟へ突入とは。すごいです、友達になってください><

そして自分も __new__ について復習してみた。

クラスオブジェクトを呼び出した時の動作

普通の自作クラスとそのインスタンスの生成はこのような感じ。

>>> class Spam(object):
...   def __init__(self, n):
...     self.n = n
...
>>> spamins1 = Spam(7)
>>> type(spamins1)
<class '__main__.Spam'>
>>> spamins1.n
7

この spam(7) のコンストラクタ呼び出し部分を分解するとこんな感じ。

>>> spamins2 = Spam.__new__(Spam, 7) # object.__new__(Spam, 7)
>>> spamins2.__init__(7)

__new__ ではインスタンスが生成されるけど、まだ __init__ が走っていないため未初期化の状態。 __init__ が走って初めて n 属性が作成される。

>>> object.__new__(Spam, 7).n
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'spam' object has no attribute 'n'

実は、 __new__ の後の __init__ は必ず呼ばれるわけではなくて型チェックが入る。

__new__() が cls のインスタンスを返さない場合、インスタンスの __init__() メソッドは呼び出されません。

http://www.python.jp/doc/release/ref/customization.html

なので Spam(7) により近いコードはこんな感じ。

>>> spamins3 = Spam.__new__(Spam, 7) # object.__new__(Spam, 7)
>>> if isinstance(spamins3, Spam):
...   spamins3.__init__(7)
...

__new__ はクラスメソッド

__init__ の第一引数はインスタンスだけど、 __new__ の第一引数はクラス。つまり、クラス属性に代入するコードが書きやすい。たとえば、 Counter('A') のような形で呼び出された回数を数えるクラスはこのようになる。

>>> class Counter(object):
...   count = 0
...   def __new__(cls, n):
...     cls.count += 1
...     return super(Counter, cls).__new__(cls) # object.__new__(cls)
...   def __init__(self, n):
...     self.n = n
...
>>> Counter('a')
<__main__.Counter object at 0x00AF8030>
>>> Counter('b')
<__main__.Counter object at 0x00AF0F50>
>>> Counter.count
2

とはいえ、クラスは self インスタンスから type(self) もしくは self.__class__ で得られるので、 __new__ が必須ということではない。むしろ普通は使わない。

class Counter(object):
    count = 0
    def __init__(self, n):
        type(self).count += 1
        self.n = n

もしくは直接クラスを書く。 Counter のサブクラスから呼び出されると意味がちょっと変わるのでまったく同じコードというわけではない。

class Counter(object):
    count = 0
    def __init__(self, n):
        Counter.count += 1
        self.n = n

単に self.count += 1 と書くとハマる。インスタンス属性 count が作られて、クラス属性 count は 0 のまま置き去りという。

しまった、脱線した。 __new__ の話に戻って、と。

__new__ から自分のクラスのではないインスタンスを返してみる

自分のクラスのではないインスタンスを返す __new__ メソッドを持つ、自作クラスを作ってしまうとよくわからない世界に到達。

ぶっちゃけると呼び出す時、名前もドットも書かずに済むクラスメソッドなんだけど、ね。

例1、Spam() と書いたのに 'Spam' という文字列が

Spam()と書くと Spam クラスのインスタンスが出来上がるんだよね」、という前提はもはや通用しない。

>>> class Spam(object):
...   def __new__(cls):
...     return 'Spam'
...
>>> Spam()
'Spam'

これと同じ意味。

>>> class Spam(object):
...   @classmethod
...   def getspam(cls):
...     return 'spam'
...
>>> Spam.getspam()
'spam'
例2、クラス属性 n=7 を持ったクラスを動的生成するまわりくどい方法

普通は関数で作る。

def class_factory(name):
    return type(name, (object,), {'n': 7})

でも、あえてクラスを使用してみる。

>>> class ClassFactory(object):
...   def __new__(cls, name):
...     return type(name, (object,), {'n': 7})
...
>>> acls = ClassFactory('A')
>>> acls
<class '__main__.A'>
>>> acls.n
7
>>> acls()
<__main__.A object at 0x00AF0A90>

関数で作るとグローバル変数が必要になる場面で、 ClassFactory クラスは自身のクラス属性を使うことができるため、より複雑なことをする際には役立つかもしれない。たとえば作ったことのあるクラス、および名前を保持・再利用してみたり。

>>> class ClassFactory(object):
...   c = {}
...   def __new__(cls, name):
...     if name not in cls.c:
...       cls.c[name] = type(name, (object,), {'n': 7})
...     return cls.c[name]
...
>>> acls = ClassFactory('A')
>>> acls2 = ClassFactory('A')
>>> acls is acls2
True
例3、シングルトンっぽいの

最初に呼び出された時にインスタンスを作ったら、それ以降は同じものを返し続ける。

>>> class Singleton(object):
...   ins = None
...   def __new__(cls):
...     if cls.ins is None:
...       print 'create instance'
...       class SingletonIns(object):
...         pass
...       cls.ins = SingletonIns()
...     return cls.ins
...
>>> a = Singleton()
create instance
>>> b = Singleton()
>>> a is b
True

とはいえ「SingletonIns クラスのインスタンスが 1 つしか存在しないことを保証する」といった機能はないので注意。 SingletonIns クラスオブジェクトを探してもなかなかみつからないので、 2 つめを作られにくいのは確かだけれど。

>>> SingletonIns # ない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'SingletonIns' is not defined
>>> Singleton.SingletonIns # ここにもない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Singleton' has no attribute 'SingletonIns'
>>> type(Singleton()) # やっと発見
<class '__main__.SingletonIns'>
>>> ins2 = _() # 2 つめ作成
>>> import copy
>>> ins3 = copy.copy(ins2) # クラスオブジェクトがなくとも 3 つめだ!

Singleton.ins にいたずらされない限り、 Singleton() で得られるオブジェクトは同一。 モジュールグローバルな状態の保持・変更などにつかってみるのもありか。 Singleton().x = 1 と代入して、あとで Singleton().x で取り出す。インスタンスは保持しないで、必要になったらそのつど Singleton() するという戦略。こうなるとクラス名は ???State みたいにしたほうがいいかも。

>>> a.spam = 'spam'
>>> b.spam
'spam'
>>> c = Singleton()
>>> c.spam
'spam'