一周一个Python语法糖:(三) 元类

先来了解下python魔法的内核吧:

一切皆对象

  • 一切皆对象
  • 一切皆有类型
  • “class” and "type" 之间本质上上并无不同
  • 类也是对象
  • 他们的类型都是type
class ObjectCreator(object):
        pass

这段代码将在内存中创建一个对象,名字叫ObjectCreator.

这个对象(类)自身拥有创建对象的(类实例的能力),##

而这也就是它为什么是一个类的原因##

你可以对该对象进行以下操作:

  1. 将它赋值给一个变量
  2. 可以拷贝它
  3. 可以为它增加属性
  4. 可以将其作为函数参数进行传递
#交互型
>>> def echo(o):
...     print(o)
... 
>>> echo(my_object)
<__main__.ObjectCreator object at 0x7fcf7d0d6c50>  #你可以将类做为参数传给函数
>>> ObjectCreator.new_attrituer=echo    #  你可以为类增加属性
>>> print(hasattr(ObjectCreator,'new_attrituer'))
True
>>> print(ObjectCreator.new_attrituer) #新加的类的方法属性
<function echo at 0x7fcf7d171f28>

一:用type创建一个类

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
#三个参数的类型分别是str,tuple,dic

比如:

#目标类:
class Animal(object):
    def __init__(self,name):
        self.name=name
    def eat(self):
        pass
    def go_to_eat(self):
        pass
#用type创建
def init(self,name):
    self.name=name
def eat(self):
    pass
def go_to_eat(self):
    pass
Animal=type('Animal',(object,),{
    '__init__':init,
    'eat':eat,
    'go_to_eat':go_to_eat
})

>>>print(Animal)
<class '__main__.Animal'>

这样我们实现了用type去动态创建一个类的
BUT~这样是不是太麻烦了!!!(抬走下一位!)

二:metaclass登场

metaclass,直译为元类,简单的解释就是:

当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。

# metaclass是创建类,所以必须从`type`类型派生:
class Listmetaclass(type):
    def __new__(cls,name,bases,attrs):
        attrs['add']=lambda self,value:self.append(value)
        return type.__new__(cls,name,bases,attrs)
    
class Mylist(list):
    __metaclass__=Listmetaclass

  • 按照默认习惯.metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass
  • 当我们写下__metaclass__=Listmetaclass时候,它表示解释器在创建Mylist的时候
    ,要通过Listmetaclass的__new__()方法创建
    在此,我们可以修改类的定义,比如:加上新的方法,然后返回修改后的定义.
  • __new__()方法接受的参数依次是:
    1. 当前准备创建的类的对象
    2. 类的名字
    3. 类继承的父类的集合
    4. 类的方法的集合

元类的主要目的就是为了当创建类时能够自动改变类.

使用到元类的代码比较复杂,这背后的原因倒并不是因为元类本身,

而是因为你通常会使用元类去做一些晦涩的事情,

依赖于自省,控制继承等等。

确实,用元类来搞些“黑暗魔法”是特别有用的,

因而会搞出些复杂的东西来。

就这个例子来说:

  • 拦截类的创建(拦截Mylist的创建)
  • 修改类(给Mylist增加add的方法)
  • 返回修改之后的类

**“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” **-----Tim Peters

元类的主要作用的创建API,一个典型的例子是 ORM.

ORM全称“Object Relational Mapping”,
即对象-关系映射,就是把关系数据库的一行映射为一个对象,
也就是一个类对应一个表,
这样,写代码更简单,不用直接操作SQL语句。

~

要编写一个ORM框架,所有的类都只能动态定义,
因为只有使用者才能根据表的结构定义出对应的类来。

我们来尝试写一个简单的ORM框架吧
首先我们要知道使用者会调用什么接口:
比如,使用者可能会定义一个User类来操作数据表User.

class User(Model):
  #创建User表
  #创建四列属性
    id=IntegerField('id')
    name=StringField('username')
    email=StringField('email')
    password=StringField('password')
    
# 创建一个实例:
u=User(id='12345',name='zhou',email='124@qq.com',password='123455')
#保存到数据库
u.save()  

首先,我们来定义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:

#为了简化,我们先定义好各种属性的type
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')

