Pythonメタクラス

StackOverflowユーザーの1人が言ったように、「SOを使用することは、リンクリストの代わりにハッシュテーブルを使用して検索を行うようなものです。」 再びこの素晴らしいリソースに目を向けましょう。このリソースは、さまざまな質問に対する非常に詳細で理解しやすい答えに出会います。



今回は、メタクラスとは何か、それらをどのように、どこで、なぜ使用するのか、そしてなぜこれを行う価値がないのかについて説明します。



オブジェクトとしてのクラス



メタクラスを学習する前に、クラスをよく理解する必要があります。Pythonのクラスは非常に具体的なものです(Smalltalk言語のアイデアに基づいています)。



ほとんどの言語では、クラスはオブジェクトの作成方法を記述するコードの一部にすぎません。 一般に、これはPythonにも当てはまります。

>>> class ObjectCreator(object): ... pass ... >>> my_object = ObjectCreator() >>> print my_object <__main__.ObjectCreator object at 0x8974f2c>
      
      





しかし、Pythonでは、クラスはさらに何か-クラスはオブジェクトでもあります。



class



キーワードが使用されると、Pythonはコマンドを実行し、 オブジェクトを作成します 。 取扱説明書

  >>> class ObjectCreator(object): ... pass ...
      
      





ObjectCreator



というオブジェクトをメモリ内に作成します。



このオブジェクト(クラス)自体がオブジェクト(インスタンス)を作成できるため、クラスです。



ただし、これはオブジェクトなので、次のとおりです。





動的クラス作成



クラスはオブジェクトなので、他のオブジェクトと同様に、外出先で作成できます。



たとえば、 class



キーワードを使用して関数内にクラスを作成できます。

  >>> def choose_class(name): ... if name == 'foo': ... class Foo(object): ... pass ... return Foo #  ,    ... else: ... class Bar(object): ... pass ... return Bar ... >>> MyClass = choose_class('foo') >>> print MyClass #   ,    <class '__main__.Foo'> >>> print MyClass() #      <__main__.Foo object at 0x89c6d4c>
      
      





ただし、クラス全体を自分で記述する必要があるため、これはあまり動的ではありません。



クラスはオブジェクトなので、何かによって生成される必要があります。



class



キーワードを使用すると、Pythonはこのオブジェクトを自動的に作成します。 しかし、Pythonのほとんどのものと同様に、これを手動で行う方法があります。



type



関数を覚えていますか? オブジェクトのタイプを判別できる古き良き関数:

 >>> print type(1) <type 'int'> >>> print type("1") <type 'str'> >>> print type(ObjectCreator) <type 'type'> >>> print type(ObjectCreator()) <class '__main__.ObjectCreator'>
      
      





実際、 type



関数にはまったく異なるアプリケーションがあります:外出先でクラスを作成することもできます。 type



はクラス宣言を取り、クラスを呼び出します。



(渡された引数に応じて、同じ関数を2つのまったく異なるものに使用できるのは愚かなことです。これは下位互換性のために行われます)



type



は次のように機能します。

  type(< >, <  >, #  ,    <,     >)
      
      





例えば

 >>> class MyShinyClass(object): ... pass
      
      





次のように手動で作成できます。

  >>> MyShinyClass = type('MyShinyClass', (), {}) #  - >>> print MyShinyClass <class '__main__.MyShinyClass'> >>> print MyShinyClass() #    <__main__.MyShinyClass object at 0x8997cec>
      
      





「MyShinyClass」をクラス名としても、クラスへの参照を含む変数の名前としても使用していることに気づいたかもしれません。 彼らは異なるかもしれませんが、なぜそれを複雑にしますか?



type



は、クラス属性を定義する辞書を受け入れます。

 >>> class Foo(object): ... bar = True
      
      





として書き換えることができます

  >>> Foo = type('Foo', (), {'bar':True})
      
      





通常のクラスとして使用する

  >>> print Foo <class '__main__.Foo'> >>> print Foo.bar True >>> f = Foo() >>> print f <__main__.Foo object at 0x8a9b84c> >>> print f.bar True
      
      





もちろん、あなたはそれから継承することができます:

  >>> class FooChild(Foo): ... pass
      
      





になります

  >>> FooChild = type('FooChild', (Foo,), {}) >>> print FooChild <class '__main__.FooChild'> >>> print FooChild.bar # bar is inherited from Foo True
      
      





ある時点で、クラスにメソッドを追加する必要があります。 これを行うには、目的の署名を使用して関数を定義し、属性として割り当てます。

 >>> def echo_bar(self): ... print self.bar ... >>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) >>> hasattr(Foo, 'echo_bar') >>> hasattr(FooChild, 'echo_bar') True >>> my_foo = FooChild() >>> my_foo.echo_bar() True
      
      





