__init __()が常に__new __()の後に呼び出されるのはなぜですか?


568

私はクラスの1つを合理化しようとしているだけで、フライウェイトデザインパターンと同じスタイルでいくつかの機能を導入しています。

ただし、__init__が常に後に呼び出される理由については、少し混乱してい__new__ます。私はこれを期待していませんでした。なぜこれが起こっているのか、そしてこの機能を別の方法で実装するにはどうすればよいですか?(実装をに入れるのは別として、__new__かなりハックに感じます。)

次に例を示します。

class A(object):
    _dict = dict()

    def __new__(cls):
        if 'key' in A._dict:
            print "EXISTS"
            return A._dict['key']
        else:
            print "NEW"
            return super(A, cls).__new__(cls)

    def __init__(self):
        print "INIT"
        A._dict['key'] = self
        print ""

a1 = A()
a2 = A()
a3 = A()

出力:

NEW
INIT

EXISTS
INIT

EXISTS
INIT

どうして?


はデザインパターンも理解しようとしていましたが、初めて:flyweightデザインパターンについて聞いたことがあります。非常に優れたリンクで、ほとんどすべての一般的な言語で例を示しています。
EmmaYang

回答:


598

__new__新しいインスタンスの作成を制御する必要がある場合に使用します。

__init__新しいインスタンスの初期化を制御する必要がある場合に使用 します。

__new__インスタンス作成の最初のステップです。最初に呼び出され、クラスの新しいインスタンスを返します。

対照的に、 __init__何も返しません。作成後のインスタンスの初期化のみを担当します。

一般に、__new__str、int、unicode、タプルなどの不変の型をサブクラス化しない限り、オーバーライドする必要はありません。

2008年4月からの記事:使用する__new__対を__init__mail.python.org。

あなたがしようとしていることは通常ファクトリーで行われ、それがそれを行うための最良の方法であることを考慮する必要があります。使用__new__はきれいな解決策ではないので、工場の使用を検討してください。ここに良い工場の例があります。


1
最終的__new__にはFactoryクラスの内部で使用することになりましたが、これは非常にクリーンになりました。入力ありがとうございます。
Dan

11
申し訳ありませんが、の使用を__new__記載されたケースに厳密に限定することはできません。拡張可能なジェネリッククラスファクトリを実装するのに非常に便利です。Pythonでクラスを生成するための不適切な使用に関する質問への私の回答を参照しくださいそうする例については。__new__
martineau 2015

170

__new__は静的クラスのメソッドですが、__init__インスタンスメソッドです。 __new__最初にインスタンスを作成する必要があるため、インスタンス__init__を初期化できます。注__init__かかるselfパラメータとして。インスタンスを作成するまではありませんself

さて、私はあなたがPythonにシングルトンパターンを実装しようとしていることを集めます。それにはいくつかの方法があります。

また、Python 2.6以降では、クラスデコレータを使用できます。

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
  ...

8
@タイラーロング、「@シングルトン」の仕組みがよくわかりません。デコレータは関数を返しますが、MyClassはクラスだからです。
Alcott、2011

11
なぜ辞書?clsは常に同じであり、たとえば、シングルトンごとに新しい辞書を取得するため、1つのアイテムのみを含む辞書を作成しています。
Winston Ewert 2011

7
@Alcott:意見は必要ありません- ドキュメントはあなたに同意します。
イーサンファーマン

2
@オルコット。はい、デコレータは関数を返します。ただし、クラスと関数の両方が呼び出し可能です。インスタンス= {}はグローバル変数である必要があると思います。
タイラーロング

4
@TylerLong Alcottには良い点があります。これMyClassにより、元のクラスのインスタンスを返す関数に名前がバインドされます。しかし、そこにすべての元のクラスを参照する方法は今だなく、MyClass機能ブレークさisinstance/ issubclassクラス属性/メソッドへのアクセスは直接として、小切手MyClass.somethingでクラスを命名、super問題かないのかどうかが依存するなど、など、通話適用するクラス(およびプログラムの残りの部分)。
Ben

148

最もよく知られているオブジェクト指向言語では、のような式SomeClass(arg1, arg2)が新しいインスタンスを割り当て、インスタンスの属性を初期化してから返します。

