python 元类详解

原文链接:https://cloud.tencent.com/developer/news/397470

1 简介

在我阅读Django框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩;在看python cookbook中关于元类创建单例模式的那一节有些疑惑。因此花了几天时间研究下元类这个概念。通过学习元类,我对python的面向对象有了更加深入的了解。

2 type和object

请下记住下面这两句话,后面再详细解释。

object类是所有新式类的父类。

type是所有类的类。

那么type和object是什么关系呢?object是一个新式类,我们可以通过object.__class__和object.__bases__来获取object所属的类核他的父类。

图1:object的类型

这说明 object类是一个type元类的实例。这与type是所有新式类的类这一说法相符合。

图2:object的继承关系

这说明 object类已经处于继承链条的顶端,是所有类的父类。

图3:type的类型

这说明type自身的类就是type。就是说type元类也就是由type自身创建的。

图4:type类型的基类

type元类的父类是object。

object类是由元类type创建的,但是type类又继承了object类。 type元类的类则是由type元类自身创建的。我们把python中的内置类和用户创建的内纳入其中,我们就可以画出一下关系图

图5:type和object的关系

3 类也是对象

在理解元类之前,你需要先掌握Python中的类。在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立:

图6: 类的描述

但是,Python中的类还远不止如此。类同样也是一种对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象。将在内存中创建一个对象,名字就是ObjectCreator。这个对象(类)自身拥有创建对象(类实例)的能力,而这就是为什么它是一个类的原因。但是,它的本质仍然是一个对象,于是你可以对它做如下的操作:你可以将它赋值给一个变量, 你可以拷贝它, 你可以为它增加属性, 你可以将它作为函数参数进行传递。

图7:类对象实例

4 元类

通过上面的描述,我们知道了Python中的类也是对象。元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解为:

MyClass = MetaClass()    #元类创建

MyObject = MyClass()     #类创建实例

实际上MyClass就是通过type()来创创建出MyClass类,它是type()类的一个实例;同时MyClass本身也是类,也可以创建出自己的实例,这里就是MyObject函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。你可以通过检查__class__属性来看到这一点。Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来。

__metaclass__属性

你可以在写一个类的时候为其添加__metaclass__属性,定义了__metaclass__就定义了这个类的元类。

class Foo(metaclass=something):   #py3

__metaclass__ = something…

元类的主要目的就是为了当创建类时能够自动地改变类。通常,你会为API做这样的事情,你希望可以创建符合当前上下文的类。假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过设定__metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。__metaclass__实际上可以被任意调用,它并不需要是一个正式的类。所以,我们这里就先以一个简单的函数作为例子开始。

图8:自定义元类

使用class来当做元类

由于__metaclass__必须返回一个类。

图9:class做元类

但是,这种方式其实不是OOP。我们直接调用了type,而且我们没有改写父类的__new__方法。现在让我们这样去处理:

图10:oop形式的元类定义

你可能已经注意到了有个额外的参数upperattr_metaclass,这并没有什么特别的。类方法的第一个参数总是表示当前的实例,就像在普通的类方法中的self参数一样。当然了,为了清晰起见,这里的名字我起的比较长。但是就像self一样,所有的参数都有它们的传统名称。因此,在真实的产品代码中一个元类应该是像这样的:

图11:元类定义

如果使用super方法的话,我们还可以使它变得更清晰一些。

图12: 修正元类定义

5 元类实例分析

在阅读Django源代码时,Django自带了ORM数据库模型,这部分源码大量使用了元类,我们通过创建一个类似Django中的ORM来熟悉一下元类的使用,通常元类用来创建API是非常好的选择,使用元类的编写很复杂但使用者可以非常简洁的调用API。

#我们想创建一个类似Django的ORM,只要定义字段就可以实现对数据库表和字段的操作。
class User(Model):    # 定义类的属性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')
# 创建一个实例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到数据库:
u.save()

上面代码展示了使用ORM轻松定义数据模型,下面我们使用元类来定义类似于Django ORM类的功能。(整理的代码,可能有点问题,没试验过)

# _*_ coding: UTF-8 _*_
# @Time : 2020/6/11 15:50
# @Author : Huang Ri Qiu
# @Site : 
# @File : type004.py
# @Software : PyCharm

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

    def __str__(self):
        return '' % (self.__class__.__name__, self.name)

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

#二、定义元类,控制Model对象的创建

class ModelMetaclass(type):
    '''定义元类'''
    def __new__(cls, name, bases, attrs):
        if name=='Model':

            return super(ModelMetaclass,cls).__new__(cls, name, bases, attrs)

        mappings = dict()
        for k, v in attrs.iteritems():
            # 保存类属性和列的映射关系到mappings字典
            if isinstance(v, Field):
                print('Found mapping: %s==>%s' % (k, v))
                mappings[k] = v

            for k in mappings.iterkeys():
                #将类属性移除,使定义的类字段不污染User类属性,只在实例中可以访问这些key
                attrs.pop(k)
                attrs['__table__'] = name.lower() # 假设表名和为类名的小写,创建类时添加一个__table__类属性
                attrs['__mappings__'] = mappings # 保存属性和列的映射关系,创建类时添加一个__mappings__类属性
        return super(ModelMetaclass,cls).__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__.iteritems():
            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))#最后,我们使用定义好的ORM接口,使用起来非常的简单。

class User(Model):
    # 定义类的属性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

# 创建一个实例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到数据库:
u.save()
原文地址:https://www.cnblogs.com/yimai-series/p/13093959.html