Python进阶-元类详解

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

#一、首先来定义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()

上面的代码完整的展示了使用ng元类创建ORM数据模型的功能,元类的功能重点在于编写Python框架和模板,以及创建编程模式比如单例模式,工厂模式等,本篇幅有限不在详细分析,后续篇幅将详细介绍Python元类实现单例模式,工厂模式等的23种变成模式。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20190220A18TM500?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励