接下来,我们要编写元类了

class ModelMetaclass(type):
    def __new__(cls,name,bases,attrs):
        #如果是基类,直接返回(即不对model类进行修改)
        if  name=='Model':
            return type.__new__(cls,name,bases,attrs)
        print('Found model: %s' %name)
        #如果是其他类,则进行装饰
        #取出所有类属性,将其放入mapping
        mappings=dict()
        for key,value in attrs.iteritems():
            if isinstance(value,Field):
                print('Found mapping: %s==>%s' %(key,value))
                mappings[key]=value
        for key in mappings.iterkeys():
            attrs.pop(key)
        attrs['__mappings__']=mappings
        attrs['__table__']=name
        return type.__new__(cls,name,bases,attrs)
        

编写基类:

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 key,value in self.__mappings__.iteritems():
            fields.append(value.name)
            params.append('?')
            args.append(getattr(self,key,None))
        sql='insert into %s(%s) values(%s)' %(self.__table__,','.join(fields),','.join(params))
        print('SQL:%s' %sql)
        print('ARGS:%s' %str(args))

当用户自定义一个class User(Model)时候,
解释器首先在当前类User的定义中寻找
metaclass,如果没找到,就在继承的父类中继续找.,
找到了,就用model中的定义的ModelMetaclass去创建User类
也就是说,metaclass可以隐式地继承到子类,但子类自己却感觉不到。

在ModelMetaclass中做的事情:

  • 排除对Model 的修改
  • 在当前类 中查找定义的类的属性,如果找到一个FIeld属性,就保存到__mappings__字典中
    同时从类属性中删除Field属性,否则容易造成运行时的错误
  • 把表名保存到__table__中

在Model 类中,就可以定义各种操作数据库的方法了(列属性保存在__mappings__)中

全部代码:

#!/usr/bin/python3
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)

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')
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 key,value in attrs.iteritems():
            if isinstance(value,Field):
                print('Found mapping: %s==>%s' %(key,value))
                mappings[key]=value
        for key in mappings.iterkeys():
            attrs.pop(key)
        attrs['__mappings__']=mappings
        attrs['__table__']=name
        return type.__new__(cls,name,bases,attrs)
        
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 key,value in self.__mappings__.iteritems():
            fields.append(value.name)
            params.append('?')
            args.append(getattr(self,key,None))
        sql='insert into %s(%s) values(%s)' %(self.__table__,','.join(fields),','.join(params))
        print('SQL:%s' %sql)
        print('ARGS:%s' %str(args))



class User(Model):
    id=IntegerField('id')
    name=StringField('username')
    email=StringField('email')
    password=StringField('password')
    
u=User(id='12345',name='zhou',email='124@qq.com',password='123455')
u.save()  
       

运行结果:

Found model: User
Found mapping: email==><StringField:email>
Found mapping: password==><StringField:password>
Found mapping: id==><IntegerField:id>
Found mapping: name==><StringField:username>
SQL:insert into User(password,email,username,id) values(?,?,?,?)
ARGS:['123455', '124@qq.com', 'zhou', '12345']

最后说几句:

  • 元类(Metaclass)类似于装饰器,可以在类的创建时动态修改类.

  • 元类语法糖:__metaclass__

学习参考:
廖雪峰Python教程
深入理解元类
5分钟理解元类

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容

  • 什么是元类? 理解元类(metaclass)之前,我们先了解下Python中的OOP和类(Class) 面向对象全...
    时间之友阅读 314评论 0 0
  • 1. 使用__slots__ 正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实...
    时间之友阅读 289评论 0 1
  • 前言 第十二篇了,撸起袖子,就是干。 目录 一、Python 中类也是对象 在了解元类之前,我们先进一步理解 Py...
    GitHubClub阅读 751评论 0 7
  • 我当然知道 那只是偶遇 可这也足够 让我欢喜 人生没有多少必须 淡淡的感动 弱弱的情绪 就把生活装点美丽 风暖菩提...
    微雨凭栏阅读 139评论 0 0
  • 我有一位老师,看起来四十岁的样子,却仍旧身材姣好,气质优雅,穿着打扮非常入时。身为女生,我们总会不住地感叹:天呐,...
    稻场旧事阅读 519评论 4 7