最もよく知られているオブジェクト指向言語では、「インスタンスの属性の初期化」の部分は、コンストラクターを定義することにより、クラスごとにカスタマイズできます。これは、基本的には新しいインスタンスを操作するコードのブロックです(コンストラクター式に提供される引数を使用) )必要な初期条件を設定します。Pythonでは、これはクラスの__init__メソッドに対応します。

Python __new__は、「新しいインスタンスの割り当て」部分のクラスごとの同様のカスタマイズにすぎません。もちろん、これにより、新しいインスタンスを割り当てるのではなく、既存のインスタンスを返すなど、異常なことを行うことができます。したがって、Pythonでは、この部分を必ずしも割り当てを伴うものと考えるべきではありません。必要なのは__new__、どこかから適切なインスタンスが出てくることだけです。

しかし、それはまだ仕事の半分にすぎず、Pythonシステムがジョブの残りの半分(__init__)を後で実行したい場合とそうでない場合があることを知る方法はありません。その動作が必要な場合は、そのように明示的に言う必要があります。

多くの場合、リファクタリングして、必要なだけ__new__、または必要ない__new__、または__init__すでに初期化されたオブジェクトに対して異なる動作をさせることができます。しかし、本当にやりたいのであれば、Pythonは実際に「仕事」を再定義することを許可しているので、SomeClass(arg1, arg2)必ずしもが__new__後に続くとは限りません__init__。これを行うには、メタクラスを作成し、その__call__メソッドを定義する必要があります。

メタクラスはクラスの単なるクラスです。また、クラスの__call__メソッドは、クラスのインスタンスを呼び出したときの動作を制御します。だから、メタクラス__call__あなたはクラスを呼び出すときに何が起こるかの方法コントロールは、つまり、インスタンス作成メカニズムを最初から最後まで再定義できます。これは、シングルトンパターンなどの完全に非標準のインスタンス作成プロセスを最もエレガントに実装できるレベルです。実際には、コードの10本の未満の線であなたを実装することができSingleton、その後でさえいじくるにあなたを必要としないことメタクラス__new__ すべてであり、変えることができます任意の単に追加することによって、シングルトンにそうでない場合は、通常のクラスを__metaclass__ = Singleton

class Singleton(type):
    def __init__(self, *args, **kwargs):
        super(Singleton, self).__init__(*args, **kwargs)
        self.__instance = None
    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super(Singleton, self).__call__(*args, **kwargs)
        return self.__instance

ただし、これはおそらく、この状況で実際に必要とされるよりも深い魔法です。


25

ドキュメントを引用するには:

通常の実装では、適切な引数を使用して「super(currentclass、cls).__ new __(cls [、...])」を使用し、必要に応じて新しく作成されたインスタンスを変更することにより、スーパークラスの__new __()メソッドを呼び出し、クラスの新しいインスタンスを作成しますそれを返す前に。

...

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

__new __()の主な目的は、不変タイプ(int、str、タプルなど)のサブクラスがインスタンスの作成をカスタマイズできるようにすることです。


18
If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked.それが重要なポイントです。別のインスタンスを返す場合、original __init__は呼び出されません。
Jeffrey Jose

@tgray私はあなたの答えがドキュメントからのものであることを理解していますが、clsのインスタンスを返さないユースケースを知っているかどうか知りたいです。返されたオブジェクトの__new__クラスがチェックされると、メソッドは失敗したチェックを黙って渡すのではなく、エラーをスローするようです。クラスのオブジェクト以外は何も返す必要がない理由がわからないためです。 。
soporific312

@ soporific312ユースケースを見たことがありません。 この回答では、設計の理由のいくつかについて説明しますが、その「機能」を利用するコードはまだ見ていません。
tgray

12

この質問はかなり古いものだと思いますが、同様の問題がありました。以下は私が望んだことをしました:

class Agent(object):
    _agents = dict()

    def __new__(cls, *p):
        number = p[0]
        if not number in cls._agents:
            cls._agents[number] = object.__new__(cls)
        return cls._agents[number]

    def __init__(self, number):
        self.number = number

    def __eq__(self, rhs):
        return self.number == rhs.number

Agent("a") is Agent("a") == True

このページをリソースとして使用しましたhttp://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html


7
注:__new__は常に適切なオブジェクトを返すため、インスタンスがすでに存在する場合でも、常に__init__が呼び出されます。
奇妙なことに2014年

10

この質問に対する簡単な答えは__new__、クラスと同じ型の値を返す場合、__init__関数は実行され、それ以外の場合は返されないということです。この場合、コードはA._dict('key')と同じクラスを返すclsため、__init__実行されます。


10

ときに__new__同じクラスの戻りインスタンスは、__init__返されたオブジェクト上で、その後実行されます。つまり、実行されるの__new__を防ぐために使用することはできません__init__。以前に作成されたオブジェクトをから戻した場合でも、何度も何度も__new__初期化されてdouble(トリプルなど)に__init__なります。

上記のvartecの答えを拡張して修正するシングルトンパターンへの一般的なアプローチは次のとおりです。

def SingletonClass(cls):
    class Single(cls):
        __doc__ = cls.__doc__
        _initialized = False
        _instance = None

        def __new__(cls, *args, **kwargs):
            if not cls._instance:
                cls._instance = super(Single, cls).__new__(cls, *args, **kwargs)
            return cls._instance

        def __init__(self, *args, **kwargs):
            if self._initialized:
                return
            super(Single, self).__init__(*args, **kwargs)
            self.__class__._initialized = True  # Its crucial to set this variable on the class!
    return Single

全文はこちら

実際に関係するもう1つのアプローチは、__new__クラスメソッドを使用することです。

class Singleton(object):
    __initialized = False

    def __new__(cls, *args, **kwargs):
        if not cls.__initialized:
            cls.__init__(*args, **kwargs)
            cls.__initialized = True
        return cls


class MyClass(Singleton):
    @classmethod
    def __init__(cls, x, y):
        print "init is here"

    @classmethod
    def do(cls):
        print "doing stuff"

このアプローチでは、すべてのメソッドを@classmethodで装飾する必要があることに注意してくださいMyClass。実際ののインスタンスを使用することは決してないからです。


6

このドキュメントを参照

数値や文字列などの不変の組み込み型をサブクラス化するとき、および他の状況では、静的メソッドnewが便利です。newは、インスタンス構築の最初のステップであり、initの前に呼び出されます

新しい方法は、その第一引数としてクラスと呼ばれています。その責任は、そのクラスの新しいインスタンスを返すことです。

この比較INITinitはその最初の引数としてインスタンスと呼ばれ、それは何も返しません。その責任はインスタンスを初期化することです。

initを呼び出さずに新しいインスタンスが作成される状況があります(たとえば、インスタンスがピクルからロードされた場合など)。呼び出すことなく、新しいインスタンスを作成する方法はありません新しいを(いくつかのケースでは、あなたが基本クラスの呼び出しで逃げることができますが、新規で)。

あなたが達成したいことに関しては、シングルトンパターンに関する同じドキュメント情報にもあります

class Singleton(object):
        def __new__(cls, *args, **kwds):
            it = cls.__dict__.get("__it__")
            if it is not None:
                return it
            cls.__it__ = it = object.__new__(cls)
            it.init(*args, **kwds)
            return it
        def init(self, *args, **kwds):
            pass

また、デコレータを使用して、PEP 318からこの実装を使用することもできます。

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
...

1
initからの粗末な形の呼び出しは__new__本当にハッキーな音です。これがメタクラスの目的です。
狂った物理学者

6
class M(type):
    _dict = {}

    def __call__(cls, key):
        if key in cls._dict:
            print 'EXISTS'
            return cls._dict[key]
        else:
            print 'NEW'
            instance = super(M, cls).__call__(key)
            cls._dict[key] = instance
            return instance

class A(object):
    __metaclass__ = M

    def __init__(self, key):
        print 'INIT'
        self.key = key
        print

a1 = A('aaa')
a2 = A('bbb')
a3 = A('aaa')

出力:

NEW
INIT

NEW
INIT

EXISTS

NBとして副作用のM._dictプロパティが自動的にからアクセス可能になるAようA._dictので、ついでにそれを上書きしないように注意してください。


