静かなる名辞

pythonとプログラミングのこと


【python】クラスを関数として使う

はじめに

 クラスはcallされたら自分のクラスのインスタンスを返さないといけないと思っていませんか? 一般論としてはその通りなのですが、Pythonではそうしないメカニズムも用意されています。

 __new__を使えば割となんでもできます。もっとも、実用的な用途は相当限られるでしょう。

原理

 こんな感じ。

クラス
クラスは呼び出し可能です。そのオブジェクトは通常、そのクラスの新たなインスタンスのファクトリとして振舞いますが、 __new__() をオーバーライドして、バリエーションを持たせることもできます。

クラス cls の新しいインスタンスを作るために呼び出されます。

3. データモデル — Python 3.7.4 ドキュメント

 なんのこっちゃと思うかもしれませんが、

  • Hogeがcallされる(コード的にはHoge(*args, **kwargs))
  • Hoge.__new__(Hoge, *args, **kwargs)がcallされる。返り値はHogeのインスタンス(を適切に生成して返すのが__new__の役割ということ)で、この段階でselfができる
  • Hoge.__init__がcallされる(いつもの)

 という流れで処理されている訳ですね。

やってみる

 以上を理解したら、自由自在にクラスを関数として使えます。

class add:
    def __new__(cls, a, b):
        return a + b

print(add(1, 2))  # => 3

 意外と単純です。

 なお、目ざとい人は「あれ、__init__の引数フォーマットでエラーにならない?」と思うかもしれませんが、上述のドキュメントにも書いてある通り

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

 
 という仕様があり、上記の記述を可能にしています。

実用的(?)な用途

 別にわざわざこんなことをする必要はないのですが、使いみちがまったくないわけではありません。

引数によってインスタンスを返すときとそうでないときがあったりする

 コンストラクタに引数が与えられたかどうかによって返り値の型が変わるパターン。

class Hoge:
    def __new__(cls, a=None):
        if a is None:
            return "this is not Hoge's instance, is a str"
        else:
            self = super().__new__(cls)
            return self

    def __init__(self, a=None):
        self.a = a

    def __repr__(self):
        return "Hoge(a={})".format(self.a)

h1 = Hoge()
h2 = Hoge(42)
print(h1)  # => this is not Hoge's instance, is a str
print(h2)  # => Hoge(a=42)

 面白い使いみちがあるかもしれません。かえって厄介かもしれません。

デコレータを書くのに使う

 デコレータをクラスで作るというテクニックがあります。

 参考:
【python】クラスでデコレータ! - 静かなる名辞

 で、このときに

@deco
def f(args):
    ...

@deco(options)
def f(args):
    ...

 の両方に対応させたいケースがあります。面倒くさいですね。

 こういうときは__new__で切り分ける実装になるのだと思いますが、書こうとしたら相当ややこしかったのでコードは割愛させてください。

まとめ

 知れば知るほど奥が深い動的言語の世界。

 クラスが自分のインスタンスを返さなくても良いということは知らなかった。