一 什么是Descriptors 描述器,是指一个包含 绑定行为的 对象,对其属性的访问被描述器协议中所定义的方法覆盖。所定义的方法有__get()__
,__set()__
,__del()__
,如果某个类实现了这三个方法中的一个,那么该实例就被称作描述器。
定义形式如下:
1 2 3 4 5 descr.__get__(self, obj, type=None) -> value descr.__set__(self, obj, value) -> None descr.__delete__(self, obj) -> None
说明:
如果某一个对象定义了__set__()
或__delete__()
,则会称为数据描述器;如果仅定义了__get__()
方法,则被称为非数据描述器。
二 获取实例属性的方法 获取实例属性一般与__getattribute__()
,__getattr__()
,__get__()
方法有关,接下来依次学习。
1.__getattribute__()
内置方法
1 2 3 4 def __getattribute__ (self, *args, **kwargs ): """ Return getattr(self, name). """ pass
说明: 通过注释,我们可以了解到__getattribute__()
方法其实等价与getattr()
基于字符串的反射机制的函数。
2.举个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Test (object ): sss = 's' def __getattribute__ (self, item ): raise AttributeError('syz' ) def __getattr__ (self, item ): return item def __get__ (self, instance, owner ): return 'love' class Operation (object ): t = Test() m = Operation()print (m.t,'|' ,type (m.t)) test = Test()print (test.sss) print (getattr (test,'sss' ) == test.sss)
分析:
1.调用m.t
获取属性<==>m.__getattribute__(t)
方法<=>m.__dict__['t'].__get__(m,type(m))
。
1 2 3 4 5 6 7 8 def __getattribute__ (self, key ): "Emulate type_getattro() in Objects/typeobjectEmulate type_getattro() in Objects/typeobject.c" v = object .__getattribute__(self, key) if hasattr (v, '__get__' ): return v.__get__(None , self) return v
2.如果定义了__getattribute__()
方法,却在内部主动抛出了AttributeError,如果同时定义了__getattr__()
方法,则会被其捕获调用。
3.无论访问的是该对象存在还是不存在的属性或方法,首先都会去寻找是否定义了__getattribute__
方法,如果定义,则尽管属性存在,但还是返回__getattribute__()
返回的值。
4.__getattribute__()
方法<==>getattr()
方法
5.描述器:当一个类(Test)的实例作为另一个类(Operation)的属性,调用类(Operation)的属性(t),其实调用类(Test)实例的__get__()
方法,可能说的有点绕,结合代码看清晰点。
6.描述器会由__getattribute__()
调用,调用了__getattribute__()
,实际底层调用__dict__['t'].__get(m,type(m))__
。实例调用和类调用是有区别的。
7.描述器必须通过类的属性赋值,而不能通过__init__()
实例化产生。
8.总结调用时机和次序:
(1)如果访问一个属性,该属性是描述器,则调用描述器的__get__()
方法
(2)如果访问非描述器的属性,首先会调用__getattribute__()
方法,无论属性是否存在,只要定义了__getattribute__()
,则会返回__getattribute__()
的值。
(3)如果调用没有定义__getattribute__()
或者在__getattribute__()
中抛出了AttributeError异常,则会调用__getattr__()
方法
三 Property—描述器的应用之一 Property是描述器应用之一,Property有两种使用方式,一种是装饰器,一种是实例化形式。
仔细学习下Property发现很有意思!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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__)
例子
利用上面Python实现的等价Property,举个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Emm : def __init__ (self ): self._name = 'syz' @Property def name (self ): return self._name @name.setter def name (self, value ): self._name = value @name.deleter def name (self ): del self._name emm = Emm()print (emm.__dict__) print (emm.name) emm.name = 'zjw' print (emm.name) del emm.nameprint (emm.__dict__)
注:
上述Emm类中,实际上创建了三个Property描述器,分别添加fget属性,fset属性,fdel属性,这三个属性分别对应三个同名的方法,在对某个属性操作的时候,三个描述器分别会调用__get()__
,__set()__
,__del()__
。
总结:
描述器在property中是对同一个属性进行不同行为绑定的一种形式,如上个例子中,对_name
属性绑定get,set,del三种行为。
四 Django中FileField —-描述器的应用之一 很多框架的底层肯定也会涉及到众多的内置方法,正巧打算用FastDFS重写Django默认的文件存储系统,在重写过程中需要知道默认的存储系统是如何调用的,以及如何和Field字段产生关系。现在就来学习以下底层的源码。
这里我不打算贴出重写的Storage,一方面还没有完善好,我准备单独写一篇笔记来记录FastDfs重写的代码。
主要分析下FileField中的描述器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class FileField (Field ): attr_class = FieldFile descriptor_class = FileDescriptor def pre_save (self, model_instance, add ): file = super ().pre_save(model_instance, add) if file and not file._committed: file.save(file.name, file.file, save=False ) return file def contribute_to_class (self, cls, name, **kwargs ): super ().contribute_to_class(cls, name, **kwargs) setattr (cls, self.name, self.descriptor_class(self))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class FileDescriptor : """ The descriptor for the file attribute on the model instance. Return a FieldFile when accessed so you can write code like:: """ def __init__ (self, field ): self.field = field def __get__ (self, instance, cls=None ): if instance is None : return self if self.field.name in instance.__dict__: file = instance.__dict__[self.field.name] else : instance.refresh_from_db(fields=[self.field.name]) file = getattr (instance, self.field.name) if isinstance (file, str ) or file is None : attr = self.field.attr_class(instance, self.field, file) instance.__dict__[self.field.name] = attr elif isinstance (file, File) and not isinstance (file, FieldFile): file_copy = self.field.attr_class(instance, self.field, file.name) file_copy.file = file file_copy._committed = False instance.__dict__[self.field.name] = file_copy elif isinstance (file, FieldFile) and not hasattr (file, 'field' ): file.instance = instance file.field = self.field file.storage = self.field.storage elif isinstance (file, FieldFile) and instance is not file.instance: file.instance = instance return instance.__dict__[self.field.name] def __set__ (self, instance, value ): instance.__dict__[self.field.name] = value
说明:
1 2 3 In [44 ]: queryset[0 ].head_image Out[44 ]: <ImageFieldFile: group1/M00/00 /00 /wKgAaV9KO-mAHdRbAAAzLoMjwYM8236232>
顺便提一下,访问某个模型的字段,实际上访问的是field.name,因为model源码中_setattr(self, field.name, rel_obj)
,将真实的obj和field.name绑定起来。这一切其实在实例化Model的时候就做好了,绑定对应的字段名到真实的对象上。
总结 :
在Django框架中ImageFiled/FileField中使用到描述器,作用是替换原来的FieldFile实例为FileField实例,通过setattr()和描述器的__get()__
内置方法,修改instance.__dict__[self.field.name] = attr
为attr_class实例,即FieldFile文件对象。做了一个偷换对象的事情,正如注释中所说That was fun, wasn’t it?
总的来说,描述器的主要作用还是绑定某个属性的行为,利用协议的方法来覆盖掉对原始的属性的访问!