python descriptor的妙用

看bottle的源码时发现的。

import functools
import random

class cache(object):

    def __init__(self, func):
        functools.update_wrapper(self, func, updated=[])
        self.getter = func

    def __get__(self, obj, cls):
        value = self.getter(cls)
        setattr(cls, self.__name__, value)
        return value


class A(object):
    @cache
    def m(cls):
        return "this is a methods %d" % random.randint(1,9)

a = A()
print a.m



cache对象用__call__方法,所以可以作为一个decorator。将类A中方法m作为self对象的func属性。注意cache()返回产生的是一个对象,也就是被修改过的self,这个对象在类A中和名字m关联。也就是原来的方法m已经被这个对象替换掉了,当然m对象依然有一个指向原函数的引用。

然后产生一个类A的实例a,当第一次访问a的m属性的时候会调用m的__get__函数,也就是m是一个descriptor。在__get__函数的内部对A新建了一个和m同名的对象(同名的原因就是先开始用update_wrapper将方法m的__name__拷贝到了self的__name__),这个对象的值就是函数m的返回值。假设这里返回的是"this is a methods 3",之后的多次调用会发现函数返回的是一样的值。从而也就实现了方法变为对象的静态属性。

这里可以在__get__函数后定义__set__函数,这样m就变成一个data descriptor了,然后在用上面的方法会发现结果一样。因为这时候属性m还是在A.__dict__中。

如果把setattr(cls, self.__name__, value)换成setattr(object, self.__name__, value)。然后的每次引用m.a就会发现值不同,因为data descriptor在查找链中的优先级要比实例的原生属性高

python中有关属性访问的下划线方法

对象本身可以定义__getattribute__()、__getattr__()、__setattr__()、__delattr__()等方法,以决定存取属性的行为:
def __getattribute__(self, name)
def __getattr__(self, name)
def __setattr__(self, name, value)
def __delattr__(self, name)

 __getattribute__()最容易理解,一旦定义了这个方法,任何属性的寻找都会被拦截,即使是那些__xxx__的內建属性名。

 __getattr__()的作用,是作为寻找属性的最后一次机会,如果同時定义有__getattribute__()、__getattr__()在尋找特性時的順序是:
1 如果有定义__getattribute__(),则返回__getattribute__()的值
2 在产生的实例的类的__dict__中寻找是否有相符的属性名。如果找到且是一个descriptor,返回__get__ ()方法的值。如果是個非descriptor ,則進行第2步
3 在实例的__dict__中寻找是否有相符的属性名,如果有則返回值
4 在产生的实例的类的__dict__中寻找是否有相符的属性名。如果不是非descriptor則直接返回属性值。如果是个只读descriptor(此時一定是仅具有__get__()方法),則返回__get__()的值
5 如果实例有定义__getattr__(),则返回__getattr__()的值
6 如果实例沒有定义__getattr__(),则丟出AttributeError

取得属性順序记忆的原则是:实例的__getattribute__()、資料描述器的__get__()、實例的__dict__、非資料描述器的__get__()、實例的__getattr__()。

__setattr__()的作用,在於攔截所有對實例的特性設定,如果對實例有個設定特性的動作,則設定的順序如下:
如果有定義__setattr__()則呼叫,如果沒有進行下一步
在產生實例的類別上,看看__dict__是否有相符合的特性名稱。如果找到 且實際是個資料描述器,則呼叫描述器的__set__()方法(如果沒有__set__()方法則丟出AttributeError),如果不是則進行下一步
在實例的__dict__上設定特性與值

設定特性順序記憶的原則是:實例的__setattr__()、資料描述器的__set__()、實例的__dict__。

__delattr__()的作用,在於攔截所有對實例的特性設定,如果對實例有個刪除特性的動作,則刪除的順序如下:
如果有定義__delattr__()則呼叫,如果沒有進行下一步
在產生實例的類別上,看看__dict__是否有相符合的特性名稱。如果找到 且實際是個資料描述器,則呼叫描述器的__delete__()方法(如果沒有__delete__()方法則丟出AttributeError),如果不是資料描述器則進行下一步
在實例的__dict__上找看看有無相符合的特性名稱,如果有則刪除,如果沒有則丟出AttributeError

刪除特性順序記憶的原則是:實例的__delattr__()、資料描述器的__delete__()、實例的__dict__。

原始来源:http://caterpillar.onlyfun.net/Gossip/Python/AttrAttribute.html

python文章资源集合

IBM的py3000简介,但是也包括一些高级内容

http://www.ibm.com/developerworks/linux/library/l-python3-1/
http://www.ibm.com/developerworks/linux/library/l-python3-2/

python对象和属性
http://www.cafepy.com/article/python_types_and_objects/python_types_and_objects.html
http://www.cafepy.com/article/python_attributes_and_methods/python_attributes_and_methods.html
http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python

felix推荐的python必读文章
http://feilong.me/2011/01/recommended-entries-for-you-to-master-python

Python Tips, Tricks, and Hacks
http://www.siafoo.net/article/52
http://stackoverflow.com/questions/101268/hidden-features-of-python
有趣加实用,链接里还有几篇有价值的文章。

IBM的python元类编程教程
http://www.ibm.com/developerworks/linux/library/l-pymeta/index.html
http://www.ibm.com/developerworks/linux/library/l-pymeta2/
http://www.ibm.com/developerworks/linux/library/l-pymeta3/index.html、

python3的元类编程
http://www.artima.com/forums/flat.jsp?forum=106&thread=236234
http://www.artima.com/forums/flat.jsp?forum=106&thread=236260

Guido的文章
http://www.python.org/doc/essays/