不会metaclass你居然敢说自己会Python?

python是个神奇的语言,神奇的地方在于一切皆对象,所以没有对象的你在这里应该可以找到真爱。

什么叫一切皆对象?什么是对象?它和类class有什么关系?

在python里我们定义类的时候往往会

class A(object):
    pass

这个object我们都知道是父类,任何类往上倒几代,祖先都是object。

那我们定义的这些类的类型是什么呢?

print(type(A))
<class 'type'>

类的type是type,哈哈哈。

python里类型的尽头是type,不信你看

a = 1
print(type(a))  # int
print(type(type(a)))  # type

因此有如下说法:

  • type为对象的顶点,所有对象都创建自type。

  • object为类继承的顶点,所有类都继承自object。

我们知道在定义类的时候需要定义__init__方法,用于在实例化的时候进行参数初始化的,但真正的对象创建却不是在这个方法里,而是在__new__里进行定义,因此我们可以进行__new__方法的重构而对对象创建的过程进行干预。

铺垫了半天metaclass到底是干嘛的?既然类是对象,大家想一下我们在定义类(对象创建)的时候能不能干涉一下?这就是metaclass的作用。看个例子

class A(type):
    def __new__(cls, *args, **kwargs):
        print('metaclass new')
        return type.__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print('metaclass init')

class B(metaclass=A):
    def __init__(self):
        print('B init')

print('==start==')
b = B()

输出

metaclass new
metaclass init
==start==
B init

大家注意一下打印的顺序,在还没有实例化b的时候,metaclass就已经执行了,也就是说class B生成的时候(不是实例化)受到了class A的影响。

那这到底有啥用?什么场景会用到?

ORM(Object Relational Mapping)对象关系映射

我们在使用Django框架的时候,不可避免会涉及到数据的管理操作(CRUD),而且需要与数据库进行连接操作。而关系型的数据库需要编写原生的SQL语句,那么如果我们的代码里包含大量的sql语句会严重影响开发效率,且变得难以维护。

  • 应用开发程序员需要耗费很大精力去优化SQL语句
  • 各个数据库之间的SQL差异导致进行数据库迁移时难以适配

因此Django提出了ORM概念,在SQL语句上进行了面向对象的封装,现在我们来实现一下:

# 一、首先定义Field类,它负责保存数据库表的字段名和字段类型
class Field:
    def __init__(self, name, column_type):
        self.name = name
        self.colmun_type = column_type

    def __str__(self):
        return f'<{self.__class__.__name__}:{self.name}>'


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


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


# 二、定义元类,控制Model对象的创建
class ModelMetaClass(type):
    def __new__(cls, name, bases, attrs):
        if name == 'Model':
            return super().__new__(cls, name, bases, attrs)
        mappings = dict()
        for k, v in attrs.items():
            # 保持类属性和列的映射关系到mappings字典
            if isinstance(v, Field):
                print(f'Found mapping:{k}==>{v}')
                mappings[k] = v
        for k in mappings.keys():  # 将类属性移除,是定义的类字段不污染User类属性,只在实例中可以访问这些key
            attrs.pop(k)
        attrs['__table__'] = name.lower()  # 假设表名为类名的小写,创建类时添加一个__table__属性
        attrs['__mappings__'] = mappings  # 保持属性和列的关系映射,创建类时添加一个__mappings__属性
        return super().__new__(cls, name, bases, attrs)


# 三、Model基类
class Model(dict, metaclass=ModelMetaClass):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(f"'Model' object has no attribute '{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 = f"insert into {self.__table__} {','.join(fields)} values ({','.join(params)})"
        print(f'SQL:{sql}')
        print(f'ARGS:{str(args)}')


# 我们想创建类似Django的ORM,只要定义字段就可以实现对数据库表和字段的操作
# 最后、我们使用定义好的ORM接口,使用起来非常简单
class User(Model):
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')


user = User(id=1, name='Job', email='[email protected]', password='pw')
user.save()

输出

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:[1, 'Job', '[email protected]', 'pw']

Model基类通过元类ModelMetaClass控制对象的创建,然后通过定义统一的save接口从而在用户定义数据库和操作的时候会十分容易且人性化,然后我们再将update、delete和search接口实现一下即可。

元类同时实现__getattr____setattr__方法,可以直接引用普通字段

user.id = 1
print(user.id)

思维发散

是不是可以不用metaclass,直接在Model里重构__new__,内容与ModelMetaClass__new__一致,能否达到一样的效果?

不行的,只有metaclass的__new__才能对子类的传参进行修改,其他类是不可以的,因此只能使用metaclass

动态加载

配置文件与类相结合,通过配置文件进行类实例化,不同的配置文件可以进行不同的实例化,这里的yaml.YAMLObject的实现也是使用了metaclass。

import time
import yaml


class Monster(yaml.YAMLObject):
    yaml_tag = '!yaml'

    def __init__(self, name):
        self.name = name


while 1:
    with open('dynamicLoad_demo.yaml', 'r') as f:
        cls = yaml.load(f, Loader=yaml.Loader)
    print(cls.name)
    time.sleep(2)

同时构建配置文件dynamicLoad_demo.yaml

!yaml
name:
  zzr

代码跑起来,过一会修改配置文件中的name字段,可以看到输出同步发生变化。

参考

python中的metaclass

你可能感兴趣的:(开发,python,python)