What are metaclasses in Python? Ask Question

What are metaclasses in Python? Ask Question

What are metaclasses? What are they used for?

ベストアンサー1

Classes as objects

Prior to delving into metaclasses, a solid grasp of Python classes is beneficial. Python holds a particularly distinctive concept of classes, a notion it adopts from the Smalltalk language.

In most languages, classes are just pieces of code that describe how to produce an object. That is somewhat true in Python too:

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

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

But classes are more than that in Python. Classes are objects too.

Yes, objects.

When a Python script runs, every line of code is executed from top to bottom. When the Python interpreter encounters the class keyword, Python creates an object out of the "description" of the class that follows. Thus, the following instruction

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

...creates an object with the name ObjectCreator!

This object (the class) is itself capable of creating objects (called instances).

But still, it's an object. Therefore, like all objects:

  • you can assign it to a variable1
    JustAnotherVariable = ObjectCreator
    
  • you can attach attributes to it
    ObjectCreator.class_attribute = 'foo'
    
  • you can pass it as a function parameter
    print(ObjectCreator)
    

1 Note that merely assigning it to another variable doesn't change the class's __name__, i.e.,

>>> print(JustAnotherVariable)
    <class '__main__.ObjectCreator'>

>>> print(JustAnotherVariable())
    <__main__.ObjectCreator object at 0x8997b4c>

Creating classes dynamically

Since classes are objects, you can create them on the fly, like any object.

First, you can create a class in a function using class:

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar

>>> MyClass = choose_class('foo')

>>> print(MyClass) # the function returns a class, not an instance
    <class '__main__.Foo'>

>>> print(MyClass()) # you can create an object from this class
    <__main__.Foo object at 0x89c6d4c>

But it's not so dynamic, since you still have to write the whole class yourself.

Since classes are objects, they must be generated by something.

When you use the class keyword, Python creates this object automatically. But as with most things in Python, it gives you a way to do it manually.

Remember the function type? The good old function that lets you know what type an object is:

>>> print(type(1))
    <class 'int'>

>>> print(type("1"))
    <class 'str'>

>>> print(type(ObjectCreator))
    <class 'type'>

>>> print(type(ObjectCreator()))
    <class '__main__.ObjectCreator'>

Well, type has also a completely different ability: it can create classes on the fly. type can take the description of a class as parameters, and return a class.

(I know, it's silly that the same function can have two completely different uses according to the parameters you pass to it. It's an issue due to backward compatibility in Python)

type works this way:

type(name, bases, attrs)

Where:

  • name: name of the class
  • bases: tuple of the parent class (for inheritance, can be empty)
  • attrs: dictionary containing attributes names and values

e.g.:

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

can be created manually this way:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
    <class '__main__.MyShinyClass'>

>>> print(MyShinyClass()) # create an instance with the class
    <__main__.MyShinyClass object at 0x8997cec>

You'll notice that we use MyShinyClass as the name of the class and as the variable to hold the class reference. They can be different, but there is no reason to complicate things.

type accepts a dictionary to define the attributes of the class. So:

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

Can be translated to:

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

And used as a normal class:

>>> print(Foo)
    <class '__main__.Foo'>

>>> print(Foo.bar)
    True

>>> f = Foo()
>>> print(f)
    <__main__.Foo object at 0x8a9b84c>

>>> print(f.bar)
    True

And of course, you can inherit from it, so:

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

would be:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
    <class '__main__.FooChild'>

>>> print(FooChild.bar) # bar is inherited from Foo
    True

Eventually, you'll want to add methods to your class. Just define a function with the proper signature and assign it as an attribute.

>>> def echo_bar(self):
...     print(self.bar)

>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})

>>> hasattr(Foo, 'echo_bar')
    False

>>> hasattr(FooChild, 'echo_bar')
    True

>>> my_foo = FooChild()
>>> my_foo.echo_bar()
    True

And you can add even more methods after you dynamically create the class, just like adding methods to a normally created class object.

>>> def echo_bar_more(self):
...     print('yet another method')

>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
    True

You see where we are going: in Python, classes are objects, and you can create a class on the fly, dynamically.

This is what Python does when you use the keyword class, and it does so by using a metaclass.

What are metaclasses (finally)

Metaclasses are the 'stuff' that creates classes.

You define classes in order to create objects, right?

But we learned that Python classes are objects.

Well, metaclasses are what create these objects. They are the classes' classes, you can picture them this way:

MyClass = MetaClass()
my_object = MyClass()

You've seen that type lets you do something like this:

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

