深夜问道 Python 题,实在没想明白。求指点! - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
PanameraM

深夜问道 Python 题,实在没想明白。求指点!

  •  2
     
  •   PanameraM 2017 年 2 月 23 日 3973 次点击
    这是一个创建于 3349 天前的主题,其中的信息可能已经有所发展或是发生改变。

    下面是廖雪峰 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 操作。

    17 条回复    2017-02-24 16:52:07 +08:00
    PanameraM
        1
    PanameraM  
    OP
       2017 年 2 月 23 日
    为什么要在找到符合条件的 key-value 对并将其放入新的字典 mapping 后,还要将这些 key-value 对从 attrs 字典中去除?也就是在执行下面的操作时
    ```
    for k in mappings.keys():
    attrs.pop(k)
    ```
    crossmaya
        2
    crossmaya  
       2017 年 2 月 23 日
    请看一下 dir 函数。
    binux
        3
    binux  
       2017 年 2 月 23 日
    如果不把这些属性从属性列表里面拿走,你访问 user.id 的时候不是就访问到 IntegerField('id') 了吗
    LittleKey
       
    LittleKey  
       2017 年 2 月 23 日 via Android
    因为__getattr__是在__getattribute__没找到时调用的吧,而 getattr 里会调用__getattribute__去找。
    所以如果你不删掉的话就像 @binux 说的一样了
    enenaaa
        5
    enenaaa  
       2017 年 2 月 23 日
    Model 继承于 dict 。 dict 里键值对不等于类定义的属性。
    getattr 函数优先从类定义里面找, 找不到后才调用__getattr__。
    在__getattr__打个 log 就看出来了。
    PanameraM
        6
    PanameraM  
    OP
       2017 年 2 月 23 日
    @enenaaa 请问为什么 getattr 函数优先从类定义里面找呢?
    PanameraM
        7
    PanameraM  
    OP
       2017 年 2 月 23 日
    @LittleKey 请问,为什么 getattr 函数会先调用__getattribute__去找呢?
    enenaaa
        8
    enenaaa  
       2017 年 2 月 23 日
    @Tianny
    用 getattr 函数或 a.b 形式访问类和对象的属性时, 先从类定义(__dict__中)获取,找不到的话则调用 __getattr__。

    对于 dict , 则是另外一种机制,以键取值用 a['b']的形式。
    这是两种不同的机制。 Model 类将对象 a.b 形式的操作转换为 a['b'], 想到这一点, 就不难理解了吧。
    enenaaa
        9
    enenaaa  
       2017 年 2 月 23 日
    Model 类里 self.b 和 self['b'] 是两个不同的变量。 他们保存在不同的表里。
    zhuangzhuang1988
        10
    zhuangzhuang1988  
       2017 年 2 月 23 日
    python ORM/metaclass 推荐看这个,
    http://www.dabeaz.com//py3meta/index.html
    一个 ppt 解决你大部分问题
    PanameraM
        11
    PanameraM  
    OP
       2017 年 2 月 24 日
    @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.
    PanameraM
        12
    PanameraM  
    OP
       2017 年 2 月 24 日
    @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 这个属性吗?
    求解释,非常感谢!
    PanameraM
        13
    PanameraM  
    OP
       2017 年 2 月 24 日
    @binux 请问为什么 如果不进行 pop 操作,就会访问到 IntegerField('id') 。"getattr(self, k, None)"这个代码的 getattr 是怎么调用的?不理解,希望您能详解给我讲解下。非常感谢!
    binux
        14
    binux  
       2017 年 2 月 24 日
    enenaaa
        15
    enenaaa  
       2017 年 2 月 24 日   1
    @Tianny pop 之后, 原先定义的 id 没有了。 Model 类又定义了__setattr__函数, 里面把键值对存到了 dict 类的表里, 而不是 User.__dict__里, dir 函数就列不出来了。
    就像 dir(dict) 不能列出 dict 对象存储的键值对一样。
    dict 是 C 写的内置对象, 里面的键值对用哈希表存储。
    User.__dict__是类对象用来存储属性的一个 dict 。 而 Model 类本身继承于 dict , 所以这里用到了两个 dict 。
    PanameraM
        16
    PanameraM  
    OP
       2017 年 2 月 24 日
    @binux 3q 懂了!
    PanameraM
        17
    PanameraM  
    OP
       2017 年 2 月 24 日
    @enenaaa 非常感谢!懂了!
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2964 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 47ms UTC 12:59 PVG 20:59 LAX 05:59 JFK 08:59
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86