python field怎么理解_如何理解 Python 的 Descriptor?

  • Post author:
  • Post category:python


简单来讲,描述符就是一个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__方法

>