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
Tianny
V2EX  ›  Python

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

  •  2
     
  •   Tianny · 2017-02-23 01:23:15 +08:00 · 3432 次点击
    这是一个创建于 2837 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    对于 dict , 则是另外一种机制,以键取值用 a['b']的形式。
    这是两种不同的机制。 Model 类将对象 a.b 形式的操作转换为 a['b'], 想到这一点, 就不难理解了吧。
    enenaaa
        9
    enenaaa  
       2017-02-23 13:03:21 +08:00
    Model 类里 self.b 和 self['b'] 是两个不同的变量。 他们保存在不同的表里。
    zhuangzhuang1988
        10
    zhuangzhuang1988  
       2017-02-23 13:09:27 +08:00
    python ORM/metaclass 推荐看这个,
    http://www.dabeaz.com//py3meta/index.html
    一个 ppt 解决你大部分问题
    Tianny
        11
    Tianny  
    OP
       2017-02-24 00:52:35 +08:00
    @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.
    Tianny
        12
    Tianny  
    OP
       2017-02-24 01:03:35 +08:00
    @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 这个属性吗?
    求解释,非常感谢!
    Tianny
        13
    Tianny  
    OP
       2017-02-24 01:17:53 +08:00
    @binux 请问为什么 如果不进行 pop 操作,就会访问到 IntegerField('id') 。"getattr(self, k, None)"这个代码的 getattr 是怎么调用的?不理解,希望您能详解给我讲解下。非常感谢!
    binux
        14
    binux  
       2017-02-24 02:23:15 +08:00
    enenaaa
        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 。
    Tianny
        16
    Tianny  
    OP
       2017-02-24 16:45:36 +08:00
    @binux 3q 懂了!😄
    Tianny
        17
    Tianny  
    OP
       2017-02-24 16:52:07 +08:00
    @enenaaa 非常感谢!懂了!😄
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1524 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 17:16 · PVG 01:16 · LAX 09:16 · JFK 12:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.