Python descriptor

1) Definition
Any new style class that implements at least one of those method is called a descriptor:
__get__(self, instance, owner=None) -> value,
__set__(self, instance, value) -> None,
__delete__(self, instance) -> None (descriptor protocol).
We can think of a descriptor as an agent that presents object attributes, 

Descriptors which do not implement __set__ are called non-data descriptor or method descriptor, descriptors which implement both __get__, __set__ are called data descriptors.
The differences between data descriptor and non-data descriptor is "Data descriptors always override a redefinition in an instance dictionary. In contrast, non-data descriptor can be overridden by instances" which means "If an instance's dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence. If an instance's dictionary has an entry with the same name as a non-data descriptor, the dictionary entry takes precedence."

Properties, bound, unbound methods, static methods, class methods are all based on the descriptor protocol

2) Invoking descriptor
obj.x => if x is a descriptor and obj is a instance, then object.__getattribute__() translates obj.x into type(obj).__dict__['x'].__get__(obj, type(obj))
(1) type(obj) gets the class of this obj
(2) type(obj).__dict__['x'] is looking the attribute 'x' against the class of obj, it returns "x" descriptor
(3) type(obj).__dict__['x'].__get__(obj, type(obj)) is calling __get__ against the "x" descriptor

cls.x => if x is a descriptor and cls is a class, then type.__getattribute__() translates cls.x into cls.__dict__['x'].__get__(None, cls)
Pure python emulation
def __getattribute__(self, key):
    v = object.__getattribute__(self, key)
    if hasattr(v, '__get__'):
        return v.__get__(None, self)
    return v

The important points are:
(1) descriptors are invoked by the __getattribute__ method
(2) overriding __getattribute__ prevents automatic descriptor calls
(3) __getattribute__ is only available with new style classes and objects
(4) object.__getattribute__ and type.__getattribute__ make different calls to __get__
(5) data descriptors always override instance dictionaries
(6) non-data descriptors may be overridden by instance dictionaries

The object returned by super() also has a custom __getattribute__ method for invoking descriptors. The call super(B,obj).m() searches obj.__class__.__mro__ for the base class A immediately following B and then returns A.__dict__['m'].__get__(obj, A). If not a descriptor, m is returned unchanged. If not in the dictionary, m reverts to a search using object.__getattribute__.

(3) Example:

Non-data descriptor:
---
 class DefaultAlias(object):

     def __init__(self, name):
         self.name = name

     def __get__(self, inst, cls):

         if inst is None:
             return self
         return getattr(inst, self.name)

 class Book(object):
     def __init__(self, title, stitle=None):
         self.title = title
         if stitle is not None:
             self.short_title = stitle
     
short_title = DefaultAlias('title')

b = Book('Ghost')   # this book instance has a descriptor called
short_title (Note "stitle" parameter is None)
b.short_title is translated into type(b).__dict__['short_title'].__get__(b, type(b))
(1) type(b) => Book (class)
(2) type(b).__dict__['short_title'] => Book.__dict__['short_title'] => Book.short_title (the short_title descriptor)
(3) type(b).__dict__['short_title'].__get__(b, type(b)) => Book.short_title.__get__(b, Book) =>('title') DefaultAlias.__get__(b, Book)

Precedence explanation:
---
b2 = Book('Ghost', 'G')  # this book instance has a descriptor called short_title  and a attribute called short_title (string) (Note "stitle" parameter is not None)
b2.short_title => 'G', attribute of an instance precedes the Non-data descriptor

 class Alias(DefaultAlias):
     def __set__(self, inst, value):
         setattr(inst, self.name, value)

     def __delete__(self, inst):
         delattr(inst, self.name)

 class Book(object):
     def __init__(self, title, stitle=None):
         self.title = title
         if stitle is not None:
             self.short_title = stitle
     # If we uncomment out the following line, "self.short_title" in the above line (self.short_title = short_title)
     
# is actually the data descriptor in the following line. 

     #short_title = Alias('title')  

b3 = Book('Ghost', 'G') # this book instance only has a attribute called short_title (string) (Note "stitle" parameter is not None)
b3.short_title => 'G'
Book.short_title = Alias('title')  # assign the data descriptor which has the same name as the attribute "short_title", now we have both 'short_title' string attribute and data descriptor 
b3.short_title => type(b3).__dict__['short_title'].__get__(b3, type(b3)) => 'Ghost', data descriptor precedes attribute of instance
b3.__dict__['short_title'] => 'G' # explicitly access the attribute 'short_title' of instance b3

(4) How property() implements the descriptor protocol
class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self         
        if self.fget is None:
            raise AttributeError, "unreadable attribute"
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError, "can't set attribute"
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError, "can't delete attribute"
        self.fdel(obj)

---
backup contents, usage of "property"

class C(object):

    def __init__(self):

        self._x = None

    def getx(self):

        return self._x

    def setx(self, value):

        self._x = value

    def delx(self):

        del self._x

    x = property(getx, setx, delx,"I'm the 'x'property.")

OR

class C(object):

    def __init__(self): self._x =None

    @property

    def x(self):

        """I'm the 'x' property."""

        return self._x

    @x.setter

    def x(self, value):

        self._x = value

    @x.deleter

    def x(self):

        del self._x


Reference:
http://users.rcn.com/python/download/Descriptor.htm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值