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 classbases
: 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__
Bar
type()
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% の場合、クラスの変更はまったく必要ありません。