Heim  >  Artikel  >  Backend-Entwicklung  >  Die schwarze magische Metaklasse, die die Regeln von Python-Objekten ändert

Die schwarze magische Metaklasse, die die Regeln von Python-Objekten ändert

WBOY
WBOYnach vorne
2023-04-14 11:43:031253Durchsuche

Die schwarze magische Metaklasse, die die Regeln von Python-Objekten ändert

Das Thema, das Xiao Ming heute teilen möchte, ist: das Artefakt der Änderung der Klassendefinition – Metaklasse

Wenn Sie den Titel sehen, fragen Sie sich vielleicht, welchen Nutzen es hat, die Definition einer Klasse zu ändern? Wann müssen Sie Metaklasse verwenden?

Heute werde ich Sie anleiten, ein einfaches ORM-Framework zu entwerfen und kurz die Prinzipien von YAML, einem Serialisierungstool, zu analysieren.

Der Gott der Python-Klassen – Typ

Apropos Metaklasse: Wir müssen zunächst das grundlegendste Konzept verstehen, dass Objekte Instanzen von Klassen und Klassen Instanzen von Typen sind. Wiederholen Sie:

  1. Objekte sind Instanzen von Klassen
  2. Klasse ist eine Instanz des Typs

Im objektorientierten Programmiermodell entspricht eine Klasse der Entwurfszeichnung eines Hauses, und das Objekt ist das Haus, das auf der Grundlage dieser Entwurfszeichnung gebaut wurde.

Im Bild unten kann das Spielzeugmodell eine Klasse darstellen, und das spezifisch hergestellte Spielzeug kann ein Objekt darstellen:

Die schwarze magische Metaklasse, die die Regeln von Python-Objekten ändert

Kurz gesagt, eine Klasse ist eine Vorlage zum Erstellen von Objekten.

Und Typ ist eine Vorlage zum Erstellen von Klassen, sodass wir über Typ die gewünschten Klassen erstellen können.

Definieren Sie beispielsweise eine Hello-Klasse:

class Hello(object):
def hello(self, name='world'):
 print('Hello, %s.' % name)

Wenn der Python-Interpreter das Hello-Modul lädt, führt er alle Anweisungen des Moduls nacheinander aus und das Ausführungsergebnis besteht darin, dynamisch ein Hello-Klassenobjekt zu erstellen. Die Funktion

type() kann nicht nur den Typ eines Typs oder einer Variablen überprüfen, sondern auch einen neuen Typ basierend auf den Parametern erstellen. Die Definition der obigen Klasse lautet beispielsweise im Wesentlichen:

def hello(self, name='world'):
print('Hello, %s.' % name)
Hello = type('Hello', (object,), dict(hello=hello))

Die Funktion type() Erstellt ein Klassenobjekt und übergibt es der Reihe nach. Geben Sie 3 Parameter ein:

  • geerbte übergeordnete Klassensammlung. Beachten Sie, dass Python die Mehrfachvererbung unterstützt Methode des Tupels;
  • Klassenmethodenname und Funktionsbindung, Feldnamen und entsprechende Werte Hier binden wir die Funktion fn an den Methodennamen hallo.
  • Die mit der Funktion type() erstellte Klasse ist genau das Gleiche wie das direkte Schreiben der Klasse, denn wenn der Python-Interpreter auf die Klassendefinition stößt, scannt er lediglich die Syntax der Klassendefinition und ruft dann die Funktion type() auf um die Klasse zu erstellen.

Unter normalen Umständen verwenden wir definitiv die Klasse Xxx..., um Klassen zu definieren, aber die Funktion type() ermöglicht uns die dynamische Erstellung von Klassen, was bedeutet, dass Python, eine dynamische Sprache, die dynamische Erstellung von Klassen zur Laufzeit unterstützt. Sie spüren möglicherweise nicht, wie leistungsfähig dies ist. Sie müssen wissen, dass Sie im Wesentlichen eine Quellcodezeichenfolge erstellen und dann den Compiler aufrufen oder einige Tools verwenden müssen, um eine Klasse zu erstellen Es ist eine dynamische Kompilierung, die sehr komplex ist.

Was genau ist Metaklasse?

Welche Beziehung besteht zwischen Typ und Metaklasse? Was genau ist eine Metaklasse?

Ich denke, dass die Metaklasse tatsächlich ein Typ oder eine Unterklasse des Typs ist, indem Sie den Typ erben und den __call__-Operator überladen, können Sie beim Erstellen eines Klassenobjekts einige Änderungen vornehmen.

Für die Klasse MyClass:

class MyClass():
 pass

ist tatsächlich äquivalent zu:

class MyClass(metaclass = type):
 pass

Sobald wir ihre Metaklasse auf MyMeta gesetzt haben:

class MyClass(metaclass = MyMeta):
 pass

MyClass wird nicht mehr vom nativen Typ erstellt, sondern ruft die Operatorüberladung __call__ von MyMeta auf.

class = type(classname, superclasses, attributedict)
## 变为了
class = MyMeta(classname, superclasses, attributedict)

Für Klassen mit Vererbungsbeziehungen:

class Foo(Bar):
 pass

Python führt die folgenden Operationen aus:

Gibt es ein __metaclass__-Attribut in Foo? Wenn dies der Fall ist, erstellt Python über __metaclass__ eine Klasse (ein Objekt) mit dem Namen Foo.
  • Wenn Python __metaclass__ nicht findet, sucht es weiterhin nach dem Attribut __metaclass__ in Bar (übergeordnete Klasse) und versucht, das Gleiche wie vor dem Vorgang zu tun.
  • Wenn Python __metaclass__ in keiner übergeordneten Klasse finden kann, sucht es in der Modulhierarchie nach __metaclass__ und versucht, denselben Vorgang auszuführen.
  • Wenn __metaclass__ immer noch nicht gefunden wird, verwendet Python den integrierten Typ, um dieses Klassenobjekt zu erstellen.
  • 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:
class UpperAttrMetaClass(type):
## __new__ 是在__init__之前被调用的特殊方法
## __new__是用来创建对象并返回之的方法
## 而__init__只是用来将传入的参数初始化给对象
## 你很少用到__new__,除非你希望能够控制对象的创建
## 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
## 如果你希望的话,你也可以在__init__中做些事情
## 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
def __new__(cls, future_class_name, future_class_parents, future_class_attr):
##遍历属性字典,把不是__开头的属性名字变为大写
newAttr = {}
for name,value in future_class_attr.items():
if not name.startswith("__"):
newAttr[name.upper()] = value
## 方法1:通过'type'来做类对象的创建
## return type(future_class_name, future_class_parents, newAttr)
## 方法2:复用type.__new__方法,这就是基本的OOP编程
## return type.__new__(cls, future_class_name, future_class_parents, newAttr)
## 方法3:使用super方法
return super(UpperAttrMetaClass, cls).__new__(cls, future_class_name, future_class_parents, newAttr)
class Foo(object, metaclass = UpperAttrMetaClass):
bar = 'bip'
print(hasattr(Foo, 'bar'))
## 输出: False
print(hasattr(Foo, 'BAR'))
## 输出:True
f = Foo()
print(f.BAR)
## 输出:'bip'

Entwurf eines einfachen ORM-Frameworks

ORMs vollständiger Name ist „Object Relational Mapping“, also objektrelationale Zuordnung besteht darin, Beziehungen abzubilden. Eine Zeile der Datenbank wird einem Objekt zugeordnet, dh eine Klasse entspricht einer Tabelle. Auf diese Weise ist es einfacher, Code zu schreiben, ohne SQL-Anweisungen direkt auszuführen.

Entwerfen Sie nun die aufrufende Schnittstelle des ORM-Frameworks. Wenn ein Benutzer beispielsweise die entsprechende Datenbanktabelle User über die User-Klasse bedienen möchte, erwarten wir von ihm, dass er Code wie diesen schreibt:

class User(Model):
## 定义类的属性到列的映射:
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')
## 创建一个实例:
u = User(id=12345, name='xiaoxiaoming', email='test@orm.org', password='my-pwd')
## 保存到数据库:
u.save()

Die obige Schnittstelle ist schwierig oder fast Mit herkömmlichen Methoden schwer zu implementieren, mit Metaklassen jedoch relativ einfach. Die Kernidee besteht darin, die Definition der Klasse über die Metaklasse zu ändern, alle Feldtypattribute der Klasse in einem zusätzlichen Wörterbuch zu speichern und sie dann aus der ursprünglichen Definition zu löschen. Die vom Benutzer beim Erstellen eines Objekts übergebenen Parameter (id=12345, name='xiaoxiaoming' usw.) können durch Nachahmen der Wörterbuchimplementierung oder direktes Erben der dict-Klasse gespeichert werden.

其中,父类Model和属性类型StringField、IntegerField是由ORM框架提供的,剩下的魔术方法比如save()全部由metaclass自动完成。虽然metaclass的编写会比较复杂,但ORM的使用者用起来却异常简单。

首先定义Field类,它负责保存数据库表的字段名和字段类型:

class Field(object):
def __init__(self, name, column_type):
self.name = name
self.column_type = column_type
def __str__(self):
return '<%s:%s>' % (self.__class__.__name__, self.name)

