【1.137】描述符__set__ __get__ __delete__
1、描述符是什么:
描述符本质就是一个新式类,
在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,
这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
2、
class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符 def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __delete__(self, instance): pass
3、描述符是干什么的:
描述符的作用
是用来 代理 另外一个 类的 属性的
(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
引子:描述符类产生的实例进行属性操作并不会触发三个方法的执行
class Foo: def __get__(self, instance, owner): print('触发get') def __set__(self, instance, value): print('触发set') def __delete__(self, instance): print('触发delete') #包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,
#并不会触发这三个方法 f1=Foo() f1.name='egon' f1.name del f1.name #疑问:何时,何地,会触发这三个方法的执行
描述符应用之何时?何地?
class Foo: def __set__(self, instance, value): print(instance) print(value) def __get__(self, instance, owner): print(instance) print(owner) def __delete__(self, instance): print(instance) f1 = Foo() f1.name = "abc" f1.name del f1.name class Name: name = Foo() def __init__(self,name): self.name = name n1 = Name("YAOMING") # 执行了 set 函数 # <__main__.Name object at 0x0000000001E968D0> # YAOMING print (n1.__dict__) # {} 执行的描述符 set set 什么也没有写 所以dict 就为空 print(Name.__dict__) # {'__module__': '__main__', # 'name': <__main__.Foo object at 0x00000000023D6828>, # '__init__': <function Name.__init__ at 0x00000000023A8D90>, # '__dict__': <attribute '__dict__' of 'Name' objects>, # '__weakref__': <attribute '__weakref__' of 'Name'objects>, # '__doc__': None} print(type(n1)) #<class '__main__.Name'> # type(obj) 就是看看n1是哪一个类来的 # print(type(n1).__dict__) == print(Name.__dict__) 他们结果一致的 Name.name = "No.1,优先级最大,不会执行set,因为类属性大于一切" print(Name.__dict__) #{'__module__': '__main__', # 'name': 'No.1,优先级最大,不会执行set,因为类属性大于一切', # '__init__': <function Name.__init__ at 0x0000000002168D08>, # '__dict__': <attribute '__dict__' of 'Name' objects>, # '__weakref__': <attribute '__weakref__' of 'Name' objects>, # '__doc__': None}
4、描述符 分为两种, 一种是数据描述符 一种是非数据描述符
如果描述符class中没有定义__get__()方法,那么访问描述符实例仅仅会返回一个描述符class的实例,而不会触发调用。
如果定义了__set__()和__delete__ ()其中之一就是一个数据描述符。
如果都没有定义,即只定义__get__,那么就是一个非数据描述符。
通常,数据描述符会同时定义__get__()和__set__(),非数据描述符仅定义__get__()。
其实,基础概念看到这里就可以了,弄清什么时候触发__get__()、什么时候触发__set__()即可。
数据描述 如果定义了__set__()和__delete__ ()其中之一就是一个数据描述符。
class Foo:
def __set__(self, instance, value):
print(instance)
print(value)
def __get__(self, instance, owner):
print(instance)
print(owner)
非数据描述符 如果都没有定义,即只定义__get__,那么就是一个非数据描述符。
class Foo:
def __get__(self, instance, owner):
print(instance)
print(owner)
5、注意事项:
一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()
class Foo: def __set__(self, instance, value): print(instance) print(value) def __get__(self, instance, owner): print(instance) print(owner) def __delete__(self, instance): print(instance) class Name: name = Foo() def __init__(self,name): self.name = name Name.name = "yaoming" print (Name.name) #yaoming print (Name.__dict__) #类属性 的优先级高于一切
class Foo: def __init__(self,name): self.name = name def __set__(self, instance, value): print(instance) print(value) instance.__dict__[self.name] = value def __get__(self, instance, owner): print(instance) # 对象 n1 print(owner) # 对象n1来至于哪一个类 return instance.__dict__[self.name] def __delete__(self, instance): print(instance) instance.__dict__.pop(self.name) class Name: name = Foo("name") def __init__(self,name): self.name = name #没有类属性 那就是数据描述符的优先级较高 n1 = Name("liuxiang") #会执行 set函数 print (n1.name) #liuxiang #会执行 get 函数 ,打印执行结果 ,没有return 返回为None print (n1.__dict__) #{'name': 'liuxiang'} del n1.name # 会执行 delete 函数 print (n1.__dict__) #{}
class Foo: #这是非数据描述符 只是定义了 get 方法 # def __init__(self,name): # self.name = name # # def __set__(self, instance, value): # print(instance) # print(value) # instance.__dict__[self.name] = value def __get__(self, instance, owner): print(instance) # 对象 n1 print(owner) # 对象n1来至于哪一个类 # return instance.__dict__[self.name] # def __delete__(self, instance): # print(instance) # instance.__dict__.pop(self.name) class Name: # name = Foo("name") name = Foo() def __init__(self,name): self.name = name # 实例属性高于 非数据描述 n1 = Name("yaoming") print(n1.name) #yaoming 执行的是实例属性 print(n1.__dict__) #{'name': 'yaoming'} del n1.name print(n1.__dict__) #{}
class Foo: def func(self): print('我胡汉三又回来了') f1=Foo() f1.func() #调用类的方法,也可以说是调用非数据描述符 #函数是一个非数据描述符对象(一切皆对象么) print(dir(Foo.func)) print(hasattr(Foo.func,'__set__')) print(hasattr(Foo.func,'__get__')) print(hasattr(Foo.func,'__delete__')) #有人可能会问,描述符不都是类么,函数怎么算也应该是一个对象啊,怎么就是描述符了 #笨蛋哥,描述符是类没问题,描述符在应用的时候不都是实例化成一个类属性么 #函数就是一个由非描述符类实例化得到的对象 #没错,字符串也一样 f1.func='这是实例属性啊' print(f1.func) del f1.func #删掉了非数据 f1.func()
class Foo: #这是非数据描述符 只是定义了 get 方法 # def __init__(self,name): # self.name = name # # def __set__(self, instance, value): # print(instance) # print(value) # instance.__dict__[self.name] = value def __get__(self, instance, owner): print(instance) # 对象 n1 print(owner) # 对象n1来至于哪一个类 # return instance.__dict__[self.name] # def __delete__(self, instance): # print(instance) # instance.__dict__.pop(self.name) class Name: # name = Foo("name") name = Foo() def __init__(self,name): self.name = name def __getattr__(self, item): #这个也是非数据描述符 print("找不到的就找 getattr。getattr 就是非数据描述符") n1 = Name("yaoming") print(n1.name) #yaoming print(n1.unknow) #找不到的就找 getattr。getattr 就是非数据描述符 # None getattr 返回None #如果 没有getattr 函数就会报错 unknow 找不到 #这里为什么 没有执行get 函数 因为 实例属性 优先级高于 非数据描述符
6、描述符 使用 如果要灵活 就是要搞定 参数化输入
,如期望的验证的值(name,age,salary 都要参数化为key)
,如期望的值得类型(int ,str ,float都要参数为 except_type)
1、基本格式
class Foo: #这是非数据描述符 只是定义了 get 方法 # def __init__(self,name): # self.name = name # # def __set__(self, instance, value): # print(instance) # print(value) # instance.__dict__[self.name] = value def __get__(self, instance, owner): print(instance) # 对象 n1 print(owner) # 对象n1来至于哪一个类 # return instance.__dict__[self.name] # def __delete__(self, instance): # print(instance) # instance.__dict__.pop(self.name) class Name: # name = Foo("name") name = Foo() def __init__(self,name): self.name = name def __getattr__(self, item): #这个也是非数据描述符 print("找不到的就找 getattr。getattr 就是非数据描述符") n1 = Name("yaoming") print(n1.name) #yaoming print(n1.unknow) #找不到的就找 getattr。getattr 就是非数据描述符 # None getattr 返回None #如果 没有getattr 函数就会报错 unknow 找不到 #这里为什么 没有执行get 函数 因为 实例属性 优先级高于 非数据描述符
class Str: def __init__(self,name): #为接收被描述对象传来的参数 定义一个init self.name = name def __set__(self, instance, value): print ("set方法") instance.__dict__[self.name]=value def __get__(self, instance, owner): print("get方法") if instance is None: # 因为用类获取name就会传来None 会报错 return self return instance.__dict__[self.name] def __delete__(self, instance): print("del方法") instance.__dict__.pop(self.name) class People: name = Str("name") #这里定义了 有参数的name 那在描述符 就有一个接收的初始化函数 init def __init__(self,name,age,salary): self.name =name self.age = age self.salary = salary p1 = People("yaoming",18,28.5) #set方法 get方法 print(People.name) #这里用类来调用name 那描述符中 instance 获取到的值就是None
class Str: def __init__(self,name): #为接收被描述对象传来的参数 定义一个init self.name = name def __set__(self, instance, value): print ("set方法") instance.__dict__[self.name]=value def __get__(self, instance, owner): print("get方法") if instance is None: # 因为用类获取name就会传来None 会报错 return self return instance.__dict__[self.name] def __delete__(self, instance): print("del方法") instance.__dict__.pop(self.name) class People: name = Str("name") #这里定义了 有参数的name 那在描述符 就有一个接收的初始化函数 init def __init__(self,name,age,salary): self.name =name self.age = age self.salary = salary p1 = People("yaoming",18,28.5) #set方法 get方法 print(People.name) #这里用类来调用name 那描述符中 instance 获取到的值就是None
class Str: def __init__(self,key,except_type): #为接收被描述对象传来的参数 定义一个init self.key = key self.except_type = except_type def __set__(self, instance, value): print ("set方法") if not isinstance(value,self.except_type): #判断给定值 是否是期待的类型 raise TypeError("%s except %s" %(self.key,self.except_type)) instance.__dict__[self.key] = value def __get__(self, instance, owner): print("get方法") if instance is None: # 因为用类获取name就会传来None 会报错 return self return instance.__dict__[self.key] def __delete__(self, instance): print("del方法") instance.__dict__.pop(self.key) class People: name = Str("name",str) age = Str("age",int) salary = Str("salary",float) #这里定义了 有参数的name 那在描述符 就有一个接收的初始化函数 init #这里增加了期望类型 str def __init__(self,name,age,salary): self.name =name self.age = age self.salary = salary p1 = People("yaoming",18,28.5) #set方法 get方法 # p1.name = 213 #报错 name 需要 str 类型的数据 # p1.age = "13" #报错age 需要 int 类型的数据 # p1.salary = 13 #报错salary 需要 float 类型的数据
2、如果像上面最后一种来使用 输入 类型限制 看起来也很不错了
但是在类里面还是要直接定义 多个 类的属性,显得很累赘
可以试一试 装饰器来简化重复
def deco(obj): print("只要有函数被我修饰,就会执行deco函数") return obj @deco def func(): print("被修饰的函数 func") func()
def deco(obj): print("只要有函数被我修饰,就会执行deco函数") return obj @deco #就会执行函数deco, func = deco(func) def func(): print("被修饰的函数 func") func() # 只要有函数被我修饰,就会执行deco函数 # 被修饰的函数 func @deco class Foo(): def __init__(self,name,age): self.name = name self.age = age f1 = Foo("yaoming",20) #只要有函数被我修饰,就会执行deco函数
def wapper(**kwargs): print("wai---->",kwargs) def deco(obj): print("只要有函数被我修饰,就会执行deco函数") print("nei---->",kwargs) return obj return deco @wapper(f1 = 1) #就会执行函数deco, func = wapper(f1=1) def func(): print("被修饰的函数 func") func() # wai----> {'f1': 1} # 只要有函数被我修饰,就会执行deco函数 # nei----> {'f1': 1} # 被修饰的函数 func @wapper(name = str,age = int) class Foo(): def __init__(self,name,age): self.name = name self.age = age f1 = Foo("yaoming",20) #wai----> {'name': <class 'str'>, 'age': <class 'int'>} # 只要有函数被我修饰,就会执行deco函数 # nei----> {'name': <class 'str'>, 'age': <class 'int'>}
class Typed(): def __init__(self,key,excpet_type): self.key = key self.excpet_type = excpet_type def __set__(self, instance, value): if not isinstance(value,self.excpet_type): raise TypeError("%s is not excpet_type: %s "%(value,self.excpet_type)) instance.__dict__[self.key] = value def __get__(self, instance, owner): return instance.__dict__[self.key] def __delete__(self, instance): instance.__dict__.pop(self.key) def wapper(**kwargs): def deco(obj): for key,val in kwargs.items(): setattr(obj,key,Typed(key,val)) return obj return deco @wapper(name = str,age = int,salary=float) class Foo(): def __init__(self,name,age,salary): self.name = name self.age = age self.salary = salary f1 = Foo("yaoming",20,30.3) print (f1.__dict__) # f1.name = 213 #报错 # f1.salary = "12" #报错 # f1.age = 3.2 #报错 del f1.salary print (f1.__dict__) #{'name': 'yaoming', 'age': 20} print (f1.age) #20
7、描述符再利用来重写功能
描述符 是可以实现 大部分python类 特性中的底层魔法,
包括@classmethod 类方法 ,@staticmethd 静态方法 ,@property 静态属性
甚至是__slots__属性
描述符是很多高级库和框架的重要工具之一,
描述符通常是使用到装饰器 或 元类的大型框架中的一个组件.
1、 利用描述符原理完成一个自定制@property,
实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)
class Room: def __init__(self,name,lenth,width): self.name = name self.lenth = lenth self.width = width @property def area(self): return ("%s's area is %s "%(self.name,self.lenth * self.width)) r1 = Room("kitchen",3,5) r2 = Room("toilet",3,1.8) print(r1.area) print(r2.area) # kitchen's area is 15 # toilet's area is 5.4
#让我们来重做 property # @ 的意思就是装饰 @property obi==property(obj) obj 为被修饰的对象 # 但这里又涉及到描述符 (要被描述符修饰,就又必须是类属性 也就 类属性名 = 描述函数(类属性名)) class Lazyproperty: def __init__(self,func): self.func = func def __get__(self, instance, owner): if instance is None: return self return self.func(instance) #这里就是实现 area(self) 这里的self 就是类产生的对象 class Room: #self 就是实例对象 r1或者r2 def __init__(self,name,lenth,width): self.name = name self.lenth = lenth self.width = width @Lazyproperty #area = Lazyproperty(area) 这里用了装饰器 ,然后产生了类属性,并用到了描述符 def area(self): return ("%s's area is %s "%(self.name,self.lenth * self.width)) r1 = Room("kitchen",3,5) #这就是self r2 = Room("toilet",3,1.8) #这就是self print(r1.area) print(r2.area) #kitchen's area is 15 # toilet's area is 5.4 #若果不用静态属性 就是把装饰器屏蔽 # 调用方法就是 r1.area() ()里面的self 自动传入的 print(r1.area())
#让我们来重做 property # @ 的意思就是装饰 @property obi==property(obj) obj 为被修饰的对象 # 但这里又涉及到描述符 (要被描述符修饰,就又必须是类属性 也就 类属性名 = 描述函数(类属性名)) class Lazyproperty: def __init__(self,func): self.func = func def __get__(self, instance, owner): if instance is None: return self else: value = self.func(instance) setattr(instance,self.func.__name__,value) return value #这里就是实现 area(self) 这里的self 就是类产生的对象 class Room: #self 就是实例对象 r1或者r2 def __init__(self,name,lenth,width): self.name = name self.lenth = lenth self.width = width @Lazyproperty #area = Lazyproperty(area) 这里用了装饰器 ,然后产生了类属性,并用到了描述符 def area(self): return ("%s's area is %s "%(self.name,self.lenth * self.width)) r1 = Room("kitchen",3,5) #这就是self # r2 = Room("toilet",3,1.8) #这就是self print (Room.__dict__) print (r1.__dict__) #{'name': 'kitchen', 'lenth': 3, 'width': 5} print(r1.area) print (Room.__dict__) print (r1.__dict__) #{'name': 'kitchen', 'lenth': 3, 'width': 5, 'area': "kitchen's area is 15 "} print(r1.area) # 来就从自己(实例)的属性里面来找, 找不到就去类里面的属性字典找,然后再触发area 然后再执行描述符的 get 函数 #若果找到了 就不再触发area 去执行get 函数 # print(r2.area) # kitchen's area is 15 # toilet's area is 5.4 #若果不用静态属性 就是把装饰器屏蔽 # 调用方法就是 r1.area() ()里面的self 自动传入的 print(r1.area())
#让我们来重做 property # @ 的意思就是装饰 @property obi==property(obj) obj 为被修饰的对象 # 但这里又涉及到描述符 (要被描述符修饰,就又必须是类属性 也就 类属性名 = 描述函数(类属性名)) # 用非数据描述符实现(只有get),但用了数据描述符(有set 和 delete之一) 就会每一次执行 lazyproperty ,可以用断点测试 class Lazyproperty: def __init__(self,func): self.func = func # def __set__(self, instance, value): # print("set方法") def __get__(self, instance, owner): if instance is None: return self else: value = self.func(instance) instance.__dict__[self.func.__name__]=value #setattr(instance,self.func.__name__,value) #上面的方式和这句话一样的效果 return value #这里就是实现 area(self) 这里的self 就是类产生的对象 # def __delete__(self, instance): # print("delete方法") class Room: #self 就是实例对象 r1或者r2 def __init__(self,name,lenth,width): self.name = name self.lenth = lenth self.width = width @Lazyproperty #area = Lazyproperty(area) 这里用了装饰器 ,然后产生了类属性,并用到了描述符 def area(self): return ("%s's area is %s "%(self.name,self.lenth * self.width)) r1 = Room("kitchen",3,5) #这就是self # r2 = Room("toilet",3,1.8) #这就是self print (Room.__dict__) print (r1.__dict__) #{'name': 'kitchen', 'lenth': 3, 'width': 5} print(r1.area) print (Room.__dict__) print (r1.__dict__) #{'name': 'kitchen', 'lenth': 3, 'width': 5, 'area': "kitchen's area is 15 "} print(r1.area) # 来就从自己(实例)的属性里面来找, 找不到就去类里面的属性字典找,然后再触发area 然后再执行描述符的 get 函数 #若果找到了 就不再触发area 去执行get 函数 # print(r2.area) # kitchen's area is 15 # toilet's area is 5.4 #若果不用静态属性 就是把装饰器屏蔽 # 调用方法就是 r1.area() ()里面的self 自动传入的 print(r1.area())
浙公网安备 33010602011771号