python-get、set、delete内置属性(描述符)
可参考 https://www.cnblogs.com/Jimmy1988/p/6808237.html
一、描述符是什么?
描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问。这些方法有 __get__(), __set__(), 和__delete__()。如果这些方法中的任何一个被定义在一个对象中,这个对象就是一个描述符。
通俗说法就是一个新式类中的类属性的值是另一个新式类,且这个新式类实现了__get__(), __set__(), 和__delete__()
那这些方法何时、何地会被触发呢?
__get__():另一个新式类中调用属性时会触发
__set__():另一个新式类中属性赋值时触发
__delete__():另一个新式类中采用del删除属性时触发
class Foo: def __get__(self, instance, owner): print("get执行了") print("self:%s" %self) print("instance:%s" %instance) print("owner:%s" %owner) def __set__(self, instance, value): print("set执行了") def __delete__(self, instance): print("delete执行了") f = Foo() f.name = "gg" # 没有触发get方法 print(f.__dict__) del f.name # 没有触发del方法 print(f.__dict__)
运行结果
{'name': 'gg'}
{}
没有触发,到底是怎么回事呢?
class Foo: def __get__(self, instance, owner): print("get执行了") print("self:%s" %self) print("instance:%s" %instance) print("owner:%s" %owner) def __set__(self, instance, value): print("set执行了") def __delete__(self, instance): print("delete执行了") class Bar: x = Foo() # Bar属性被Foo代理 # 我们再获取属性看是否会触发 b = Bar() b.x
运行结果
get执行了 self:<__main__.Foo object at 0x0000000002117630> #其实就是Bar的属性x,因为x被Foo代理了,所以这里是Foo这个实例 instance:<__main__.Bar object at 0x0000000002117828> # 自己本身的实例对象 owner:<class '__main__.Bar'> # 实例的拥有者
那么set和del是怎么触发呢?
class Foo: cls_val = "en" def __init__(self,name): self.name = name def __get__(self, instance, owner): print("get执行了") # print("self:%s" %self) # print("instance:%s" %instance) # print("owner:%s" %owner) print("name:%s" %self.name) def __set__(self, instance, value): print("set执行了") def __delete__(self, instance): print("delete执行了") class Bar: x = Foo("x") # Bar属性被Foo代理 def __init__(self): self.y = Foo("y") b = Bar() print("#"*40) b.x = 100 print("#"*40) del b.x
运行结果
######################################## set执行了 ######################################## delete执行了
二、描述符用来干什么?
描述符的作用是用来代理另一个类的属性(必须把描述符定义为这个类的类属性,不能定义到构造函数中)
三、描述符分类
数据描述符:至少实现了__set__()和__get__()方法
非数据描述符:没有实现__set__()方法
四、注意事项
1、描述符本身定义为新式类,被代理的类也应该是新式类
2、必须把描述符定义为被代理类的类属性,不能定义到构造函数值
3、 严格遵循该优先级,优先级由高到低如下
类属性 >数据描述符>实例属性>非数据描述符>找不到(__getattr__())
类属性 >数据描述符
class Foo: def __get__(self, instance, owner): print("get执行了") def __set__(self, instance, value): print("set执行了") def __delete__(self, instance): print("delete执行了") class Bar: x = Foo() # Bar属性被Foo代理 print(Bar.x) # 查找x属性,然后发现是个描述符,于是触发了get Bar.x = 1 # 设置类属性,没有触发描述符,说明类属性>数据描述符 print(Bar.__dict__["x"]) # 类属性已经设置的值为1
运行结果
get执行了
None
1
数据描述符>实例属性
class Foo: def __get__(self, instance, owner): print("get执行了") def __set__(self, instance, value): print("set执行了") def __delete__(self, instance): print("delete执行了") class Bar: x = Foo() # Bar属性被Foo代理 b = Bar() b.x # 找实例b的属性,但是x属于已经被代理,是数据描述符,所以触发get b.x = 100 # 赋值实例b的属性,但是x属于已经被代理,是数据描述符,所以触发set del b.x #删除实例b的属性,但是x属于已经被代理,是数据描述符,所以触发del
运行结果
get执行了
set执行了
delete执行了
实例属性>非数据描述符
class Foo: def __get__(self, instance, owner): print("get执行了") class Bar: x = Foo() # Bar属性被Foo代理 b2 = Bar() b2.x #找到属性x是描述符,触发get print("#"*40) b = Bar() b.x = 900 # 找到属性x是描述符,但是没有set方法,所以实例x属性的值改为900。实例属性>非数据描述符
运行结果
get执行了 ########################################
五、描述符的应用
限制传入参数的数据类型
name为字符串类型
age为int类型
class Type: def __init__(self,key): self.key = key def __get__(self, instance, owner): print("运行get") return instance.__dict__[self.key] def __set__(self, instance, value): print("运行set") instance.__dict__[self.key] = value # 将实例的属性赋值 def __delete__(self, instance): print("运行delete") instance.__dict__.pop(self.key) class People: name = Type("name") def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary p1 = People("老顽童", 99, 1000) # 实例化,将"老顽童"传给形参name,name又赋值给实例属性name,而name被Tpye代理了,触发set方法 print(p1.__dict__) # 没有name属性,因为name被代理了 print(p1.name) # 触发get方法
运行结果
运行set {'name': '老顽童', 'age': 99, 'salary': 1000} 运行get 老顽童
被代理的属性,在设置、获取、删除时,会触发描述符(可以理解为一个代理,一个中介的角色),通过get、set、del方法然后回传给实例
那么我们在get、set、del加上一些判断逻辑,就可以实现实例添加属性时对数据类型进行限制的功能了
class Type: def __init__(self,key,data_type): self.key = key self.data_type = data_type def __get__(self, instance, owner): print("运行get") return instance.__dict__[self.key] def __set__(self, instance, value): print("运行set") if not isinstance(value,self.data_type): raise TypeError ("传入的%s不是%s类型" % (value,self.data_type)) instance.__dict__[self.key] = value # 将实例的属性赋值 def __delete__(self, instance): print("运行delete") instance.__dict__.pop(self.key) class People: name = Type("name",str) age = Type("age",int) def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary # p1 = People("老顽童", 99, 1000) # 实例化,将"老顽童"传给形参name,name有赋值给属性name,而name被Tpye代理了,触发set方法 # print(p1.__dict__) # 没有name属性,因为name被代理了 # print(p1.name) # 触发get方法 p1 = People("东方不败", "99", 1000)
运行结果
Traceback (most recent call last): 运行set File "D:/pythonproject/work/venv/myplan/day28/描述符应用.py", line 37, in <module> 运行set p1 = People("东方不败", "99", 1000) File "D:/pythonproject/work/venv/myplan/day28/描述符应用.py", line 29, in __init__ self.age = age File "D:/pythonproject/work/venv/myplan/day28/描述符应用.py", line 15, in __set__ raise TypeError ("传入的%s不是%s类型" % (value,self.data_type)) TypeError: 传入的99不是<class 'int'>类型
Type加入了data_type属性,设置数据类型,可以灵活的限制每个被代理属性的数据类型
浙公网安备 33010602011771号