第一次实际接触描述符是在我们项目中我们需要实现自己的Manger管理器从而在查询时加上特定条件,所以去看了Django ORM的源码就打开了对数据描述符的大门。
要入门描述符,我们首先要知道几个前提的知识:
- 什么是描述器?
- 什么是数据描述器?
- 什么是非数据描述器?
- 属性被调用时,属性访问的顺序?
01、什么是描述器
python官方的解释是:一般地,一个描述器是一个包含 “绑定行为” 的对象,对其属性的访问被描述器协议中定义的方法覆盖。这些方法有:__get__()
,__set__()
和 __delete__()
。如果某个对象中定义了这些方法中的任意一个,那么这个对象就可以被称为一个描述器。
通俗的讲,描述器其实就是一个类, 这个类至少要实现__get__()
,__set__()
和 __delete__()中的一个。
class Descriptor: def __init__(self, default): self.value = default self.attr_dict = dict() def __get__(self, instance, owner): # 在这里instance等于Digit的实例子, owner等于Digit return self.attr_dict[instance] def __set__(self, instance, value): self.attr_dict[instance] = value class Digit: num = Descriptor(0) def __init__(self, num): self.num = num
在上图代码中 Descriptor 类实现了__get__()
,__set__()
和 __delete__()中的一个,所以是一个描述器。
02、什么是数据描述器
数据描述器:只要实现了__get__()
,__set__()
和 __delete__()中的任意两个,就称为数据描述器。
03、什么是非数据描述器
非数据描述器: 只是实现了__get__()
,__set__()
和 __delete__()中的一个,就称为非数据描述器。
04、属性被调用时,属性访问的顺序
05、ORM中ManagerDescriper的实现
class ManagerDescriptor: def __init__(self, manager): self.manager = manager def __get__(self, instance, cls=None): if instance is not None: # 这里表示不允许用实例去调用onjects只能用类调用 raise AttributeError("Manager isn't accessible via %s instances" % cls.__name__) if cls._meta.abstract: raise AttributeError("Manager isn't available; %s is abstract" % ( cls._meta.object_name, )) if cls._meta.swapped: raise AttributeError( "Manager isn't available; '%s.%s' has been swapped for '%s'" % ( cls._meta.app_label, cls._meta.object_name, cls._meta.swapped, ) ) return cls._meta.managers_map[self.manager.name]
这里真的设计的真的很精妙,可能很多朋友看到这有点懵逼objects是什么?ManagerDescriptor是怎样被调用的,因为本节主讲描述器相关的知识,所以这些可去查询ORM相关的源码。
06、数据校验
当我做教学管理系统时,我们有一个学生类用于记录每个学生每科的成绩,我们要求记录记录的成绩不能是负数,这时我们学生类的代码可能如下:
class Student: def __init__(self, chinese, math): self.chinese = chinese if chinese >= 0 else 0 self.math = math if chinese >= 0 else 0
试想如果是这种写法的话,如果我们对于分数多几个校验条件,或者多几个科目,这个代码整体将会非常的臃肿和难看。
这时我们的脑海可能立马闪现出用python自带的property来装饰科目属性,就可以省去大量的重复代码了,这样就有我们的2.0代码
class Student:
def __init__(self, chinese, math):
self._chinese = None
self._math = None
self.chinese = chinese
self.math = math
@property
def math(self):
return self._math
@math.setter
def math(self, value):
if value >= 0:
self._math = value
else:
self._math = 0
@property
def chinese(self):
return self._chinese
@chinese.setter
def chinese(self, value):
if value >= 0:
self._chinese = value
else:
self._chinese = 0
c = Student(1, -1)
这时我们如果有多种校验时能让代码简洁很多,可是还是没能解决多科目的情况下重复代码的问题,并且带来一个额外的开销是要创建每个科目的额外变量来存储分数。
这时就是我们的描述器上场了。
class Score: def __init__(self): self.score = dict() def __get__(self, instance, owner): if not self.score[instance]: return 0 return self.score[instance] def __set__(self, instance, value): if value >= 0: self.score[instance] = value else: self.score[instance] = 0 class Student: chinese = Score() math = Score() def __init__(self, chinese, math): self.chinese = chinese self.math = math c = Student(1, -1) print(c.math)
这里我们用Score描述器去存储各科目的分数,完美的解决了多科目时的数据校验问题, 不过它也有局限性,这里能这样用的前提是, 每个科目的检验方式是相同的,不然就得新建一个描述器去储存分数值,在描述器中我们用instance作为字典的键,这是有一定局限性的,如果instance的类是一个list类型等可变类型就不能把instance作为键。
看到这里有没有发现,有点像ORM的Model类了,哈哈,自己去看看源码。
07、用描述器实现property
class Property: def __init__(self, fget=None, fset=None, fdelete=None): self.fget = fget self.fset = fset self.fdel = fdelete def __get__(self, instance, owner): if instance is None: return self if self.fget is None: raise AttributeError("unreadable attribute") return self.fget(instance) def __set__(self, instance, value): if self.fset is None: raise AttributeError("can't set attribute") self.fset(instance, value) def __delete__(self, instance): if self.fdel is None: raise AttributeError("can't delete attribute") self.fdel(instance) def getter(self, func): return type(self)(func, self.fset, self.fdel) def setter(self, func): return type(self)(self.fget, func, self.fdel) def deleter(self, func): return type(self)(self.fget, self.fset, func) class Student: def __init__(self, chinese, math): self._chinese = None self._math = None self.chinese = chinese self.math = math @Property def math(self): return self._math @math.setter def math(self, value): if value >= 0: self._math = value else: self._math = 0 @Property def chinese(self): return self._chinese @chinese.setter def chinese(self, value): if value >= 0: self._chinese = value else: self._chinese = 0 c = Student(1, -1) print(c.math) c.math = 1
上面代码中的Property是python代码的实现,发现它本质上用的就是描述器。
其他python底层方法实现可以参考官网https://docs.python.org/zh-cn/3/howto/descriptor.html