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%の場合、クラスをまったく変更する必要はありません:-)

Source: https://habr.com/ru/post/J145835/


All Articles