今回は、メタクラスとは何か、それらをどのように、どこで、なぜ使用するのか、そしてなぜこれを行う価値がないのかについて説明します。
オブジェクトとしてのクラス
メタクラスを学習する前に、クラスをよく理解する必要があります。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__
は呼び出されたオブジェクトを受け入れるため、明らかに複雑なクラスを突然使用するのはなぜですか?
これにはいくつかの理由があります。
- 目的は明確です。
UpperAttrMetaclass(type)
を見ると、次に来るものがすぐにわかります。 - OOPを使用できます。 メタクラスはメタクラスから継承でき、親メソッドをオーバーロードします。
- より良い構造化コード。 上記の例のような単純なことにはメタクラスを使用しません。 通常、これは複雑なものです。 複数のメソッドを作成して1つのクラスにグループ化する機能は、コードを読みやすくするために非常に役立ちます。
-
__new__
、__new__
および__call__
使用できます。 もちろん、通常は__new__
ですべてを行うことができますが、一部は__init__
を使用する方が快適です - それらはメタクラスと呼ばれています。 それは何かを意味するに違いありません!
なぜメタクラスを使用するのですか?
最後に、主な質問です。 なぜ誰かがあいまいな(そしてエラーの原因となる)機能を使用するのでしょうか?
まあ、通常は使用しないでください:
メタクラスは、ユーザーの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%の場合、クラスをまったく変更する必要はありません:-)