メタクラス関連で__new__
の使い方はよくみるけど、クラスレベルでの使い方がぐぐってもあんまりなし。
というところに、以下の記事のコメントでやってることが面白かったので、似たことを検証がてらメモ。
class A(): def __new__(cls, value): print("generate by A.__new__") cls.X = 50 return object.__new__(cls) def __init__(self, value): self.a = value def hello(self): return 'hello' class B(): def __new__(cls, value): return A.__new__(cls, value) def __init__(self, value): self.b = value class C(): def __new__(cls, value): return A(value) def __init__(self, value): self.c = value if __name__ == '__main__': b = B(10) print("type of b :", type(b)) print("b has a? :", hasattr(b, "a")) print("b has b? :", hasattr(b, "b")) print("B has X? :", hasattr(B, "X")) print("b.__dict__ :", b.__dict__) c = C(0) print("type of c :", type(c)) print("c has a? :", hasattr(c, "a")) print("c has c? :", hasattr(c, "c")) print("c.__dict__ :", c.__dict__)
> python new_with_other.py generate by A.__new__ type of b : <class '__main__.B'> b has a? : False b has b? : True B has X? : True b.__dict__ : {'b': 10} generate by A.__new__ type of c : <class '__main__.A'> c has a? : True c has c? : False c.__dict__ : {'a': 0}
BとCは__new__
でA.__new__(cls, value)
で返すか、A(value)
で返すかの違い。
クラスを呼び出してインスタンスを生成すると、__new__
が呼ばれ、インスタンスが作られてから __init__
で初期化されるというのが典型的な流れ。
まず、b
。こいつはB
のインスタンスで、この際呼ばれた__new__(cls, value)
では、cls
に B
が入ってるわけなので、たとえA.__new__
を使おうと、B
を使ってインスタンスを作ろうとしていることに変わりはない。
持ってるアトリビュートを見る限り、呼び出したクラスの__init__
だけが呼び出されている模様(つまり今回 A の __init__
は呼び出されていない)。まあこれも当然っちゃあ当然か。一方で、A.__new__
ではクラスのアトリビュート X
が加えられるんだけど、そのおかげで B.X
が存在する。まあこれも当然。
b.__dict__
みると分かる通り、基本的にメソッドはクラスのもんで、インスタンスから呼び出す場合も、クラスのアトリビュートとしてその関数が探索され(そして、self
にインスタンスが束縛され)るわけなので、Aのコンストラクタを使ったからといって、Aのメソッドがそのインスタンスに直接登録されるわけではない(よって、b.hello()
はエラーが起きる)。
続いて c
。こっちは __new__
の返り値で A(value)
、Aクラスのインスタンスを返している。ので、クラスCの呼び出しをしたのに返ってくるインスタンスの生成元クラスはAという変なことになっている。興味深いのはCの__init__
が呼び出されていないというところですかね。
で、これはなんでかというと、3. データモデル — Python 3.5.2 ドキュメント にある通りで、
__new__() が cls のインスタンスを返さない場合、インスタンスの __init__() メソッドは呼び出されません。
わけですな。だので、ちゃんとそのクラス独自の初期化をしたい場合は、object.__new__(cls, ...)
なんなり、他の__new__
を使ってとにかくcls
のインスタンスを作って返すというのが必要そう。
__new__()
が cls
のインスタンスを返さないような場合(None
はアホらしいので除くとして)ってどういうときに使えるんだろ…?
ごく平凡な1つ目の案は、実体のあるエイリアス。Aのインスタンスを作るときに、C()
が使える。*1
あるいは、Aの呼び出しに関してなにかprint
とかフックしたいとき*2。クラスCになにかクラス変数をもたせておいて、それを使ってなにかフックしたりできそう*3
あるいは、ディスパッチ*4
なんか何を考えても大体「関数でよくない?」になってしまう…。
あくまで「インスタンス生成」だということを主張しつつ、でもそれ自身のインスタンスじゃないことにそこまで違和感をもたれないような使い方か…。たとえば、A がファイルオブジェクトを引数にとるけど、Cではfilenameを引数にとって、コンストラクタ内でファイルオブジェクトをつくって、それを使ってAインスタンスを生成して返してもらう。とか。
class Hoge(): def __init__(self, file): # .. class HogeWithFilename(): def __new__(self, filename): with open(filename) as f: return Hoge(f)
みたいな。いやどうなんだ・・・・・・。
インスタンス生成しないクラス
ところで、インスタンス生成しないクラスというのをつくるときには __new__
が必要そうだ*5
class InstantiateError(Exception): pass class DontInit(): def __init__(self): raise InstantiateError if __name__ == '__main__': try: DontInit() except InstantiateError: print("eh, don't instantiate.") d = DontInit.__new__(DontInit) print("type of d :", type(d))
eh, don't instantiate. type of d : <class '__main__.DontInit'>
これは、__init__
時点で例外吐いていて、一見インスタンス生成不可に見えますが、__new__
が生き残ってるので無理やり DontInt
のインスタンスを作ることができてしまう。ので、本当にインスタンスを作りたくなければ、__new__
で例外吐くしかない。
class DontInitAtAll(): def __new__(cls): raise InstantiateError if __name__ == '__main__': try: DontInitAtAll() except InstantiateError: print("eh, don't instantiate.") try: d = DontInitAtAll.__new__(DontInitAtAll) except: print("oh, can't really instantiate")
eh, don't instantiate. oh, can't really instantiate
たとえば、MixInクラス(ふつうこいつのインスタンスを作ろうとは思わんよね?)であることを明示するために、こういう処置をするのはいいような気もする。
class MixIn(): def __new__(cls): raise InstantiateError
という MixIn
基底クラスを用意しておいて、MixInクラスはこれを継承していけば、結構いい感じにいい感じなのでは?と思ったけど、__new__
も継承されるから、MixIn先のクラスの__new__
をきちんとオーバーライドしてやらないといけないことになるから面倒か(そもそも __init__
がないって時点で MixInクラスか?ってなるだろうし、有用じゃなさが上回る…)
ううん
やっぱようわからんね