__init__を設定するメソッドがありませんcls._dict = {}。おそらく、このメタタイプのすべてのクラスで共有される辞書は必要ありません(ただし、アイデアでは+1)。
狂った物理学者

5

__new__は、クラスの新しい空のインスタンスを返す必要があります。次に、そのインスタンスを初期化するために__init__が呼び出されます。__new__の "NEW"ケースでは__init__を呼び出さないため、呼び出されます。呼び出し__new__ているコードは、__ init__が特定のインスタンスで呼び出されたかどうかを追跡しません。また、ここでは非常に異常なことをしているためです。

__init__関数でオブジェクトに属性を追加して、初期化されたことを示すことができます。__init__の最初の属性としてその属性が存在するかどうかを確認し、存在している場合は先に進まないでください。


5

@AntonyHatchkinsの回答を更新すると、メタタイプのクラスごとにインスタンスの個別のディクショナリが必要になる可能性があり__init__ます。つまり、すべてのクラスにわたってグローバルにするのではなく、そのディクショナリでクラスオブジェクトを初期化するメソッドをメタクラスに含める必要があります。

class MetaQuasiSingleton(type):
    def __init__(cls, name, bases, attibutes):
        cls._dict = {}

    def __call__(cls, key):
        if key in cls._dict:
            print('EXISTS')
            instance = cls._dict[key]
        else:
            print('NEW')
            instance = super().__call__(key)
            cls._dict[key] = instance
        return instance

class A(metaclass=MetaQuasiSingleton):
    def __init__(self, key):
        print 'INIT'
        self.key = key
        print()

私は先に進んで、元のコードを__init__メソッドで更新し、構文をPython 3表記(super属性としてではなく、クラス引数の引数なしの呼び出しとメタクラス)に変更しました。

いずれにせよ、ここで重要な点は、クラス初期化子(ということである__call__メソッドが)のいずれかを実行しません__new__か、__init__キーが見つかった場合。これは__new__、デフォルトの__init__ステップをスキップする場合にオブジェクトをマークする必要があるを使用するよりもはるかにクリーンです。


4

少し深く掘り下げます!

CPythonのジェネリッククラスのタイプはでtypeあり、そのベースクラスはObject(メタクラスのような別のベースクラスを明示的に定義しない限り)です。低レベルの呼び出しのシーケンスはここにあります。呼び出される最初のメソッドは、type_call次にを呼び出しtp_new、次にtp_initです。

ここで興味深いのは、オブジェクトのメモリを割り当てる()を実行するの(基本クラス)新しいメソッドをtp_new呼び出すことです:)Objectobject_newtp_allocPyType_GenericAlloc

その時点でオブジェクトがメモリ内に作成され、__init__メソッドが呼び出されます。__init__がクラスに実装されていない場合、object_initは呼び出され、何もしません:)

次にtype_call、変数にバインドするオブジェクトを返します。


4

__init__従来のオブジェクト指向言語では、単純なコンストラクターと見なす必要があります。たとえば、JavaまたはC ++に精通している場合、コンストラクターには独自のインスタンスへのポインターが暗黙的に渡されます。Javaの場合はthis変数です。Java用に生成されたバイトコードを検査すると、2つの呼び出しに気付くでしょう。最初の呼び出しは「新しい」メソッドへの呼び出しであり、次の呼び出しはinitメソッド(ユーザー定義のコンストラクターへの実際の呼び出し)への呼び出しです。この2ステップのプロセスにより、インスタンスの別のメソッドであるクラスのコンストラクターメソッドを呼び出す前に、実際のインスタンスを作成できます。

Pythonの場合は__new__、ユーザーがアクセスできる追加機能です。Javaは型付けされているため、柔軟性はありません。言語がその機能を提供している場合、の実装者__new__は、場合によっては無関係なオブジェクトのまったく新しいインスタンスを作成するなど、インスタンスを返す前にそのメソッドで多くのことを実行できます。また、このアプローチは、特にPythonの場合の不変タイプに対してもうまく機能します。


4

ただし、__init__が常に後に呼び出される理由については、少し混乱してい__new__ます。