私が何を得ているかはすでに明らかです。クラスはPythonのオブジェクトであり、外出先でクラスを作成できます。



これは、 class



キーワードが使用されたときにPythonが行うことと、メタクラスを使用して行うことです。



メタクラスとは(最終的に)



メタクラスは、クラスを作成する「もの」です。



オブジェクトを作成するクラスを作成しますか? クラスはオブジェクトです。 メタクラスは、これらのまさにオブジェクトを作成するものです。 これらはクラスのクラスであり、次のように想像できます。

  MyClass = MetaClass() MyObject = MyClass()
      
      





このtype



使用すると、次のようなことができることを既に確認しました。

  MyClass = type('MyClass', (), {})
      
      





これは、 type



関数が実際にはメタクラスだからです。 type



は、Pythonがすべてのクラスを作成するために内部的に使用するメタクラスです。



自然な質問は、なぜ彼の名前がType



ではなく小文字で書かれているのですか?



行オブジェクトを作成するためのクラスであるstr



と整数オブジェクトを作成するためのクラスであるint



を照合するためだけのものだと思います。 type



は、クラスオブジェクトを作成するための単なるクラスです。



これは、 __class__



属性を使用して簡単に確認__class__



ます。



Pythonでは、すべて(すべて!)はオブジェクトです。 数字、文字列、関数、クラスを含む-それらはすべてオブジェクトであり、すべてクラスから作成されました:

  >>> age = 35 >>> age.__class__ <type 'int'> >>> name = 'bob' >>> name.__class__ <type 'str'> >>> def foo(): pass >>> foo.__class__ <type 'function'> >>> class Bar(object): pass >>> b = Bar() >>> b.__class__ <class '__main__.Bar'>
      
      





そして、各__class__



何ですか?

  >>> a.__class__.__class__ <type 'type'> >>> age.__class__.__class__ <type 'type'> >>> foo.__class__.__class__ <type 'type'> >>> b.__class__.__class__ <type 'type'>
      
      





したがって、メタクラスはクラスオブジェクトを作成するだけのものです。



必要に応じて、「クラスファクトリ」と呼ぶことができます。



type



はPythonが使用する組み込みメタクラスですが、もちろん独自に作成することもできます。



__metaclass__



属性



クラスを記述するときに、 __metaclass__



属性を追加できます。

 class Foo(object): __metaclass__ = something... [...]
      
      





この場合、PythonはFoo



クラスを作成するときに指定されたメタクラスを使用します。



注意、微妙な点があります!



class Foo(object)



を記述しますが、クラスオブジェクトはまだメモリに作成されていません。



Pythonはクラス定義で__metaclass__



を探します。 見つかったら、 Foo



を使用してクラスを作成します。 そうでない場合は、 type



を使用します。



つまり、あなたが書くとき

 class Foo(Bar): pass
      
      





Pythonは次のことを行います。



Foo



クラスには__metaclass__



属性がありますか?



その場合、 __metaclass__



で指定されたものを使用して、名前Foo



でメモリ内にクラスオブジェクトを作成します。



Pythonが__metaclass__



見つけられない場合、親クラスBar



__metaclass__



を検索し、同じことを試みます。



__metaclass__



がどの親にもない場合、Pythonはモジュールレベルで__metaclass__



を探します。



また、 __metaclass__



がまったく見つからない場合、 type



を使用してクラスオブジェクトを作成します。



ここで重要な質問は: __metaclass__



何を入れることができますか?



答え:クラスを作成できるもの。



クラスを作成するものは何ですか? type



またはそのサブクラス、およびそれらを使用するもの。



カスタムメタクラス



メタクラスの主な目的は、作成時にクラスを自動的に変更することです。



これは通常、現在のコンテキストに従ってクラスを作成するときにAPIに対して行われます。



馬鹿げた例を想像してみてください。モジュール内のすべてのクラスに大文字の属性名を付けることにしました。 これを行う方法はいくつかありますが、そのうちの1つはモジュールレベルで__metaclass__



を設定することです。



この場合、指定されたmeaclassを使用してこのモジュールのすべてのクラスが作成され、メタクラスに強制的に大文字のすべての属性の名前を変換させることができます。



幸いなことに、 __metaclass__



は、必ずしも正式なクラスではなく、任意の呼び出されたオブジェクトにすることができます(名前に「クラス」という単語があるクラスはクラスである必要はありませんが、これはどんなナンセンスですか?)



したがって、関数を使用した簡単な例から始めましょう。

 #        , #     `type` def upper_attr(future_class_name, future_class_parents, future_class_attr): """  -,        """ #   ,    '__' attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__')) #      uppercase_attr = dict((name.upper(), value) for name, value in attrs) #     `type` return type(future_class_name, future_class_parents, uppercase_attr) __metaclass__ = upper_attr #        class Foo(object): #    __metaclass__ ,       bar = 'bip' print hasattr(Foo, 'bar') # Out: False print hasattr(Foo, 'BAR') # Out: True f = Foo() print f.BAR # Out: 'bip'
      
      