在Field的基础上,进一步定义各种类型的Field,比如StringField,IntegerField等等:

class StringField(Field):
def __init__(self, name):
super(StringField, self).__init__(name, 'varchar(100)')
class IntegerField(Field):
def __init__(self, name):
super(IntegerField, self).__init__(name, 'bigint')

下一步,编写ModelMetaclass:

class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
if name == 'Model':
return type.__new__(cls, name, bases, attrs)
print('Found model: %s' % name)
mappings = dict()
for k, v in attrs.items():
if isinstance(v, Field):
print('Found mapping: %s ==> %s' % (k, v))
mappings[k] = v
for k in mappings.keys():
attrs.pop(k)
attrs['__mappings__'] = mappings## 保存属性和列的映射关系
attrs.setdefault('__table__', name) ## 当未定义__table__属性时,表名直接使用类名
return type.__new__(cls, name, bases, attrs)

以及基类Model:

class Model(dict, metaclass=ModelMetaclass):
def __init__(self, **kw):
super(Model, self).__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Model' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
def save(self):
fields = []
params = []
args = []
for k, v in self.__mappings__.items():
fields.append(v.name)
params.append('?')
args.append(getattr(self, k, None))
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
print('SQL: %s' % sql)
print('ARGS: %s' % str(args))

在ModelMetaclass中,一共做了几件事情:

  1. 在当前类(比如User)中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到一个__mappings__的dict中,同时从类属性中删除该Field属性(避免实例的属性遮盖类的同名属性);
  2. 当类中未定义__table__字段时,直接将类名保存到__table__字段中作为表名。

在Model类中,就可以定义各种操作数据库的方法,比如save(),delete(),find(),update等等。

我们实现了save()方法,把一个实例保存到数据库中。因为有表名,属性到字段的映射和属性值的集合,就可以构造出INSERT语句。

测试:

u = User(id=12345, name='xiaoxiaoming', email='test@orm.org', password='my-pwd')
u.save()

输出如下:

Found model: User
Found mapping: id ==> <IntegerField:id>
Found mapping: name ==> <StringField:username>
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
SQL: insert into User (id,username,email,password) values (?,?,?,?)
ARGS: [12345, 'xiaoxiaoming', 'test@orm.org', 'my-pwd']

测试2:

class Blog(Model):
__table__ = 'blogs'
id = IntegerField('id')
user_id = StringField('user_id')
user_name = StringField('user_name')
name = StringField('user_name')
summary = StringField('summary')
content = StringField('content')
b = Blog(id=12345, user_, user_name='xxm', name='orm框架的基本运行机制', summary="简单讲述一下orm框架的基本运行机制",
 content="此处省略一万字...")
b.save()

输出:

Found model: Blog
Found mapping: id ==> <IntegerField:id>
Found mapping: user_id ==> <StringField:user_id>
Found mapping: user_name ==> <StringField:user_name>
Found mapping: name ==> <StringField:user_name>
Found mapping: summary ==> <StringField:summary>
Found mapping: content ==> <StringField:content>
SQL: insert into blogs (id,user_id,user_name,user_name,summary,content) values (?,?,?,?,?,?)
ARGS: [12345, 'user_id1', 'xxm', 'orm框架的基本运行机制', '简单讲述一下orm框架的基本运行机制', '此处省略一万字...']

可以看到,save()方法已经打印出了可执行的SQL语句,以及参数列表,只需要真正连接到数据库,执行该SQL语句,就可以完成真正的功能。

YAML序列化工具的实现原理浅析

YAML是一个家喻户晓的 Python 工具,可以方便地序列化 / 逆序列化结构数据。

官方文档:https://pyyaml.org/wiki/PyYAMLDocumentation

安装:

pip install pyyaml

YAMLObject 的任意子类支持序列化和反序列化(serialization & deserialization)。比如说下面这段代码:

import yaml
class Monster(yaml.YAMLObject):
yaml_tag = '!Monster'
def __init__(self, name, hp, ac, attacks):
self.name = name
self.hp = hp
self.ac = ac
self.attacks = attacks
def __repr__(self):
return f"{self.__class__.__name__}(name={self.name}, hp={self.hp}, ac={self.ac}, attacks={self.attacks})"
monster1 = yaml.load("""
--- !Monster
name: Cave spider
hp: [2,6]
ac: 16
attacks: [BITE, HURT]
""")
print(monster1, type(monster1))
monster2 = Monster(name='Cave lizard', hp=[3, 6], ac=16, attacks=['BITE', 'HURT'])
print(yaml.dump(monster2))

运行结果:

Monster(name=Cave spider, hp=[2, 6], ac=16, attacks=['BITE', 'HURT']) <class '__main__.Monster'>
!Monster
ac: 16
attacks: [BITE, HURT]
hp: [3, 6]
name: Cave lizard

这里面调用统一的 yaml.load(),就能把任意一个 yaml 序列载入成一个 Python Object;而调用统一的 yaml.dump(),就能把一个 YAMLObject 子类序列化。

对于 load() 和 dump() 的使用者来说,他们完全不需要提前知道任何类型信息,这让超动态配置编程成了可能。比方说,在一个智能语音助手的大型项目中,我们有 1 万个语音对话场景,每一个场景都是不同团队开发的。作为智能语音助手的核心团队成员,我不可能去了解每个子场景的实现细节。

在动态配置实验不同场景时,经常是今天我要实验场景 A 和 B 的配置,明天实验 B 和 C 的配置,光配置文件就有几万行量级,工作量不可谓不小。而应用这样的动态配置理念,就可以让引擎根据配置文件,动态加载所需要的 Python 类。

对于 YAML 的使用者也很方便,只要简单地继承 yaml.YAMLObject,就能让你的 Python Object 具有序列化和逆序列化能力。

据说即使是在大厂 Google 的 Python 开发者,发现能深入解释 YAML 这种设计模式优点的人,大概只有 10%。而能知道类似 YAML 的这种动态序列化 / 逆序列化功能正是用 metaclass 实现的人,可能只有 1% 了。而能够将YAML 怎样用 metaclass 实现动态序列化 / 逆序列化功能讲出一二的可能只有 0.1%了。

对于YAMLObject 的 load和dump() 功能,简单来说,我们需要一个全局的注册器,让 YAML 知道,序列化文本中的 !Monster 需要载入成 Monster 这个 Python 类型,Monster 这个 Python 类型需要被序列化为!Monster 标签开头的字符串。

一个很自然的想法就是,那我们建立一个全局变量叫 registry,把所有需要逆序列化的 YAMLObject,都注册进去。比如下面这样:

registry = {}
def add_constructor(target_class):
registry[target_class.yaml_tag] = target_class

然后,在 Monster 类定义后面加上下面这行代码:

add_constructor(Monster)

这样的缺点很明显,对于 YAML 的使用者来说,每一个 YAML 的可逆序列化的类 Foo 定义后,都需要加上一句话add_constructor(Foo)。这无疑给开发者增加了麻烦,也更容易出错,毕竟开发者很容易忘了这一点。

更优雅的实现方式自然是通过metaclass 解决了这个问题,YAML 的源码正是这样实现的:

class YAMLObjectMetaclass(type):
def __init__(cls, name, bases, kwds):
super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
cls.yaml_dumper.add_representer(cls, cls.to_yaml)
## 省略其余定义
class YAMLObject(metaclass=YAMLObjectMetaclass):
yaml_loader = Loader
yaml_dumper = Dumper
## 省略其余定义

可以看到,YAMLObject 把 metaclass 声明成了 YAMLObjectMetaclass,YAMLObjectMetaclass则会改变YAMLObject类和其子类的定义,就是下面这行代码将YAMLObject 的子类加入到了yaml的两个全局注册表中:

cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
cls.yaml_dumper.add_representer(cls, cls.to_yaml)

YAML 应用 metaclass,拦截了所有 YAMLObject 子类的定义。也就是说,在你定义任何 YAMLObject 子类时,Python 会强行插入运行上面这段代码,把我们之前想要的add_constructor(Foo)和add_representer(Foo)给自动加上。所以 YAML 的使用者,无需自己去手写add_constructor(Foo)和add_representer(Foo)。

Zusammenfassung

Diese Freigabe analysiert hauptsächlich kurz den Implementierungsmechanismus der Metaklasse. Ich glaube, dass Sie durch die Implementierung eines ORM-Frameworks und die Interpretation des YAML-Quellcodes bereits ein gutes Verständnis der Metaklasse haben.

metaclass ist eine Sprachfunktion der schwarzen Magie von Python. Sie kann das Verhalten einer Klasse ändern, wenn sie erstellt wird. Diese leistungsstarke Funktion muss mit Vorsicht verwendet werden.

Was ist Ihrer Meinung nach nach dem Lesen dieses Artikels der Unterschied zwischen Dekoratoren und Metaklassen? Gerne können Sie unten eine Nachricht hinterlassen, um diese mit mir zu besprechen. Denken Sie daran, zum Nachfüllen dreimal hintereinander zu klicken!

Das obige ist der detaillierte Inhalt vonDie schwarze magische Metaklasse, die die Regeln von Python-Objekten ändert. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:51cto.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen