blog.mtb-production.info

よくある経歴のPythonエンジニア

__new__ と __init__ って何が違うんですか

Python__new____init__ って何が違うんですか。

前回の記事の中で疑問に思ったことです。 blog.mtb-production.info

目次

現状の理解

えー、、、

インスタンス生成とインスタンス初期化って何が違うんだろう。

違う様な一緒の様な・・・

再度、ドキュメントを読もう

ドキュメント読んで動かすのをやりたい。 でもちょいちょい動かしても、何が起きてるのか、よくわかってないので、ドキュメントを読んでイメージを作る。

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

今度は、__init__ も読む。

あれ、、、、 object.__init_subclass__ という似たメソッドがある・・・ややこしい。

とりあえずバージョン3.6で追加されたらしい。

なるほど。わからん。置いとこう。

__init__ について

__init__ のドキュメント読んだ。

とりあえず、 __new__ の後に、 __init__ が呼ばれることはわかった。

後、__new__ で生成したインスタンス__init__ でカスタマイズするのもわかった。

なるほど。なんとなくわかって来たぞ。

サンプルコードを書いて動きを確かめてみよう。

書いてみたサンプルコード1

class SampleSuperClass(object):
    def __new__(cls):
        print("called super class __new__ method")
        return super().__new__(cls)

    def __init__(self):
        print("called super class __init__ method.")


class SampleSubClass(SampleSuperClass):
    def __new__(cls):
        print("called sub class  __new__ method")
        return super().__new__(cls)

    def __init__(self):
        print("called sub class __init__ method.")
        super().__init__()


SampleSubClass()

実行結果

called sub class  __new__ method
called super class __new__ method
called sub class __init__ method.
called super class __init__ method.

ふむ。

わかったこと。

  • __new__ は、親クラスの __new__ を呼ぶ様に実装したら、自分のイメージしてた実行の流れになった(正しいのかどうかは謎。)
  • object.__new__ が呼ばれると、 __init__() が実行される。
  • あれ? SampleSuperClass みたいな親クラスに、 __new__ がない時も、__init__ は実行されるよね?

書いてみたサンプルコード2

class SampleSuperClass(object):
    def __init__(self):
        print("called super class __init__ method.")


class SampleSubClass(SampleSuperClass):
    def __new__(cls):
        print("called sub class  __new__ method")
        return super().__new__(cls)

    def __init__(self):
        print("called sub class __init__ method.")
        super().__init__()


SampleSubClass()

実行結果

called sub class  __new__ method
called sub class __init__ method.
called super class __init__ method.

はい。イメージと同じ。

SampleSubClassに __init__ がない時もやってみよう。

書いてみたサンプルコード3

class SampleSuperClass(object):
    def __init__(self):
        print("called super class __init__ method.")


class SampleSubClass(SampleSuperClass):
    def __new__(cls):
        print("called sub class  __new__ method")
        return super().__new__(cls)

SampleSubClass()

実行結果

called sub class  __new__ method
called super class __init__ method.

これもイメージと同じ。 なるほど。

サブクラスで、 __new__ を実行すると、 親クラスの __init__ より先に実行される。 ということは、、、、こんな感じで実装すれば、親クラスの __init__ の挙動を変更できる?

書いてみたサンプルコード4

親クラスの __init__ の挙動を __init__ をオーバーライドせずに変えてみた。

class SampleSuperClass(object):
    def __init__(self):
        if not hasattr(self, "setting_name"):
            self.setting_name = "default"
        print("called super class __init__ method with {}.".format(self.setting_name))


class SampleSubClass(SampleSuperClass):
    def __new__(cls):
        print("called sub class  __new__ method")
        return super().__new__(cls)

class SampleSubClassWithCustom(SampleSuperClass):
    def __new__(cls):
        print("called sub class  __new__ method with custom setting.")
        cls.setting_name = "custom"
        return super().__new__(cls)

SampleSubClass()
SampleSubClassWithCustom()

実行結果

called sub class  __new__ method
called super class __init__ method with default.
called sub class  __new__ method with custom setting.
called super class __init__ method with custom.

だいぶメタプログラミングっぽくなって来た。

これで、渡した文字列を元にクラスを生成をしたら、だいぶメタプログラミングっぽい気がする。

やってみよう。

書いてみたサンプルコード5

class DefaultDatabaseDriver:
    def __init__(self):
        print("load default database driver")

class CustomDatabaseDriver:
    def __init__(self):
        print("load custom database driver")

class SampleSuperClass(object):
    def __init__(self):
        if not hasattr(self, "db_driver_class"):
            self.db_driver_class = "DefaultDatabaseDriver"
        self.db_driver = eval("{}()".format(self.db_driver_class))
        print("called super class __init__ method")

class SampleSubClass(SampleSuperClass):
    def __new__(cls):
        print("called sub class  __new__ method with default driver")
        return super().__new__(cls)

class SampleSubClassWithCustom(SampleSuperClass):
    def __new__(cls):
        print("called sub class  __new__ method with custom driver.")
        cls.db_driver_class = "CustomDatabaseDriver"
        return super().__new__(cls)

SampleSubClass()
SampleSubClassWithCustom()

実行結果

called sub class  __new__ method with default driver
load default database driver
called super class __init__ method
called sub class  __new__ method with custom driver.
load custom database driver
called super class __init__ method

とりあえず、なんとなくそれっぽい感じになった。(eval より、もっといい方法がある様な気がするけど。。。)こういう風に実装すると、セッティングで、使うクラスを変更できる。ポリモーフィズム

ただ、これがPythonっぽいのかどうかはわからない。

おわり

__init____new__ の違いを知りたくて、色々書いてみた。

とりあえず、 __init__ より __new__ の方が先に実行されるし、なんか色々できるんだろうなあ、みたいな印象。でも、 super().__init__ を、いつ実行するか、を制御したら、結局同じ様なことをできそうなきがするので、なんであるのかはいまいちわかってない。

実装のフレームワークのコードを見たりしながら、「あーこんな風に使うんだ」って勉強してきたい。