下面是廖雪峰 python 教程中的编写一个简易 ORM 框架的例子,附上源代码
#!/usr/bin/env python3
# -*- 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):
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 k, v in attrs.items():
if isinstance(v, Field):
print('Found mapping: %s ==> %s' % (k, v))
mappings[k] = v
for k in mappings.keys():
attrs.pop(k)
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 k, v in self.__mappings__.items():
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))
# testing code:
class User(Model):
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')
u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')
u.save()
其中编写元类时,下面这句不太理解:
for k in mappings.keys():
attrs.pop(k)
为什么要在找到符合条件的 key-value 对并将其放入新的字典 mapping 后,还要将这些 key-value 对从 attrs 字典中去除? 我试验了一下,如果不这么做,最后的 ARGS 打印结果是
ARGS: [<__main__.IntegerField object at 0x10cf63630>, <__main__.StringField object at 0x10cf636d8>, <__main__.StringField object at 0x10cf63668>, <__main__.StringField object at 0x10cf636a0>]
而不是创建实例 u 是传入的参数的值,即
ARGS: ['my-pwd', '[email protected]', 'Michael', 12345]
按我的理解,就算不执行 pop 操作,在创建实例 u 后,当执行 u.save()到
args.append(getattr(self, k, None))
这一步时,实例 u 的属性应该会自动覆盖类 User 的属性,按这个道理, getattr(self,k,None)返回的应该是传入的参数的值,即 my-pwd', '[email protected]', 'Michael', 12345 这些。 所以,实在没想通,到底为什么要执行元类中的 pop 操作。
1
Tianny OP 为什么要在找到符合条件的 key-value 对并将其放入新的字典 mapping 后,还要将这些 key-value 对从 attrs 字典中去除?也就是在执行下面的操作时
``` for k in mappings.keys(): attrs.pop(k) ``` |
2
crossmaya 2017-02-23 03:54:56 +08:00
请看一下 dir 函数。
|
4
LittleKey 2017-02-23 10:11:33 +08:00 via Android
因为__getattr__是在__getattribute__没找到时调用的吧,而 getattr 里会调用__getattribute__去找。
所以如果你不删掉的话就像 @binux 说的一样了 |
5
enenaaa 2017-02-23 10:51:55 +08:00
Model 继承于 dict 。 dict 里键值对不等于类定义的属性。
getattr 函数优先从类定义里面找, 找不到后才调用__getattr__。 在__getattr__打个 log 就看出来了。 |
8
enenaaa 2017-02-23 12:56:52 +08:00
@Tianny
用 getattr 函数或 a.b 形式访问类和对象的属性时, 先从类定义(__dict__中)获取,找不到的话则调用 __getattr__。 对于 dict , 则是另外一种机制,以键取值用 a['b']的形式。 这是两种不同的机制。 Model 类将对象 a.b 形式的操作转换为 a['b'], 想到这一点, 就不难理解了吧。 |
9
enenaaa 2017-02-23 13:03:21 +08:00
Model 类里 self.b 和 self['b'] 是两个不同的变量。 他们保存在不同的表里。
|
10
zhuangzhuang1988 2017-02-23 13:09:27 +08:00
|
11
Tianny OP @enenaaa 再次请教下,说一下我的思路,希望您能帮我看下,不对的地方请指出,万分感谢!要创建 User 类,先根据元类 ModelMetaclass 来创建它。当执行元类后,如果不执行 pop 操作,此时 User 类的 attrs 即属性集合是{id: IntegerField('id') ,__mapping__:{id: IntegerField('id') }},这里我只是拿出 id 举个例子。然后,当创建 User 类的对象 u 时,即执行 u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')这一步,需要初始化对象就是执行__init__,因为 User 类的父类是 Model ,同时 Model 类的__init__方法是调用 dict 的__init__方法,所以初始化实例 u 时,调用的是 dict 的__init__方法,所以根据传入的参数属性就变成了字典形式{id:12345}。最后执行 u.save(),当执行到 args.append(getattr(self, k, None))时,此时就像你前面说的“用 getattr 函数或 a.b 形式访问类和对象的属性时, 先从类定义(__dict__中)获取,找不到的话则调用 __getattr__”,因为前面没有执行 pop 操作, User 类中有属性 id ,那么 getattr(self,id,None)会首先到 User 类中查找 id 对应的属性值,为 IntegerField('id')。如果前面元类中执行 pop 操作的话, getattr(id)在 User 类中找不到,就会调用__getattr__,此时执行 return self[key],那么就返回初始化实例 u 后 key id 对应的值,即 12345.
|
12
Tianny OP @enenaaa 还有个问题。
我在这句“ u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')”后面加上“ print(dir(u))”。 当在元类中不进行 pop 操作,打印结果是带有 id 即属性中是有 id 的。 当在元类中进行 pop 操作,打印的结果不带 id 即属性中没有 id 。 那么问题来了, dir(u)这个操作,作用是返回实例 u 的属性的吧?按我的理解,不管元类有没有 pop ,在初始化实例 u 后,实例 u 不是肯定有 id 这个属性吗? 求解释,非常感谢! |
13
Tianny OP @binux 请问为什么 如果不进行 pop 操作,就会访问到 IntegerField('id') 。"getattr(self, k, None)"这个代码的 getattr 是怎么调用的?不理解,希望您能详解给我讲解下。非常感谢!
|
14
binux 2017-02-24 02:23:15 +08:00
@Tianny #13 https://docs.python.org/2.7/reference/datamodel.html#object.__getattr__
不去掉就叫做 usual places |
15
enenaaa 2017-02-24 09:37:49 +08:00 1
@Tianny pop 之后, 原先定义的 id 没有了。 Model 类又定义了__setattr__函数, 里面把键值对存到了 dict 类的表里, 而不是 User.__dict__里, dir 函数就列不出来了。
就像 dir(dict) 不能列出 dict 对象存储的键值对一样。 dict 是 C 写的内置对象, 里面的键值对用哈希表存储。 User.__dict__是类对象用来存储属性的一个 dict 。 而 Model 类本身继承于 dict , 所以这里用到了两个 dict 。 |