深入理解python元类mateclass

深入理解metaclass在python中的应用

Python 中的

元类(metaclass)是一个深度魔法,平时我们可能比较少接触到元类,本文将通过一些简单的例子来理解这个魔法。

everything is objec ,类也是对象

在 Python 中,一切皆对象。字符串,列表,字典,函数是对象,类也是一个对象,因此你可以:

  • 把类赋值给一个变量
  • 把类作为函数参数进行传递
  • 把类作为函数的返回值
  • 在运行时动态地创建类

看一个简单的例子:

class Foo(object):
foo = True
class Bar(object):
bar = True
def echo(cls):
print cls
def select(name):
if name == 'foo':
return Foo # 返回值是一个类
if name == 'bar':
return Bar
>>> echo(Foo) # 把类作为参数传递给函数 echo

<class>
>>> cls = select('foo') # 函数 select 的返回值是一个类,把它赋给变量 cls
>>> cls
__main__.Foo
/<class>

熟悉又陌生的 type

在日常使用中,我们经常使用 object 来派生一个类,事实上,在这种情况下,Python 解释器会调用 type 来创建类。

这里,出现了 type,没错,是你知道的 type,我们经常使用它来判断一个对象的类型,比如:

class Foo(object):
Foo = True
>>> type(10)
<type>
>>> type('hello')
<type>
>>> type(Foo())
<class>
>>> type(Foo)
<type>
/<type>/<class>/<type>/<type>

事实上,type 除了可以返回对象的类型,它还可以被用来动态地创建类(对象)。下面,我们看几个例子,来消化一下这句话。

使用 type 来创建类(对象)的方式如下:

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性和方法的字典(名称和值))

最简单的情况

假设有下面的类:

class Foo(object):
pass

现在,我们不使用 class 关键字来定义,而使用 type,如下:

Foo = type('Foo', (object, ), {}) # 使用 type 创建了一个类对象

上面两种方式是等价的。我们看到,type 接收三个参数:

  • 第 1 个参数是字符串 'Foo',表示类名
  • 第 2 个参数是元组 (object, ),表示所有的父类
  • 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法

在上面,我们使用 type() 创建了一个名为 Foo 的类,然后把它赋给了变量 Foo,我们当然可以把它赋给其他变量,但是,此刻没必要给自己找麻烦。

接着,我们看看使用:

>>> print Foo 

<class>
>>> print Foo()
<__main__.foo object="" at="">
/<class>

有属性和方法的情况

假设有下面的类:

class Foo(object):
foo = True
def greet(self):
print 'hello world'
print self.foo

用 type 来创建这个类,如下:

def greet(self):
print 'hello world'
print self.foo
Foo = type('Foo', (object, ), {'foo': True, 'greet': greet})

上面两种方式的效果是一样的,看下使用:

>>> f = Foo()
>>> f.foo
True
>>> f.greet
<bound>>
>>> f.greet()
hello world
True
/<bound>

继承的情况

再来看看继承的情况,假设有如下的父类:

class Base(object):
pass

我们用 Base 派生一个 Foo 类,如下:

class Foo(Base):
foo = True

改用 type 来创建,如下:

Foo = type('Foo', (Base, ), {'foo': True})

什么是元类(metaclass)

元类(metaclass)是用来创建类(对象)的可调用对象。这里的可调用对象可以是函数或者类等。但一般情况下,我们使用类作为元类。对于实例对象、类和元类,我们可以用下面的图来描述:

类是实例对象的模板,元类是类的模板
+----------+ +----------+ +----------+
| | | | | |
| | instance of | | instance of | |
| instance +------------>+ class +------------>+ metaclass|
| | | | | |
| | | | | |
+----------+ +----------+ +----------+

我们在前面使用了 type 来创建类(对象),事实上,type 就是一个元类

那么,元类到底有什么用呢?要你何用...

元类的主要目的是为了控制类的创建行为。我们还是先来看看一些例子,以消化这句话。

