>  기사  >  백엔드 개발  >  Python 객체의 규칙을 바꾸는 흑마술 메타클래스

Python 객체의 규칙을 바꾸는 흑마술 메타클래스

WBOY
WBOY앞으로
2023-04-14 11:43:031233검색

Python 객체의 규칙을 바꾸는 흑마술 메타클래스

오늘 Xiao Ming이 공유하고 싶은 주제는 클래스 정의 변경 아티팩트-메타클래스

제목을 보면 클래스 정의를 변경하면 무슨 소용이 있는지 궁금하실 것입니다. 언제 메타클래스를 사용해야 합니까?

오늘은 간단한 ORM 프레임워크를 설계하고 직렬화 도구인 YAML의 원리를 간략하게 분석하도록 안내하겠습니다.

파이썬 클래스의 신 - type

메타클래스에 대해 말하자면, 객체는 클래스의 인스턴스이고, 클래스는 유형의 인스턴스라는 가장 기본적인 개념을 먼저 이해해야 합니다. 반복:

  1. 객체는 클래스의 인스턴스입니다
  2. 클래스. 은 유형의 인스턴스입니다

객체 지향 프로그래밍 모델에서 클래스는 집의 설계 도면에 해당하고 객체는 이 설계 도면을 기반으로 지은 집입니다.

아래 그림에서 장난감 모델은 클래스를 나타낼 수 있고, 특정 제작된 장난감은 객체를 나타낼 수 있습니다.

Python 객체의 규칙을 바꾸는 흑마술 메타클래스

간단히 클래스는 객체를 생성하기 위한 템플릿입니다.

그리고 Type은 클래스 생성을 위한 템플릿이므로 Type을 통해 원하는 클래스를 생성할 수 있습니다.

예를 들어 Hello 클래스를 정의합니다.

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

Python 인터프리터가 hello 모듈을 로드하면 모듈의 모든 명령문을 순서대로 실행하고 실행 결과는 Hello 클래스 개체를 동적으로 생성하는 것입니다.

type() 함수는 유형이나 변수의 유형을 확인할 수 있을 뿐만 아니라 매개변수를 기반으로 새 유형을 생성할 수도 있습니다. 예를 들어 위 클래스의 정의는 기본적으로 다음과 같습니다.

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

type() 함수. 클래스 객체를 생성하고 이를 차례로 전달합니다. 3개의 매개변수를 입력합니다:

  • 클래스 이름;
  • 상속된 상위 클래스 컬렉션. Python은 다중 상속을 지원합니다. 단일 요소 작성을 잊지 마세요. 튜플의 메소드;
  • 클래스 메소드 이름 및 함수 바인딩, 필드 이름 및 해당 값 여기서는 함수 fn을 메소드 이름 hello에 바인딩합니다.

type() 함수를 통해 생성된 클래스는 클래스를 직접 작성하는 것과 완전히 동일합니다. 왜냐하면 Python 인터프리터가 클래스 정의를 만나면 클래스 정의의 구문을 스캔한 다음 type() 함수를 호출하기 때문입니다. 클래스를 생성합니다.

일반적인 상황에서는 클래스 Xxx...를 사용하여 클래스를 정의하지만, type() 함수를 사용하면 클래스를 동적으로 생성할 수 있습니다. 이는 동적 언어인 Python이 런타임 중에 클래스의 동적 생성을 지원한다는 의미입니다. 이것이 얼마나 강력한지 실감하지 못할 수도 있습니다. 정적 언어 런타임 중에 클래스를 생성하려면 소스 코드 문자열을 구성한 다음 컴파일러를 호출하거나 본질적으로 일부 도구를 사용하여 바이트코드 구현을 생성해야 한다는 점을 알아야 합니다. , 동적 컴파일은 매우 복잡합니다.

메타클래스란 정확히 무엇인가요?

타입과 메타클래스의 관계는 무엇인가요? 메타클래스란 정확히 무엇입니까?

메타클래스는 실제로 유형이거나 유형의 하위 클래스라고 생각합니다. 유형을 상속하고 __call__ 연산자를 오버로드하면 클래스 객체를 생성할 때 몇 가지 수정이 가능합니다.

MyClass 클래스의 경우:

class MyClass():
 pass

는 실제로 다음과 같습니다.

class MyClass(metaclass = type):
 pass

메타클래스를 MyMeta로 설정하면

class MyClass(metaclass = MyMeta):
 pass

MyClass는 더 이상 기본 유형으로 생성되지 않지만 MyMeta의 __call__ 연산자 오버로드를 호출합니다.

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

상속 관계가 있는 클래스의 경우:

class Foo(Bar):
 pass

Python은 다음 작업을 수행합니다.

  • Foo에 __metaclass__ 속성이 있습니까? 그렇다면 Python은 __metaclass__
  • 를 통해 Foo라는 클래스(객체)를 생성합니다. Python이 __metaclass__를 찾지 못하면 계속해서 Bar(상위 클래스)에서 __metaclass__ 속성을 찾아 이전 작업과 동일한 작업을 시도합니다.
  • Python이 상위 클래스에서 __metaclass__를 찾을 수 없으면 모듈 계층 구조에서 __metaclass__를 찾아 동일한 작업을 수행하려고 시도합니다.
  • __metaclass__가 여전히 발견되지 않으면 Python은 내장 유형을 사용하여 이 클래스 객체를 생성합니다.

모듈의 모든 클래스 속성을 대문자로 사용해야 한다고 결정하는 어리석은 예를 상상해 보세요. 여러 가지 방법이 있지만 그 중 하나는 모듈 수준에서 __metaclass__를 설정하는 것입니다.

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'

간단한 ORM 프레임워크 디자인

ORM의 전체 이름은 "Object Relational Mapping"이며, 이는 객체 관계형 매핑입니다. 관계를 매핑하는 것입니다. 데이터베이스의 행을 객체, 즉 클래스에 대응하는 방식으로 SQL 문을 직접 실행하지 않고도 코드를 작성하기가 더 쉽습니다.

이제 ORM 프레임워크의 호출 인터페이스를 디자인합니다. 예를 들어 사용자가 User 클래스를 통해 해당 데이터베이스 테이블 User를 조작하려는 경우 다음과 같은 코드를 작성할 것으로 예상됩니다.

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()

위 인터페이스는 어렵거나 거의 기존 방법으로는 구현하기 어렵지만 메타클래스를 사용하면 비교적 간단합니다. 핵심 아이디어는 메타클래스를 통해 클래스 정의를 수정하고 클래스의 모든 Field 유형 속성을 추가 사전에 저장한 다음 원래 정의에서 삭제하는 것입니다. 객체(id=12345, name='xiaoxiaoming' 등) 생성 시 사용자가 전달한 매개변수는 사전 구현을 모방하거나 dict 클래스를 직접 상속하여 저장할 수 있습니다.

其中,父类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)。

요약

이 공유는 주로 메타클래스의 구현 메커니즘을 간략하게 분석합니다. ORM 프레임워크를 구현하고 YAML 소스 코드를 해석함으로써 여러분은 이미 메타클래스에 대해 잘 이해하고 있다고 믿습니다.

metaclass는 Python의 흑마술 수준 언어 기능으로, 클래스가 생성될 때 클래스의 동작을 변경할 수 있으므로 주의해서 사용해야 합니다.

이 글을 읽고 나면 데코레이터와 메타클래스의 차이점이 무엇이라고 생각하시나요? 나와 논의할 내용을 아래에 메시지로 남겨주세요. 재충전하려면 연속으로 세 번 클릭하는 것을 잊지 마세요!

위 내용은 Python 객체의 규칙을 바꾸는 흑마술 메타클래스의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 51cto.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제