··· type 与 MetaClass
type
实例指的是某类的对象,如husky是Dog类的实例,但husky同时也是对象,即单独称呼时叫对象,附属于某类时叫某类的实例。
python默认的元类是type(缺省元类),就连 object 也是 type 创造出来的类, type 是一切类型的缺省元类,所以 object 类也是一种 type ;但同时type也是object的类,构造出的世间万物都是object的实例。所以就算声明一个类不继承自object,它也依然是object的实例,只是不继承自object而已,虽然没有从object继承它的一些方法,但是从构造器
object. __new__( )中获得了作为一个对象所需要的数据结构和参数。
用type来 动态创建 类:
>>> def fn(self, name='world'): # 先定义函数
... print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
使用type创建新的类的语法如下:
class type(name, bases, dict)
return a new type object。
由上图可知,
用type和class创建类,type中的 “ y ” 和class 中的 “ x_1 ” 是等同的,都是类名,赋值给__name__属性,但是type 创建的类却不能用类名 “y” 来创建实例,只能用type的返回值 “ x ”来创建 “ h = x() ” —— 为了避免混淆,我们最好把类名和 type 的返回值设置成相同的字符串,如 X = type( ' X ' , ( object , ) , dict ( a = 1) )。
MetaClass
参考资料:
深刻理解python中的元类(没看)
MetaClass翻译过来就是元类,什么叫元?元者,源也,根也,本也!所谓元类就是能够创造出其他类的类,即原始类。
下面看看怎么用MetaClass来动态创建类。
# -*- coding:utf-8 -*-
# metaclass是创建类,所以必须从 'type' 类派生
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
print 'attrs:%s\n' %attrs
attrs['add'] = lambda self, value: self.append(value)
print 'cls: %s, \n name: %s, \n bases: %s, \n attrs: %s\n' %(cls, name, bases, attrs)
print type.__new__(cls, name, bases, attrs)
return type.__new__(cls, name, bases, attrs) #用type申请内存空间(并返回用metaclass创建好的MyList对象)
class MyList(list):
__metaclass__ = ListMetaclass #指示使用ListMetaclass来定制类
结果:
值得注意的是,
attrs 是 dict 类型的数据,它
存储着MetaClass调用者的所有属性和方法,其中属性包括
类属性和实例的属性,所以我们可以往 用Metaclass创建的类中添加方法 ,这实际上就是给 attrs 添加 key - value 值对。给attrs 赋值时要用dict 的方式
attrs[ ' ' ] = ?。
关于类属性和实例属性:
直接在class中定义的是类属性:
class Student(object):
name = 'Student'
实例属性必须通过实例来绑定,比如self.name = ' xxx ' 。来测试一下:
>>> # 创建实例s:
>>> s = Student()
>>> # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性:
>>> print(s.name)
Student
>>> # 这和调用Student.name是一样的:
>>> print(Student.name)
Student
>>> # 给实例绑定name属性:
>>> s.name = 'Michael'
>>> # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性:
>>> print(s.name)
Michael
>>> # 但是类属性并未消失,用Student.name仍然可以访问:
>>> print(Student.name)
Student
>>> # 如果删除实例的name属性:
>>> del s.name
>>> # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了:
>>> print(s.name)
Student
因此,在编写程序的时候,千万不要把实例属性和类属性使用相同的名字(如果碰到这种情况)。
创建一个实例时调用了其类的构造器__new__(),当解释器看到类声明时,就调用type的构造器__new__(),把类声明中写的东西作为name, bases, attrs参数传递进去,然后__new__()就申请内存创建了一个类的对象(实例),并返回它。cls 参数是metaclass简称,指它调用的metaclass。
用type()创建一个类,是调用了type()的构造器__new__(),从而创建了一个type的新实例,也就是一个新的类。如果解释器在类声明中读到了__metaclass__属性,那就会用这个自定义元类的构造器__new__()来造这个类,如果没有,那就会用type的构造器__new__()来造这个类,也就是说type就是缺省元类。
有了自定义元类,就可以使真实造出来的类和类声明中写的有所不同,只要覆盖元类的构造器就行了,当然,它
需要调用type的构造器来申请空间。
这样,我们就用Metaclass创建了 list 的派生类 MyList ,并添加了一个方法 add()
关于lambda 的用法疑问,可以用下面例子解释:
>>>x = lambda a,b : a*b
>>>x(2,3)
6
看来调用lambda 函数就是用括号里穿参数的形式。这样就可以解释下面调用add()方法的疑问
>>>from ok5 import MyList
>>>L=MyList()
>>>L.add(1) # self 就是 L,调用add,就是调用 lambda 函数
>>>L
[1]
调用add()方法,就是调用 lambda 函数。
定制类:__str__ 用作自定义打印实例
我们先定义一个Student类,打印一个实例:
>>> class Student(object):
... def __init__(self, name):
... self.name = name
...
>>> print Student('Michael')
<__main__.Student object at 0x109afb190>
打印出一堆<__main__.Student object at 0x109afb190>,不好看。
怎么才能打印得好看呢?只需要定义好__str__()方法,返回一个好看的字符串就可以了:
>>> class Student(object):
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return 'Student object (name: %s)' % self.name
...
>>> print Student('Michael')
Student object (name: Michael)
这样打印出来的实例,不但好看,而且容易看出实例内部重要的数据。
··· ORM
为什么ORM一定要用Metaclass来创建?很简单,假设我们已经写好了ORM,用户会怎么调用呢?
class User(Model):
id_key = IntegerField('uid')
name_key = StringField('username')
email_key = StringField('email')
password_key = StringField('password')
这就是用户调用ORM的方式,但是我们并不知道用户会用哪些类属性,在这里的类属性是id_key、name_key、email_key、password_key,但是下一次可能用户定义的就是id、address、cellphone、name、email、password等,所以我们必须把用户创建的类属性动态加入,这就要用到Metaclass,
总结一句话,Metaclass就是给动态创建的类【
添加属性和方法的】。
拿上面的例子来说,
在User类调用的MetaClass中打印attrs得到上面的结果,可见在User中定义的类属性变成了其所调用的 MetaClass的 attrs的 key,类属性的值则作为attrs的 value,类属性的值可以是任何类型的数据,这里是 IntergetField 或 StringField 类的实例。
另外,利用上面的__str__()方法,我们在打印类属性的值由于其是实例(object)类型的数据时,所以我们可以自己选择打印格式,而不是类似
,这样冷冰冰的东西。(当然不设置__str__()也并没有影响)
ORM代码
# -*- coding: utf-8 -*-
' Simple ORM using metaclass '
class Field(object):
def __init__(self, name, column_type):
self.name = name
self.column_type = column_type
def __str__(self): #定义__str__()方法的目的:一旦打印Field实例就调用
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)
print '-------------------------------------------------'
print 'cls: %s, \n name: %s, \n bases: %s, \n attrs: %s\n' %(cls, name, bases, attrs)
print '-------------------------------------------------'
mappings = dict()
for key, value in attrs.iteritems():
if isinstance(value, Field):
print('Found mapping: %s ==> %s' % (key, value)) #打印v时调用__str__()方法
mappings[key] = value
for key in mappings.iterkeys():
attrs.pop(key)
attrs['__mappings__'] = mappings
'''
以上那么做的目的很明显,就是为了让attrs的结构更清晰,让所有的类属性都集中到mappings dict中,再赋值给attrs的__mappings__
这样类属性就整合为一个整体作为attrs的__mappings__的 value
'''
attrs['__table__'] = name # 假设表名和类名一致
return type.__new__(cls, name, bases, attrs)
class Model(dict):
__metaclass__ = ModelMetaclass #User和Model都会调用ModelMetaclass,不过Model调用ModelMetaclass会什么都不做就返回(if name=='Model')
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 = []
'''
print '-------------------------------------------------'
print '__mappings__:%s,\n' %self.__mappings__
print '-------------------------------------------------'
'''
#self是u,u是User的实例,但是 u 没有属性__mappings__,__mappings__是类的属性
#u继承类的属性,所以这里的self.__mappings__是User.__mappings__
for k, v in self.__mappings__.iteritems():
#print k,v
fields.append(v.name) #v本身就是实例(object),所以可以有自己的属性
params.append('?')
args.append(self[k])
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_key = IntegerField('uid') #括号里的是数据库表的列名,因为 fields.append(v.name),而v.name就是uid,uid等作为sql的 values
name_key = StringField('username')
email_key = StringField('email')
password_key = StringField('password')
'''
print '-------------------------------------------------'
print 'User.__mappings__:%s,\n User.__table__:%s,\n User.__module__:%s\n' %(User.__mappings__,User.__table__,User.__module__)
print '-------------------------------------------------'
'''
#########################################################################################
u = User(id_key=12345, name_key='zhuma', email_key='test@orm.org', password_key='zhangpan')
#括号里的全部是**kw 关键字参数(dict), self 是 u . id, name, email, password变成了实例u的属性,而非User的类属性
#根据User的类属性key,找到实例u的对应的value(u的key一定要和User类属性的名称保持一致).
'''
print '-------------------------------------------------'
print 'u.__mappings__:%s,\n u.__table__:%s,\n u.__module__:%s\n' %(u.__mappings__,u.__table__,u.__module__)
print '-------------------------------------------------'
'''
u.save()
u=User()会调用User的父类 Model 初始化参数,而 Model 的父类又是dict ,所以u = User(id_key=12345, name_key='Michael', email_key='test@orm.org', password_key='my-pwd')括号里的参数会被dict初始化,同时Model 又定义了__getattr__() 和 __setattr__()方法,所以初始化后可以用 u[id_key] 和 u.id_key 来访问 u 的属性。
注意很重要的一点:save()方法中
args.append(self[k])
self 是 u , 而 k 是User定义的id_key、name_key、email_key、password_key,所以要想用self [k] 找到 u 对应的value ,就必须u的key一定要和User类属性(即上面的 k )的名称保持一致。