简单来讲,描述符就是一个Python对象,但这个对象比较特殊,特殊性在于其属性的访问方式不再像普通对象那样访问,它通过一种叫描述符协议的方法来访问。这些方法包括__get__、__set__、__delete__。定义了其中任意一个方法的对象都叫描述符。举个例子:
普通对象
class Parent(object):name = ‘p’class Person(Parent):name = “zs”zhangsan = Person()zhangsan.name = “zhangsan”print zhangsan.name#>> zhangsan
普通的Python对象操作(get,set,delete)属性时都是在这个对象的__dict__基础之上进行的。比如上例中它在访问属性name的方式是通过如下顺序去查找,直到找到该属性位置,如果在父类中还没找到那么就抛异常了。通过实例对象的__dict__属性访问:zhangsan.__dict__[‘name’]
通过类型对象的__dict__属性访问:type(zhangsan).__dict__[‘name’] 等价于 Person.__dict__[‘name’]
通过父类对象的__dict__属性访问:zhangsan.__class__.__base__.__dict__[‘name’] 等价于 Parent.__dict__[‘name’]
类似地修改属性name的值也是通过__dict__的方式:
zhangsan.__dict__[‘name’] = ‘lisi’print zhangsan.name#>> lisi
描述符
class DescriptorName(object):def __init__(self, name):self.name = namedef __get__(self, instance, owner):print ‘__get__’, instance, ownerreturn self.namedef __set__(self, instance, value):print ‘__set__’, instance, valueself.name = valueclass Person(object):name = DescriptorName(‘zhangsan’)zhangsan = Person()print zhangsan.name#>>__get__ <__main__.person object at> #>>zhangsan
这里的DescriptorName就是一个描述符,访问Person对象的name属性时不再是通过__dict__属性来访问,而是通过调用DescriptorName的__get__方法获取,同样的道理,给name赋值的时候是通过调用__set__方法实现而不是通过__dict__属性。
zhangsan.__dict__[‘name’] = ‘lisi’print zhangsan.name#>>__get__ <__main__.person object at> #>>zhangsan#通过dict赋值给name但值并不是”lisi”,而是通过调用get方法获取的值zhangsan.name = “lisi”print zhangsan.name#>>__set__ <__main__.person object at> lisi#>>__get__ <__main__.person object at> #>>lisi
类似地,删除属性的值也是通过调用__delete__方法完成的。此时,你有没有发现描述符似曾相识,没错,用过Django就知道在定义model的时候,就用到了描述符。比如:
from django.db import models
class Poll(models.Model):
question = models.CharField(max_length=200)
pub_date = models.DateTimeField(‘date published’)
上面的例子是基于类的方式来创建描述符,你还可以通过property()函数来创建描述符,例如:
class Person(object):def __init__(self):self._email = Nonedef get_email(self):return self._emaildef set_email(self, value):m = re.match(‘\w+@\w+\.\w+’, value)if not m:raise Exception(’email not valid’)self._email = valuedef del_email(self):del self._email#使用property()函数创建描述符email = property(get_email, set_email, del_email, ‘this is email property’)>>> p = Person()>>> p.email>>> p.email = ‘dsfsfsd’Traceback (most recent call last):File “”, line 1, in File “test.py”, line 71, in set_emailraise Exception(’email not valid’)Exception: email not valid>>> p.email = ‘lzjun567@gmail.com’>>> p.email’lzjun567@gmail.com’>>>
property()函数返回的是一个描述符对象,它可接收四个参数:property(fget=None, fset=None, fdel=None, doc=None)fget:属性获取方法
fset:属性设置方法
fdel:属性删除方法
doc: docstring
采用property实现描述符与使用类实现描述符的作用是一样的,只是实现方式不一样。python里面的property是使用C语言实现的,不过你可以使用纯python的方式来实现property函数,如下:
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
if doc is None and fget is not None:
doc = fget.__doc__
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)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
留心的你发现property里面还有getter,setter,deleter方法,那他们是做什么用的呢?来看看第三种创建描述符的方法。使用@property装饰器
class Person(object):def __init__(self):self._email = None@propertydef email(self):return self._email@email.setterdef email(self, value):m = re.match(‘\w+@\w+\.\w+’, value)if not m:raise Exception(’email not valid’)self._email = value@email.deleterdef email(self):del self._email>>>>>> Person.email>>> p.email = ‘lzjun’Traceback (most recent call last):File “”, line 1, in File “test.py”, line 93, in emailraise Exception(’email not valid’)Exception: email not valid>>> p.email = ‘lzjun@gmail.com’>>> p.email’lzjun@gmail.com’>>>
发现没有,其实装饰器property只是property函数的一种语法糖而已,setter和deleter作用在函数上面作为装饰器使用。哪些场景用到了描述符
其实python的实例方法就是一个描述符,来看下面代码块:
>>> class Foo(object):
… def my_function(self):
… pass
…
>>> Foo.my_function
>>> Foo.__dict__[‘my_function’]
>>> Foo.__dict__[‘my_function’].__get__(None, Foo) #my_function函数实现了__get__方法
>>> Foo().my_function
>
>>> Foo.__dict__[‘my_function’].__get__(Foo(), Foo) #Foo的实例对象实现了__get__方法
>