そして今、同じことを、実際のクラスのみを使用して:

 # ,  `type`     ,  `str`  `int`, #       class UpperAttrMetaclass(type): #  __new__   __init__ #       , #     __init__   ,    . #     __new__,     , #    #       ,     , #    __new__. #    -  __init__,  . #        __call__, #      . def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__')) uppercase_attr = dict((name.upper(), value) for name, value in attrs) return type(future_class_name, future_class_parents, uppercase_attr)
      
      





しかし、これは厳密にはOOPではありません。 type



を直接呼び出し、親の__new__



呼び出しをオーバーロードしません。 それをやってみましょう:

 class UpperAttrMetaclass(type): def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__')) uppercase_attr = dict((name.upper(), value) for name, value in attrs) #   type.__new__ #  ,   return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
      
      





オプションのupperattr_metaclass



引数に気付いたかもしれません。 特別なことは何もありません。メソッドは常に現在のインスタンスを最初の引数として取得します。 通常のメソッドでself



を使用するように。



もちろん、ここで使用した名前は明確にするために非常に長いですが、 self



ように、これらすべての引数に名前を付けるための規則があります。 したがって、実際のメタクラスは次のようになります。

 class UpperAttrMetaclass(type): def __new__(cls, name, bases, dct): attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')) uppercase_attr = dict((name.upper(), value) for name, value in attrs) return type.__new__(cls, name, bases, uppercase_attr)
      
      





継承を引き起こすsuper



を使用すると、さらに改善できます(もちろん、 type



から継承したメタクラスから継承したメタクラスを作成できるため)。

 class UpperAttrMetaclass(type): def __new__(cls, name, bases, dct): attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')) uppercase_attr = dict((name.upper(), value) for name, value in attrs) return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)
      
      





以上です。 メタクラスについて言うことはもうありません。



メタクラスを使用するコードの複雑さの理由は、メタクラス自体にはありません。 通常、メタクラスは、イントロスペクション、継承操作、__ dict__などの変数に基づいて、あらゆる種類の洗練されたものに使用されます。



実際、メタクラスはすべての「ブラックマジック」、したがって複雑な部分に特に役立ちます。 しかし、それら自体は単純です。





関数の代わりにメタクラスを使用する理由



__metaclass__



は呼び出されたオブジェクトを受け入れるため、明らかに複雑なクラスを突然使用するのはなぜですか?



これにはいくつかの理由があります。





なぜメタクラスを使用するのですか?



最後に、主な質問です。 なぜ誰かがあいまいな(そしてエラーの原因となる)機能を使用するのでしょうか?



まあ、通常は使用しないでください:

メタクラスは、ユーザーの99%が考える必要さえない深い魔法です。 それらを使用する必要があるかどうかを考える場合、あなたは必要ありません(本当にそれらを必要とする人々は、彼らがそれらを必要とする理由を正確に知っており、理由を説明する必要はありません)。

〜Pythonの達人Tim Peters



メタクラスの主な用途は、APIを作成することです。 典型的な例はDjango ORMです。



次のような記述ができます。

  class Person(models.Model): name = models.CharField(max_length=30) age = models.IntegerField()
      
      





ただし、次のコードを実行する場合:

  guy = Person(name='bob', age='35') print guy.age
      
      





IntegerField



ではなくint



を取得し、値はデータベースから直接取得できます。



models.Model



定義するため、これが可能にmodels.Model



ます。これは、魔法を実行し、単純な式で定義したPerson



クラスを複雑なデータベースバインディングに変換します。



Djangoは、シンプルなAPIを公開し、メタクラスを使用してAPIからコードを再作成し、すべての作業を静かに行うことで、複雑な外観をシンプルにします。



最後に



最初に、クラスはインスタンス化できるオブジェクトであることを学びました。



実際、クラスもインスタンスです。 メタクラスのインスタンス。

  >>> class Foo(object): pass >>> id(Foo) 142630324
      
      





Pythonのオブジェクトはすべて、クラスのインスタンスまたはメタクラスのインスタンスです。



type



除く。



type



は独自のメタクラスです。 これは純粋なPythonで再現することはできず、実装レベルで少しごまかして行われます。



第二に、メタクラスは複雑です。 単にクラスを変更するためにそれらを使用する必要はありません。 これは、2つの異なる方法で実行できます。



クラスを変更する必要がある99%のケースでは、これら2つを使用することをお勧めします。



しかし、99%の場合、クラスをまったく変更する必要はありません:-)



All Articles