Benutzerdefinie...LOGIN

Benutzerdefinierte Metaklasse

Jetzt wissen wir bereits, was eine Metaklasse ist. Wir wissen also vom Anfang bis zum Ende immer noch nicht, was der Nutzen von Metaklassen ist. Habe gerade etwas über Metaklassen gelernt. Bevor wir seine Verwendung verstehen, wollen wir zunächst verstehen, wie Metaklassen angepasst werden. Denn nur wenn wir verstehen, wie wir es anpassen können, können wir seine Funktion besser verstehen.

Lassen Sie uns zunächst das __metaclass__-Attribut

Metaklasse verstehen, wörtlich übersetzt als Metaklasse. Die einfache Erklärung lautet:

Nachdem wir die Klasse definiert haben Sie können eine Instanz basierend auf dieser Klasse erstellen. Definieren Sie also zuerst die Klasse und erstellen Sie dann die Instanz.

Aber was ist, wenn wir eine Klasse erstellen möchten? Dann müssen Sie eine Klasse basierend auf der Metaklasse erstellen. Definieren Sie also zuerst die Metaklasse und erstellen Sie dann die Klasse.

Der Zusammenhang ist: Definieren Sie zuerst die Metaklasse, dann können Sie die Klasse erstellen und schließlich die Instanz erstellen.

Mit Metaclass können Sie also Klassen erstellen oder ändern. Mit anderen Worten: Sie können sich Klassen als „Instanzen“ vorstellen, die von Metaklassen erstellt werden.

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

Wenn Sie es so schreiben, verwendet Python die Metaklasse, um die Klasse MyObject zu erstellen. Wenn Sie die Klasse MyObject(object) schreiben, wurde das Klassenobjekt MyObject noch nicht im Speicher erstellt. Python sucht in der Klassendefinition nach dem Attribut __metaclass__. Wenn es gefunden wird, erstellt Python damit die Klasse MyObject. Wenn es nicht gefunden wird, verwendet es die integrierte Typfunktion, um die Klasse zu erstellen. Wenn Sie es immer noch nicht verstehen, werfen Sie einen Blick auf das Flussdiagramm unten:

c7dd72cd3c802993425a3b6516a97e2.png

Ein weiteres Beispiel:

class Foo(Bar):
    pass

Wie ist der Beurteilungsprozess?

Stellen Sie zunächst fest, ob in Foo ein __metaclass__-Attribut vorhanden ist. Wenn dies der Fall ist, erstellt Python über __metaclass__ ein Klassenobjekt namens Foo im Speicher (beachten Sie, dass es sich um ein Klassenobjekt handelt). Wenn Python __metaclass__ nicht findet, sucht es weiterhin nach dem Attribut __metaclass__ in Bar (der übergeordneten Klasse) und versucht, denselben Vorgang wie zuvor auszuführen. Wenn Python __metaclass__ in keiner übergeordneten Klasse finden kann, sucht es in der Modulhierarchie nach __metaclass__ und versucht, dasselbe zu tun. Wenn __metaclass__ immer noch nicht gefunden wird, verwendet Python den integrierten Typ, um das Klassenobjekt zu erstellen.

Tatsächlich definiert __metaclass__ das Verhalten der Klasse. Ähnlich wie eine Klasse das Verhalten einer Instanz definiert, definiert eine Metaklasse das Verhalten einer Klasse. Man kann sagen, dass die Klasse eine Instanz einer Metaklasse ist.

Jetzt verstehen wir im Grunde das Attribut __metaclass__, aber wir haben noch nicht darüber gesprochen, wie dieses Attribut verwendet wird oder was in dieses Attribut eingefügt werden kann.

Die Antwort lautet: Sie können eine Klasse erstellen. Was kann also zum Erstellen einer Klasse verwendet werden? Typ oder alles, was Typ oder Unterklassentyp verwendet.

Der Hauptzweck von Metaklassen besteht darin, Klassen automatisch zu ändern, wenn sie erstellt werden. Normalerweise würden Sie so etwas für eine API tun, bei der Sie Klassen erstellen möchten, die zum aktuellen Kontext passen. Stellen Sie sich ein albernes Beispiel vor, in dem Sie entscheiden, dass alle Klassenattribute in Ihrem Modul in Großbuchstaben geschrieben werden sollen. Es gibt mehrere Möglichkeiten, dies zu tun, aber eine davon besteht darin, __metaclass__ auf Modulebene festzulegen. Mit dieser Methode werden alle Klassen in diesem Modul über diese Metaklasse erstellt. Wir müssen der Metaklasse nur mitteilen, dass sie alle Attribute in Großbuchstaben ändern soll, und alles wird gut.

Glücklicherweise kann __metaclass__ tatsächlich beliebig aufgerufen werden, es muss keine formale Klasse sein. Beginnen wir also mit einer einfachen Funktion als Beispiel.

# 元类会自动将你通常传给‘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)

Dieser Ansatz ist jedoch kein OOP. Wir haben den Typ direkt aufgerufen und die Methode __new__ der übergeordneten Klasse nicht überschrieben. Machen wir es jetzt so:

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)

Sie haben vielleicht bemerkt, dass es einen zusätzlichen Parameter „upperattr_metaclass“ gibt, der nichts Besonderes ist. Das erste Argument einer Klassenmethode stellt immer die aktuelle Instanz dar, genau wie das Argument self in einer gewöhnlichen Klassenmethode. Der Übersichtlichkeit halber habe ich die Namen hier natürlich länger gemacht. Aber genau wie „self“ haben alle Parameter ihre traditionellen Namen. Im echten Produktionscode würde eine Metaklasse also so aussehen:

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)

Wir können es auch etwas klarer machen, wenn wir die Super-Methode verwenden, die die Vererbung erleichtert (ja, Sie können Metaklassen haben, von Metaklassen erben, vom Typ erben)

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)

Normalerweise verwenden wir Metaklassen, um einige obskure Dinge zu tun, uns auf Selbstbeobachtung zu verlassen, die Vererbung zu kontrollieren usw. Tatsächlich ist es besonders nützlich, Metaklassen zu verwenden, um „dunkle Magie“ zu wirken und so komplexe Dinge zu erschaffen. Aber was die Metaklassen selbst betrifft, sind sie eigentlich sehr einfach:

Erstellung einer Abfangklasse

Änderung der Klasse

Rückgabe der geänderten Klasse

nächsten Abschnitt
KapitelKursunterlagen