It's because the function type is in fact a metaclass. type is the metaclass Python uses to create all classes behind the scenes.

Now you wonder "why the heck is it written in lowercase, and not Type?"

Well, I guess it's a matter of consistency with str, the class that creates strings objects, and int the class that creates integer objects. type is just the class that creates class objects.

You see that by checking the __class__ attribute.

Everything, and I mean everything, is an object in Python. That includes integers, strings, functions and classes. All of them are objects. And all of them have been created from a class:

>>> 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'>

Now, what is the __class__ of any __class__ ?

>>> age.__class__.__class__
    <type 'type'>

>>> name.__class__.__class__
    <type 'type'>

>>> foo.__class__.__class__
    <type 'type'>

>>> b.__class__.__class__
    <type 'type'>

So, a metaclass is just the stuff that creates class objects.

You can call it a 'class factory' if you wish.

type is the built-in metaclass Python uses, but of course, you can create your own metaclass.

The __metaclass__ attribute

In Python 2, you can add a __metaclass__ attribute when you write a class (see next section for the Python 3 syntax):

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

If you do so, Python will use the metaclass to create the class Foo.

Careful, it's tricky.

You write class Foo(object) first, but the class object Foo is not created in memory yet.

Python will look for __metaclass__ in the class definition. If it finds it, it will use it to create the object class Foo. If it doesn't, it will use type to create the class.

Read that several times.

When you do:

class Foo(Bar):
    pass

Python does the following:

Is there a __metaclass__ attribute in Foo?

If yes, create in-memory a class object (I said a class object, stay with me here), with the name Foo by using what is in __metaclass__.

If Python can't find __metaclass__, it will look for a __metaclass__ at the MODULE level, and try to do the same (but only for classes that don't inherit anything, basically old-style classes).

Then if it can't find any __metaclass__ at all, it will use the Bar's (the first parent) own metaclass (which might be the default type) to create the class object.

__metaclass__ここで、属性は継承されず、親のメタクラス ( Bar.__class__) が継承されることに注意してください。作成された(ではなく) 属性Barを使用すると、サブクラスはその動作を継承しません。__metaclass__Bartype()type.__new__()

さて、大きな疑問は、何を入れられるかということです__metaclass__

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

クラスを作成できるものは何ですか? type、またはそれをサブクラス化したり使用したりするものは何ですか?

Python 3 のメタクラス

Python 3 では、メタクラスを設定する構文が変更されました。

class Foo(object, metaclass=something):
    ...

つまり、__metaclass__属性は使用されなくなり、代わりに基本クラスのリスト内のキーワード引数が使用されます。

しかし、メタクラスの動作はほぼ同じ

Python 3 のメタクラスに追加された機能の 1 つは、次のように属性をキーワード引数としてメタクラスに渡すこともできるようになったことです。

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

Python がこれをどのように処理するかについては、以下のセクションをお読みください。

カスタムメタクラス

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

通常、これは現在のコンテキストに一致するクラスを作成する API に対して行います。

モジュール内のすべてのクラスの属性を大文字で記述すると決めたという愚かな例を想像してください。これを行うにはいくつかの方法がありますが、1 つの方法は__metaclass__モジュール レベルで設定することです。

この方法では、このモジュールのすべてのクラスがこのメタクラスを使用して作成され、すべての属性を大文字に変換するようにメタクラスに指示するだけです。

幸いなことに、__metaclass__実際には任意の呼び出し可能オブジェクトにすることができ、正式なクラスである必要はありません (名前に「クラス」が含まれているものはクラスである必要がないのはわかっていますが、考えてみてください... でも役に立ちます)。

