カスタムメタクラス

これまでのところ、メタクラスが何であるかはすでにわかっています。したがって、メタクラスの用途が最初から最後までまだわかりません。メタクラスについて学びました。その使用法を理解する前に、まずメタクラスをカスタマイズする方法を理解しましょう。カスタマイズ方法を理解することによってのみ、その機能をより深く理解できるからです。

最初に __metaclass__ 属性について理解しましょう

metaclass (直訳するとメタクラス) を簡単に説明します。

class を定義すると、次のことができます。このクラスに基づいてインスタンスを作成するため、最初にクラスを定義してからインスタンスを作成します。

しかし、クラスを作成したい場合はどうすればよいでしょうか?次に、メタクラスに基づいてクラスを作成する必要があるため、最初にメタクラスを定義してからクラスを作成します。

接続は次のとおりです。最初にメタクラスを定義し、次にクラスを作成し、最後にインスタンスを作成します。

つまり、メタクラスを使用すると、クラスを作成したり、クラスを変更したりできます。つまり、クラスはメタクラスによって作成される「インスタンス」と考えることができます。

class MyObject(object):
    __metaclass__ = something…
[…]

このように書くと、Python はメタクラスを使用してクラス MyObject を作成します。 class MyObject(object) を作成したとき、クラス オブジェクト MyObject はまだメモリ内に作成されていません。 Python はクラス定義で __metaclass__ 属性を探します。それが見つかった場合、Python はそれを使用してクラス MyObject を作成します。見つからない場合は、組み込みの型関数を使用してクラスを作成します。それでも理解できない場合は、以下のフローチャートを見てください。

c7dd72cd3c802993425a3b6516a97e2.png

別の例:

class Foo(Bar):
    pass

その判断プロセスは何ですか?

まず、Foo に __metaclass__ 属性があるかどうかを確認します。存在する場合、Python は __metaclass__ を通じてメモリ内に Foo という名前のクラス オブジェクトを作成します (これはクラス オブジェクトであることに注意してください)。 Python が __metaclass__ を見つけられない場合、引き続き Bar (親クラス) で __metaclass__ 属性を探し、以前と同じ操作を実行しようとします。 Python が親クラスで __metaclass__ を見つけられない場合、モジュール階層で __metaclass__ を探し、同じことを試みます。それでも __metaclass__ が見つからない場合、Python は組み込み型を使用してクラス オブジェクトを作成します。

実際、 __metaclass__ はクラスの動作を定義します。クラスがインスタンスの動作を定義する方法と同様に、メタクラスはクラスの動作を定義します。クラスはメタクラスのインスタンスであると言えます。

これで、__metaclass__ 属性の基本は理解できましたが、この属性の使用方法や、この属性に何を入力できるかについてはまだ説明していませんでした。

答えは、クラスを作成できるということです。では、クラスを作成するには何を使用できるのでしょうか? type、または type またはサブクラス type を使用するもの。

メタクラスの主な目的は、クラスの作成時に自動的にクラスを変更することです。通常、API に対して次のようなことを行い、現在のコンテキストに適合するクラスを作成します。モジュール内のすべてのクラス属性を大文字にする必要があるという愚かな例を想像してください。これを行うにはいくつかの方法がありますが、その 1 つはモジュール レベルで __metaclass__ を設定することです。このメソッドを使用すると、このモジュール内のすべてのクラスがこのメタクラスを通じて作成されます。すべての属性を大文字に変更するようにメタクラスに指示するだけで、すべて問題なく実行されます。

幸いなことに、__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')
# 输出: False
print hasattr(Foo, 'BAR')
# 输出:True
 
f = Foo()
print f.BAR
# 输出:'bip'
用 class 当做元类的做法:
# 请记住,'type'实际上是一个类,就像'str'和'int'一样
# 所以,你可以从type继承
class UpperAttrMetaClass(type):
    # __new__ 是在__init__之前被调用的特殊方法
    # __new__是用来创建对象并返回之的方法
    # 而__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__方法
        # 这就是基本的OOP编程,没什么魔法
        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)

スーパー メソッドを使用すると、継承が容易になります (はい、メタクラスを持ち、メタクラスから継承し、タイプから継承)

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)

通常、私たちはメタクラスを使用して、イントロスペクションに依存したり、継承を制御したりするなど、あいまいなことを実行します。実際、メタクラスを使用して「暗い魔法」を実行し、複雑なものを作成するのは特に便利です。しかし、メタクラス自体に関して言えば、実際には非常に単純です。

インターセプト クラスの作成

クラスの変更

変更されたクラスに戻る

学び続ける
  • おすすめコース
  • コースウェアのダウンロード
現時点ではコースウェアはダウンロードできません。現在スタッフが整理中です。今後もこのコースにもっと注目してください〜