ここではC ++の類推が役立つと思います。

  1. __new__オブジェクトにメモリを割り当てるだけです。オブジェクトのインスタンス変数は、それを保持するためにメモリを必要とし、これがステップ__new__が行うことです。

  2. __init__ オブジェクトの内部変数を特定の値に初期化します(デフォルトの場合もあります)。


2

__init__後に呼び出され__new__ますが、サブクラスでそれをオーバーライドするとき、あなたの追加のコードがまだ呼び出されますように。

すでにを持っているクラスをサブクラス化しようとしている場合__new__、これに気付かない人は、を適合させ__init__、呼び出しをサブクラスに転送することから始める可能性があり__init__ます。この__init__後呼び出しの規則は__new__、期待どおりに機能するのに役立ちます。

__init__まだスーパークラスは、任意のパラメータを可能にする必要が__new__必要ですが、そうしないと、通常、明確なランタイムエラーを作成します。そして、エクステンションに問題がないことを明確__new__にするために*args、おそらく明示的にfor および '** kw'を許可する必要があります。

両方持っていることが一般的に悪い形である__new____init__理由オリジナルポスターは、説明した動作で、相続の同じレベルで同じクラスにします。


1

ただし、__init__が常に後に呼び出される理由については、少し混乱してい__new__ます。

それがそのように行われること以外に理由はあまりありません。__new__クラスを初期化する責任はありませんが、他のいくつかのメソッドはそうします(__call__おそらく、-確かにわかりません)。

私はこれを期待していませんでした。なぜこれが起こっているのか、そしてこの機能を別の方法で実装する方法を誰かに教えてもらえますか?(実装をに入れるのは別として、__new__かなりハックに感じます)。

__init__すでに初期化されている場合は何もできません。または、新しいインスタンス__call__のみを呼び出すnew を使用して新しいメタクラスを作成し、__init__それ以外の場合は単にを返すこともできます__new__(...)


1

単純な理由は、新しいインスタンスがインスタンスの作成に使用され、initがインスタンスの初期化に使用されるためです。初期化する前に、インスタンスを最初に作成する必要があります。そのため、initの前にnewを呼び出す必要があります


1

今、私は同じ問題を抱えており、いくつかの理由で、デコレーター、ファクトリー、メタクラスを避けることにしました。私はそれをこのようにしました:

メインファイル

def _alt(func):
    import functools
    @functools.wraps(func)
    def init(self, *p, **k):
        if hasattr(self, "parent_initialized"):
            return
        else:
            self.parent_initialized = True
            func(self, *p, **k)

    return init


class Parent:
    # Empty dictionary, shouldn't ever be filled with anything else
    parent_cache = {}

    def __new__(cls, n, *args, **kwargs):

        # Checks if object with this ID (n) has been created
        if n in cls.parent_cache:

            # It was, return it
            return cls.parent_cache[n]

        else:

            # Check if it was modified by this function
            if not hasattr(cls, "parent_modified"):
                # Add the attribute
                cls.parent_modified = True
                cls.parent_cache = {}

                # Apply it
                cls.__init__ = _alt(cls.__init__)

            # Get the instance
            obj = super().__new__(cls)

            # Push it to cache
            cls.parent_cache[n] = obj

            # Return it
            return obj

クラスの例

class A(Parent):

    def __init__(self, n):
        print("A.__init__", n)


class B(Parent):

    def __init__(self, n):
        print("B.__init__", n)

使用中で

>>> A(1)
A.__init__ 1  # First A(1) initialized 
<__main__.A object at 0x000001A73A4A2E48>
>>> A(1)      # Returned previous A(1)
<__main__.A object at 0x000001A73A4A2E48>
>>> A(2)
A.__init__ 2  # First A(2) initialized
<__main__.A object at 0x000001A7395D9C88>
>>> B(2)
B.__init__ 2  # B class doesn't collide with A, thanks to separate cache
<__main__.B object at 0x000001A73951B080>
  • 警告: Parentは初期化しないでください。他のクラス衝突します-各子に個別のキャッシュを定義しない限り、それ私たちが望むことではありません。
  • 警告:祖父母は奇妙な振る舞いをするため、Parentのクラスのようです。[未確認]

オンラインでお試しください!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.