それでは、関数を使用した簡単な例から始めましょう。

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attrs):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """
    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attrs = {
        attr if attr.startswith("__") else attr.upper(): v
        for attr, v in future_class_attrs.items()
    }

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attrs)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

確認しよう:

>>> hasattr(Foo, 'bar')
    False

>>> hasattr(Foo, 'BAR')
    True

>>> Foo.BAR
    'bip'

さて、メタクラスに実際のクラスを使用して、まったく同じことを実行してみましょう。

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(
        upperattr_metaclass,
        future_class_name,
        future_class_parents,
        future_class_attrs
    ):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in future_class_attrs.items()
        }
        return type(future_class_name, future_class_parents, uppercase_attrs)

では、上記を書き直してみましょう。ただし、変数名の意味がわかったので、より短く現実的な名前にします。

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type(clsname, bases, uppercase_attrs)

追加の引数に気づいたかもしれませんcls。特別なことは何もありません。常に、定義されているクラスを最初のパラメータとして受け取ります。インスタンスを最初のパラメータとして受け取る通常のメソッドや、クラス メソッドの定義クラスと__new__同じです。self

しかし、これは適切な OOP ではありません。直接呼び出しておりtype、親の を​​オーバーライドしたり呼び出したりしていません__new__。代わりに次のようにします。

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type.__new__(cls, clsname, bases, uppercase_attrs)

を使用するとさらにわかりやすくなりsuper、継承が容易になります (メタクラスから継承したメタクラス、さらにメタクラスから継承したメタクラスを持つことができるため)。

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }

        # Python 2 requires passing arguments to super:
        return super(UpperAttrMetaclass, cls).__new__(
            cls, clsname, bases, uppercase_attrs)

        # Python 3 can use no-arg super() which infers them:
        return super().__new__(cls, clsname, bases, uppercase_attrs)

ああ、Python 3 では、次のようにキーワード引数を使用してこの呼び出しを行うと、次のようになります。

class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
    ...

これを使用するには、メタクラスで次のように変換します。

class MyMetaclass(type):
    def __new__(cls, clsname, bases, dct, kwargs1=default):
        ...

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

メタクラスを使用するコードが複雑になる理由は、メタクラス自体が原因ではなく、通常はメタクラスを使用して、イントロスペクション、継承の操作、などの変数などに依存した複雑な処理を実行するためです__dict__

確かに、メタクラスはブラックマジックを行うのに特に便利です。つまり、複雑なことです。しかし、それ自体は単純です。

  • クラス作成を傍受する
  • クラスを変更する
  • 変更されたクラスを返す

関数の代わりにメタクラス クラスを使用するのはなぜですか?

__metaclass__任意の呼び出し可能オブジェクトを受け入れることができるので、明らかにより複雑なのに、なぜクラスを使用するのでしょうか?

そうする理由はいくつかあります。

  • 意図は明らかです。読めばUpperAttrMetaclass(type)、次に何が起こるかが分かります。
  • OOP を使用できます。メタクラスはメタクラスから継承し、親メソッドをオーバーライドできます。メタクラスはメタクラスを使用することもできます。
  • メタクラス関数ではなく、メタクラスクラスを指定した場合、クラスのサブクラスはそのメタクラスのインスタンスになります。
  • コードをより適切に構造化できます。上記の例のように些細なことにメタクラスを使用することはありません。通常は複雑な場合に使用します。複数のメソッドを作成し、それらを 1 つのクラスにグループ化する機能は、コードを読みやすくするのに非常に便利です。
  • __new____init__を接続できます__call__。これにより、さまざまなことが可能になります。通常はすべて で実行できますが__new__、 を使用する方が快適な人もいます__init__
  • これらはメタクラスと呼ばれます。何か意味があるに違いありません。

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

さて、大きな疑問です。なぜ、わかりにくいエラーが発生しやすい機能を使用するのでしょうか?

まあ、通常はそうはしません:

メタクラスは、99% のユーザーが気にする必要のない、より深い魔法です。メタクラスが必要かどうか疑問に思う場合、必要ありません (実際にメタクラスが必要な人は、メタクラスが必要であることを確実に知っており、その理由の説明は必要ありません)。

Python の達人 Tim Peters

メタクラスの主な使用例は API の作成です。その典型的な例は Django ORM です。次のようなものを定義できます。

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

しかし、次のようにすると:

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

オブジェクトは返されませんIntegerField。 が返されint、データベースから直接取得することもできます。

これが可能なのは、models.Modelを定義し、単純なステートメントで定義したものをデータベース フィールドへの複雑なフックに__metaclass__変換する魔法を使用しているためです。Person

Django は、シンプルな API を公開し、メタクラスを使用して、この API からコードを再作成し、舞台裏で実際の作業を実行することで、複雑なものをシンプルに見せます。

最後の言葉

まず、クラスはインスタンスを作成できるオブジェクトであることがわかります。

実は、クラス自体がメタクラスのインスタンスなのです。

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

Python ではすべてがオブジェクトであり、それらはすべてクラスのインスタンスかメタクラスのインスタンスのいずれかです。

を除いてtype

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

第二に、メタクラスは複雑です。非常に単純なクラスの変更にはメタクラスを使用しない方がよいでしょう。クラスを変更するには、次の 2 つの方法があります。

クラス変更が必要な場合、99% はこれらを使用する方がよいでしょう。

しかし、98% の場合、クラスの変更はまったく必要ありません。

おすすめ記事