57

私はいつも次のようなメタクラスを設定してきました。

class SomeMetaClass(type):
    def __new__(cls, name, bases, dict):
        #do stuff here

しかし、私はちょうどこのように定義されたメタクラスに出くわしました:

class SomeMetaClass(type):
    def __init__(self, name, bases, dict):
        #do stuff here

どちらか一方を優先する理由はありますか?

更新:メタクラスでの__new__使用について質問していることを覚えておいてください。__init__私はすでに別のクラスでそれらの違いを理解しています。しかし、メタクラスでは、メタクラスでのクラスの作成時にのみ呼び出される__new__ため、キャッシュの実装には使用できません。__new__

4

5 に答える 5

73

クラスが作成される前に属性dictを変更する場合、またはベースタプルを変更する場合は、を使用する必要があります__new__。引数を見るまで__init__に、クラスオブジェクトはすでに存在しています。また、__new__問題のタイプの新しく作成されたクラス以外のものを返したい場合は、を使用する必要があります。

一方、__init__実行されるまでに、クラスは存在します。したがって、作成したばかりのクラスへの参照をそのメンバーオブジェクトの1つに与えるなどのことができます。

編集:「オブジェクト」とは、クラスオブジェクトを意味することをより明確にするために文言を変更しました。

于 2009-12-03T15:08:54.380 に答える
8

完全な記述は公式ドキュメントで確認できますが、基本的には、新しいオブジェクトが作成される前に(オブジェクトを作成する目的で)__new__呼び出さ__init__新しいオブジェクトが作成された後に(初期化する目的で)呼び出されます。

を使用__new__すると、オブジェクトキャッシング(新しい引数を作成するのではなく、常に同じ引数に対して同じオブジェクトを返す)や、要求されたものとは異なるクラスのオブジェクトを生成する(要求されたクラスのより具体的なサブクラスを返すために使用される)などのトリックが可能になります。一般的に、かなり奇妙なことをしているのでない限り、__new__有用性は限られています。このようなトリックを呼び出す必要がない場合は、を使用して__init__ください。

于 2009-12-03T15:16:57.087 に答える
6

実際、いくつかの違いがあります。

一つには、との最初の引数は同じではありません。これは、を使用しているすべての人に役立つわけではありませ__new__ん。誰かがこれを指摘しました、そしてそれは違いを理解するための核心です: __init__cls

  • __new__メタクラスを取得します-MyType私の例では(アプリケーションレベルのクラスはまだ作成されていないことを思い出してください)。ここで変更できますbases(注意しないと、MRO解決エラーが発生する可能性があります)。

  • __init__新しく作成されたアプリケーションレベルのクラスを取得BarFoo、その時点で、このクラスの名前空間が設定されていcls_attribます。以下の例を参照してください。

サンプルコード:

class Mixin:
    pass

class MyType(type):


    def __new__(mcls, name, bases, attrs, **kwargs):
        print("  MyType.__new__.mcls:%s" % (mcls))

        if not Mixin in bases:
            #could cause MRO resolution issues, but if you want to alter the bases
            #do it here
            bases += (Mixin,)

        #The call to super.__new__ can also modify behavior:
        #                                    classes Foo and Bar are instances of MyType
        return super(MyType, mcls).__new__(mcls, name, bases, attrs)

        #now we're back to the standard `type` 
        #doing this will neuter most of the metaclass behavior, __init__ wont
        #be called.                         
        #return super(MyType, mcls).__new__(type, name, bases, attrs)

    def __init__(cls, name, bases, attrs):
        print("  MyType.__init__.cls:%s." % (cls))

        #I can see attributes on Foo and Bar's namespaces
        print("    %s.cls_attrib:%s" % (cls.__name__, getattr(cls, "cls_attrib", None)))
        return super().__init__(name, bases, attrs)


print("\n Foo class creation:")
class Foo(metaclass=MyType):
    pass


print("\n bar class creation:")
class Bar(Foo):
    #MyType.__init__ will see this on Bar's namespace
    cls_attrib = "some class attribute"

出力:

 Foo class creation:
  MyType.__new__.mcls:<class '__main__.test.<locals>.MyType'>
  MyType.__init__.cls:<class '__main__.test.<locals>.Foo'>.
    Foo.cls_attrib:None

 Bar class creation:
  MyType.__new__.mcls:<class '__main__.test.<locals>.MyType'>
  MyType.__init__.cls:<class '__main__.test.<locals>.Bar'>.
    Bar.cls_attrib:some class attribute
于 2020-02-03T21:24:29.500 に答える
2

すでに述べたように、基本クラスや属性などを変更する場合は、で行う必要があります__new__。同じことがクラスのにも当てはまりますが、nameそれには特異性があるようです。を変更すると、たとえば、に伝播されますがname、に伝播されません。__init__attr

だからあなたは持っているでしょう:

class Meta(type):
    def __new__(cls, name, bases, attr):
        name = "A_class_named_" + name
        return type.__new__(cls, name, bases, attr)

    def __init__(cls, name, bases, attr):
        print "I am still called '" + name + "' in init"
        return super(Meta, cls).__init__(name, bases, attr)

class A(object):
    __metaclass__ = Meta

print "Now I'm", A.__name__

プリント

I am still called 'A' in init
Now I'm A_class_named_A

__init__これは、追加の魔法を実行するスーパーメタクラスを呼び出す場合に知っておくことが重要です。その場合、を呼び出す前に名前を再度変更する必要がありsuper.__init__ます。

于 2011-03-11T21:24:27.567 に答える
1

キャッシュを実装できます。Person("Jack")2番目の例では常に新しいオブジェクトを返しますが、最初の例では既存のインスタンスをで検索できます__new__(または必要に応じて何も返さないでください)。

于 2009-12-03T15:05:44.297 に答える