元类的使用

先从一个简单的例子开始,假设有下面的类:

class Foo(object):
name = 'foo'
def bar(self):
print 'bar'

现在我们想给这个类的方法和属性名称前面加上 my_ 前缀,即 name 变成 my_name,bar 变成 my_bar,另外,我们还想加一个 echo 方法。当然,有很多种做法,这里展示用元类的做法。

1.首先,定义一个元类,按照默认习惯,类名以 Metaclass 结尾,代码如下:

class PrefixMetaclass(type):
def __new__(cls, name, bases, attrs):
# 给所有属性和方法前面加上前缀 my_
_attrs = (('my_' + name, value) for name, value in attrs.items())
_attrs = dict((name, value) for name, value in _attrs) # 转化为字典
_attrs['echo'] = lambda self, phrase: phrase # 增加了一个 echo 方法

return type.__new__(cls, name, bases, _attrs) # 返回创建后的类

上面的代码有几个需要注意的点:

  • PrefixMetaClass 从 type 继承,这是因为 PrefixMetaclass 是用来创建类的
  • __new__ 是在 __init__ 之前被调用的特殊方法,它用来创建对象并返回创建后的对象,对它的参数解释如下:
  • cls:当前准备创建的类
  • name:类的名字
  • bases:类的父类集合
  • attrs:类的属性和方法,是一个字典

2.接着,我们需要指示 Foo 使用 PrefixMetaclass 来定制类。

在 Python2 中,我们只需在 Foo 中加一个 __metaclass__ 的属性,如下:

class Foo(object):
__metaclass__ = PrefixMetaclass
name = 'foo'
def bar(self):
print 'bar'

在 Python3 中,这样做:

class Foo(metaclass=PrefixMetaclass):
name = 'foo'
def bar(self):

print 'bar'

现在,让我们看看使用:

>>> f = Foo()
>>> f.name # name 属性已经被改变
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-774-4511c8475833> in <module>()
----> 1 f.name
AttributeError: 'Foo' object has no attribute 'name'
>>>
>>> f.my_name
'foo'
>>> f.my_bar()
bar
>>> f.echo('hello')
'hello'
/<module>/<ipython-input-774-4511c8475833>

可以看到,Foo 原来的属性 name 已经变成了 my_name,而方法 bar 也变成了 my_bar,这就是元类的魔法。

再来看一个继承的例子,下面是完整的代码:

class PrefixMetaclass(type):
def __new__(cls, name, bases, attrs):
# 给所有属性和方法前面加上前缀 my_
_attrs = (('my_' + name, value) for name, value in attrs.items())
_attrs = dict((name, value) for name, value in _attrs) # 转化为字典
_attrs['echo'] = lambda self, phrase: phrase # 增加了一个 echo 方法
return type.__new__(cls, name, bases, _attrs)
class Foo(object):
__metaclass__ = PrefixMetaclass # 注意跟 Python3 的写法有所区别
name = 'foo'
def bar(self):
print 'bar'
class Bar(Foo):
prop = 'bar'

其中,PrefixMetaclass 和 Foo 跟前面的定义是一样的,只是新增了 Bar,它继承自 Foo。先让我们看看使用:

>>> b = Bar()
>>> b.prop # 发现没这个属性
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-778-825e0b6563ea> in <module>()
----> 1 b.prop
AttributeError: 'Bar' object has no attribute 'prop'
>>> b.my_prop
'bar'
>>> b.my_name
'foo'
>>> b.my_bar()
bar
>>> b.echo('hello')
'hello'
/<module>/<ipython-input-778-825e0b6563ea>

我们发现,Bar 没有 prop 这个属性,但是有 my_prop 这个属性,这是为什么呢?

