python总结:元类 -- 转载自廖雪峰元类评论-采蘑菇的lucas_688

猿类:它们与人类最接近,被称为是人类的“表兄弟” - - - 来自百度百科。
在这里插入图片描述
好了,进入正题

第一部分 - 数据类型Field类的定义

定义Field类,包含两个属性,分别是字段的名称name与类型column_type

class Field(object):
    # 类Fiedl的构造函数有两个属性: name, column_type
    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)

类StringField继承自类Field,同样有两个属性name与类型column_type

class StringField(Field):
    # 此处仅属性name是强制属性
    def __init__(self, name):
        # 通过super()函数调用parent类的构造函数
        # 其中name就直接传递给Field, column_type传递固定值'varchar(100)'
        # 所以StringField('username').__dict__ == Field('username', 'varchar(100)').__dict__
        super(StringField, self).__init__(name, 'varchar(100)')

同理类IntegerField继承自类Field,同样有两个属性name与类型column_type

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

第二部分 - 元类ModelMetaclass的定义

class ModelMetaclass(type):
    # 此处说明下__new__和__init__的区别:
    # __new__是用来创造一个类对象的构造函数,而__init__是用来初始化一个实例对象的构造函数
    # 类似于__init__,__new__接收的第一个参数cls(类对象)其实就是相当于__init__的self(实例对象)
    # 在初始化__init__之前,类是通过__new__创建的,所以在__init__前一定有__new__来构造类cls,之后__init__才能初始化对象self
    def __new__(cls, name, bases, attrs):
        # 对于名称为Model的类不做其他操作,直接通过type()函数生成类对象Model
        if name == 'Model':
            return type.__new__(cls, name, bases, attrs)
        print('Found model: %s' % name)
        # 对于名称不是Model的类,如User类,通过下面代码过滤类对象User的属性
        # 因为类属性以dict格式存在attrs中,所以是对dict格式的操作:
        # 第一步,过滤出满足条件的属性
        # 先新建一个空dict,如果对象的属性值是Field类的实例对象(如StringField('username')),则将这些属性放入dict格式的mappings变量中
        mappings = dict()
        for k, v in attrs.items():
            # 下面判断属性的值是否是Field类格式,满足Field类格式形态如下:
            # isinstance(StringField('username'), Field)
            # 或者isinstance(Field('name', StringField('username')), Field)
            # 注意此处是属性的值,不是属性,如:属性name的值为StringField('username')
            if isinstance(v, Field):
                print('Found mapping: %s ==> %s' % (k, v))
                # 将过滤出的dict数据写入mappings变量
                mappings[k] = v
        # 其实上面的这几行可以简化为一行 mappings = {k:v for k, v in attrs.items() if isinstance(v, Field)} 吧?

        # 第二步,把上一步过滤出满足条件的属性从类对象User的属性dict中移除
        for k in mappings.keys():
            attrs.pop(k)

        # 第三步,把dict格式的mappings变量添加到类对象User的属性__mapping__中
        # 即__mapping__成为了类对象User的一个属性,该属性值为dict格式,内容为满足Field类格式的原类对象的属性值
        attrs['__mappings__'] = mappings
        # 到目前为止其实就是把实例对象User的一些属性(满足Field格式)移了个位置
        # 下面再新建一个属性__table__,并且赋值为该类对象的名字,如User
        attrs['__table__'] = name  # 假设表名和类名一致
        # 将修改后的类对象User的属性值返回给type().__new__
        return type.__new__(cls, name, bases, attrs)  # 也就是动态创建对象

其实定义好的元类ModelMetaclass,仍然是调用type.new(cls, name, bases, attrs)函数去构造类对象,可以试下如下代码

不同的是type.new()构造出的类的属性就在__dict__下,但是ModelMetaclass将属性移到__mappings__下了

type.__new__(type, 'Model', (dict, ), {'id': IntegerField('id')}).__dict__
ModelMetaclass('Table1', (object, ), {'name': StringField('username')}).__mappings__

第三部分 - 生成Model的实例对象

核心在于:在运行__init__来生成实例对象前,调用元函数ModelMetaclass来生成类对象,用这个类对象再去生成实例对象。 根据ModelMetaclass代码可以知道,当类名称为Model时,直接返回原始的type.new(cls, name, bases, attrs), 如:type.new(type, ‘Model’, (dict, ), Model(id=12345, name=‘Michael’))

此处对于super()函数的理解还是不清楚

class Model(dict, metaclass=ModelMetaclass):
    # 在运行__init__来生成实例对象前,调用元函数ModelMetaclass来生成类对象,用这个类对象再去生成实例对象
    # 根据ModelMetaclass代码可以知道,当类名称为Model时,直接返回原始的type.__new__(cls, name, bases, attrs)
    # 如:type.__new__(type, 'Model', (dict, ), Model(id=12345, name='Michael'))
    # 接下来定义从类对象生成实例对象的__init__函数
    def __init__(self, **kw):
        # 通过super调用父类初始化函数,__init__()动态函数无需self
        # 此处先调用dict
        # ModelMetaclass('User', (type, ), {'id': IntegerField('id')})
        super(Model, self).__init__(**kw)
    # 重新定义getattr函数,可以匹配dict格式输入的{属性:属性值}
    # 如:Model(id = IntegerField('id')).__getattr__
    def __getattr__(self, key):
        '''
        重载__getattr__, __setattr__方法使子类可以像正常的类使用
        '''
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)
    # 个人觉得可以不定义__setattr__,没什么影响
    def __setattr__(self, key, value):
        self[key] = value
    # 定义save方法,用于动态生成SQL
    def save(self):
        # 定义3个数组
        fields = []
        params = []
        args = []
        # 此处引入了之前ModelMetaclass元类定义的dict格式变量__mappings__
        # 假设__mappings__ = {'name': StringField('username')},则'name'就是k, StringField('username')是v, 'username'是v.name
        for k, v in self.__mappings__.items():
            # 把__mappings__的v.name(即'username')写入fields变量
            fields.append(v.name)
            params.append('?')
            # fields变量('username')对应的数值通过重新定义的__getattr__函数获取
            # __mappings__中的k为{'name': StringField('username')}中的'name'
            args.append(getattr(self, k, None))
        # __table__在之前ModelMetaclass元类定义为类对象的名称,即'User'表
        # 通过'sep'.join(seq)函数(','.join(['id', 'username']))生成表字段和字段值
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

第四部分 - 生成User实例对象

class User(Model):
    # 因为User继承了Model,而Model继承了dict类型    # 所以dict(id=12345, name='Michael')直接转化为{'id': 12345, 'name': 'Michael'}格式    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')
    test = str('abc')
# 使用
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
u.save()

总结User实例对象的创建步骤:

  1. User类的创建1: User类继承了Model类,而Model类由元类ModelMetaclass定义类的生成,所以User类对象首先通过ModelMetaclass的type.new()构造生成

  2. User类的创建2: 在ModelMetaclass的type.new()构造函数中,name和bases已经确认,而属性attr是根据输入的属性内容,由__new__构造出

  3. User实例的创建1:User类继承了Model类,所以User通过Model的构造函数__init__生成属性的时候会把输入的(id=12345, name=‘Michael’)转化为dict格式{‘id’: 12345, ‘name’: ‘Michael’}

  4. User实例的创建2: User实例的生成中要按照元类ModelMetaclass的定义生成,即生成__mappings__和__Table__等属性

作者链接https://www.liaoxuefeng.com/user/1353866849288226

----------------------------分割----------------------------

hellojakes:补充一下楼主的解释,Model类中__init__()方法中的super()函数是用来调用dict类的init函数的!这行代码至关重要:

class Model(dict, metaclass=ModelMetaclass):

括号中的dict说明Model继承自dict类,换句话说,Model类就是Python的字典类!

所以才有了下文的初始化方法:

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

这完全等价于我们创建字典的初始化方法:

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

继续简化,就变成了这样:

u = {'id': 12345,
     'name': 'Michael',
     'email': 'test@orm.org',
     'password': 'my-pwd'}

-----------------分割线-----------------

下面都是我自己写的了啊

优点:元类就是类祖宗的意思,类的创建创建过程是找到类定义class - > 继承的类 - > 元类(更底层),所以如果修改了元类,就从上修改了类的创建过程。

解析:
第一部分:自定义了一个数据结构。
在这里插入图片描述
第二部分:实现了一个元类,这个元类实现的是对创建类的属性进行筛选,筛选出自己定义的数据类型,只要三个划线的,这就是这里元类的作用
在这里插入图片描述
第三部分:实现了一个Model类,这个model写复杂了,加大了对元类的理解难度,其实这个Model就是字典dict的改写,同时增加了一个save函数,这就使得model既有dict功能,也有save函数功能。

  • dict功能:由于User继承了Model,Model继承dict,所以按照继承的传递性,User类是一个字典类的孙子,拥有字典的一切特性。
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
c = dict(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
print(type(u))
print(type(c))
print(c == u)
print(c is u)

输出

<class '__main__.User'>  
<class 'dict'>
True
False  

从结果可以看出啊,这两个都是类,一个是字典类,一个是main下的User类,其实也是字典类。"=="就能说明返回的结果一样,但是is说明这两个不是一个,明白吗?好比一个人和他爷爷,爷爷秃头,孙子也秃头,表现得都是秃头,但不是一个秃头,哈哈,越说越糊涂啦。
在上个图,看下u能够点出来东西,希望能明白。
在这里插入图片描述

  • 另一部分就是绑定一个save()方法,这个save()主要是用留下的属性创建条SQL语句,在Model上绑定就相当于给User绑定,这里就不在解释了吧。
    第四部分:一上来就变换了三个数据格式,用的第一部分定义的的数据结构。然后这些属性会被拿着去创建类,就到了元类里,筛选好之后就会用**type.new**动态创建这个类。

元类三部曲:

  1. 拦截类的创建-----因为所有的类都要通过元类创建
  2. 修改操作-----实现想要的目的
  3. 返回修改好的类-----返回修改的类

所以元类能够在类创建时偷偷的做一些操作,这些偷偷的操作偏底层,我们看不见调用关系,所以也难理解。

这种三不娶的还有一种情况:护士、幼师、银行女 - - - (同样来自百度)。
好吧,其实是这个,@property

class Student(object):
    def __init__(self, birth):
    	self.birth = birth
along = Student("along")
print(along.birth)

# 输出
along

其实这里没有任何语法错误,但是显然逻辑不对,birth的值显然应该是一个一定范围的年份,这就需要在赋值的过程校验参数,,元类是创建类的过程中操作类,所以改写代码如下

class Student(object):

    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        if not isinstance(value, int):
            raise ValueError('birth must be an integer!')
        if value < 0 or value > 2020:
            raise ValueError('birth must between 0 ~ 2020!')
        self._birth = value
 
 
 along = Stuent()
 along.birth = 2000
 print(along.birth)

这就是一个三部曲,通过@property,后面的是元类的三部曲。

  1. 拦截-----找到 @birth.setter,运行到里面 -> 找到ModelMetaclass,运行到里面
  2. 对赋值进行操作------运行 if…else…操作 -> 也一样运行 if…else…,筛选啥的
  3. 设置赋值-----------self._birth = value赋值 -> type.new(cls, name, bases, attrs)动态创建类

这种能看到程序调用流程的操作更容易理解吧,其实元类也一样,只不过程序运行过程是看不到的。
以上,希望我能说明白。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值