原来,当我们定义 class Bar(Foo) 时,Python 会首先在当前类,即 Bar 中寻找 __metaclass__,如果没有找到,就会在父类 Foo 中寻找 __metaclass__,如果找不到,就继续在 Foo 的父类寻找,如此继续下去,如果在任何父类都找不到 __metaclass__,就会到模块层次中寻找,如果还是找不到,就会用 type 来创建这个类。

这里,我们在 Foo 找到了 __metaclass__,Python 会使用 PrefixMetaclass 来创建 Bar,也就是说,元类会隐式地继承到子类,虽然没有显示地在子类使用 __metaclass__,这也解释了为什么 Bar 的 prop 属性被动态修改成了 my_prop。

元类在Djange实现ORM

为什么ORM一定要用Metaclass来创建?很简单,假设我们已经写好了ORM,用户会怎么调用呢?

class User(Model):
\tid_key = IntegerField('uid')
\tname_key = StringField('username')
\temail_key = StringField('email')
\tpassword_key = StringField('password')

ORM代码

# -*- coding: utf-8 -*-
' Simple ORM using metaclass '
class Field(object):
\tdef __init__(self, name, column_type):
\t\tself.name = name
\t\tself.column_type = column_type
\t\t
\tdef __str__(self): #定义__str__()方法的目的:一旦打印Field实例就调用
\t\treturn '' % (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):
\t\tif name=='Model':
\t\t\treturn type.__new__(cls, name, bases, attrs)
\t\t
\t\tprint('Found model: %s' % name)
\t\t
\t\tprint '-------------------------------------------------'
\t\tprint 'cls: %s, \\n name: %s, \\n bases: %s, \\n attrs: %s\\n' %(cls, name, bases, attrs)
\t\tprint '-------------------------------------------------'
\t\t
\t\tmappings = dict()
\t\tfor key, value in attrs.iteritems():
\t\t\tif isinstance(value, Field):
\t\t\t\tprint('Found mapping: %s ==> %s' % (key, value)) #打印v时调用__str__()方法

\t\t\t\tmappings[key] = value\t\t
\t\t\t\t
\t\tfor key in mappings.iterkeys():
\t\t\tattrs.pop(key)
\t\tattrs['__mappings__'] = mappings
\t\t'''
\t\t以上那么做的目的很明显,就是为了让attrs的结构更清晰,让所有的类属性都集中到mappings dict中,再赋值给attrs的__mappings__
\t\t这样类属性就整合为一个整体作为attrs的__mappings__的 value
\t\t'''
\t\t
\t\tattrs['__table__'] = name # 假设表名和类名一致
\t\treturn type.__new__(cls, name, bases, attrs)
class Model(dict):
__metaclass__ = ModelMetaclass #User和Model都会调用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):
\t\tfields = []
\t\tparams = []
\t\targs = []
\t\t
\t\t'''
\t\tprint '-------------------------------------------------'
\t\tprint '__mappings__:%s,\\n' %self.__mappings__
\t\tprint '-------------------------------------------------'
\t\t'''
\t\t
\t\t#self是u,u是User的实例,但是 u 没有属性__mappings__,__mappings__是类的属性
\t\t#u继承类的属性,所以这里的self.__mappings__是User.__mappings__
\t\tfor k, v in self.__mappings__.iteritems():
\t\t\t#print k,v
\t\t\tfields.append(v.name) #v本身就是实例(object),所以可以有自己的属性

\t\t\tparams.append('?')
\t\t\targs.append(self[k])
\t\tsql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
\t\tprint('SQL: %s' % sql)
\t\tprint('ARGS: %s' % str(args))
---------------------

写到这里,不知道你理解元类了没?希望理解了,如果没理解,就多看几遍吧~

小结

  • 在 Python 中,类也是一个对象。
  • 类创建实例,元类创建类。
  • 元类主要做了三件事:
  • 拦截类的创建
  • 修改类的定义
  • 返回修改后的类
  • 当你创建类时,解释器会调用元类来生成它,定义一个继承自 object 的普通类意味着调用 type 来创建它。


分享到:


相關文章: