描述符(__get__, __set__, __delete__)
1. 描述符是什么:
描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议。
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
def __get__(self, instance, owner):
pass
def __set__(self, instance, value):
pass
def __delete__(self, instance):
pass
2. 描述符是干什么的:
描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数__init__中)
2.1 引子:描述符类产生的实例进行属性操作并不会触发三个方法的执行
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 = 'lebron'
f1.name
del f1.name
#疑问:何时,何地,会触发这三个方法的执行
2.2 描述符应用之何时?何地?
#描述符Str
class Str:
def __get__(self, instance, owner):
print('Str调用')
def __set__(self, instance, value):
print('Str设置...')
# instance.__dict__[name] = value 加上这一行代码,则将会在被描述符代理的对象中的属性字典里面加上‘name’= value
def __delete__(self, instance):
print('Str删除...')
#描述符Int
class Int:
def __get__(self, instance, owner):
print('Int调用')
def __set__(self, instance, value):
print('Int设置...')
def __delete__(self, instance):
print('Int删除...')
class People:
name = Str() # name被Str类代理
age = Int() # age被Int类代理
def __init__(self, name, age):
self.name = name
self.age = age
#何地?:定义成另外一个类的类属性
#何时?:且看下列演示
p1=People('alex',18)
#描述符Str的使用
p1.name
p1.name = 'egon'
del p1.name
#描述符Int的使用
p1.age
p1.age = 18
del p1.age
#我们来看到底发生了什么
print(p1.__dict__)
print(People.__dict__)
#补充
print(type(p1) == People) # type(obj)其实是查看obj是由哪个类实例化来的
print(type(p1).__dict__ == People.__dict__)
>>>
Str设置...
Int设置...
Str调用
Str设置...
Str删除...
Int调用
Int设置...
Int删除...
{}
{'__module__': '__main__', 'name': <__main__.Str object at 0x00000190E6027748>, 'age': <__main__.Int object at 0x00000190E6027780>, '__init__': <function People.__init__ at 0x00000190E6042048>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
True
True
3. 描述符分两种
3.1 数据描述符:至少实现了__get__()和__set__()
class Foo:
def __set__(self, instance, value):
print('set')
def __get__(self, instance, owner):
print('get')
3.2 非数据描述符:没有实现__set__()
class Foo:
def __get__(self, instance, owner):
print('get')
4.注意事项
一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数__init__中
三 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()
类属性 > 数据描述符
#描述符Str
class Str:
def __get__(self, instance, owner):
print('Str调用')
def __set__(self, instance, value):
print('Str设置...')
def __delete__(self, instance):
print('Str删除...')
class People:
name = Str()
def __init__(self, name, age): # name被Str类代理,age被Int类代理,
self.name = name
self.age = age
#基于上面的演示,我们已经知道,在一个类中定义描述符它就是一个类属性,存在于类的属性字典中,而不是实例的属性字典
#那既然描述符被定义成了一个类属性,就可直接通过类名调用
People.name # 调用类属性name,本质就是在调用描述符Str,触发了__get__()
People.name = 'egon' # 那赋值呢,并没有触发__set__()
del People.name # del也没有触发__delete__()
'''
原因:描述符在使用时被定义成另外一个类的类属性,因而类属性比二次加工的描述符伪装而来的类属性有更高的优先级
People.name #调用类属性name,找不到就去找描述符伪装的类属性name,触发了__get__()
People.name='egon' #那赋值呢,直接赋值了一个类属性,它拥有更高的优先级,相当于覆盖了描述符,肯定不会触发描述符的__set__()
del People.name #同上
'''
数据描述符 > 实例属性
#描述符Str
class Str:
def __get__(self, instance, owner):
print('Str调用')
def __set__(self, instance, value):
print('Str设置...')
def __delete__(self, instance):
print('Str删除...')
class People:
name = Str()
def __init__(self, name, age): #name被Str类代理,age被Int类代理,
self.name = name
self.age = age
p1 = People('egon', 18) # 触发__set__
#如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,于p1本身无关了,相当于覆盖了实例的属性
p1.name = 'egonnnnnn' # 触发__set__
p1.name # 触发__get__
print(p1.__dict__) # 实例的属性字典中没有name,因为name是一个数据描述符,优先级高于实例属性,查看/赋值/删除都是跟描述符有关,与实例无关了
del p1.name # 触发__delete__
实例属性 > 非数据描述符
#非数据描述符Str
class Str:
def __get__(self, instance, owner):
print('Str调用')
class People:
name = Str()
def __init__(self, name, age): # name被Str类代理,age被Int类代理,
self.name = name
self.age = age
p1 = People('chen', 18) # 不会触发__set__
# Str是一个非数据描述符,因为name=Str()而Str没有实现__set__方法,因而比实例属性有更低的优先级
# 对实例的属性操作,触发的都是实例自己的
p1.name # 不会触发__get__
print(p1.__dict__) # {'name': 'chen', 'age': 18}
del p1.name # 不会触发__delete__
5 描述符使用
众所周知,python是弱类型语言,即参数的赋值没有类型限制,下面通过描述符机制来实现类型限制功能
class Typed:
def __init__(self, key, type):
self.key = key
self.type = type
def __get__(self, instance, owner):
if not instance:
return self #类调用方法时,传入的instance为空
return instance.__dict__[self.key]
def __set__(self, instance, value):
if not isinstance(value, self.type):
raise TypeError('传入的不是%s' % self.type)
instance.__dict__[self.key] = value
def __delete__(self, instance):
instance.__dict__.pop(self.key)
class People:
name = Typed('name', str)
age = Typed('age', int)
salary = Typed('salary', float)
def __init__(self, name, age, salary):
self.name = name
self.age = age
self.salary = salary
p1 = People('chen_jian', 25, 250000.1)
p2 = People('CHEN', 22, 14000.1)
print(p1.__dict__)
p1.name = 'CHENJINA'
print(p1.__dict__)
>>>
{'name': 'chen_jian', 'age': 25, 'salary': 250000.1}
{'name': 'CHENJINA', 'age': 25, 'salary': 250000.1}
6. 类的装饰器
上述案例通过描述符已经能实现功能了,但是问题是,如果类有很多属性,仍然采用在定义一堆类属性的方式去实现,就会有很多重复代码,可以利用类的装饰器来解决
#函数的装饰器
def deco(func):
print('函数的装饰器--------->')
return func
@deco #等同于Foo = deco(test)
def test():
pass
#类似地有类的装饰器
def deco1(obj):
print('类的装饰器---------->')
obj.x = 23
obj.y = 6
obj.z = 23
return obj
@deco1 #等同于Foo = deco1(Foo)
class Foo:
pass
print(Foo.__dict__)
>>>
函数的装饰器--------->
类的装饰器---------->
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 23, 'y': 6, 'z': 23}
类的有参装饰器
def typed(**kwargs):
def deco(obj):
for key, value in kwargs.items():
#obj.__dict__[key] = value 未知错误
setattr(obj, key, value)
return obj
return deco
@typed(x=23, y=6, z=24 )
class Foo:
pass
print(Foo.__dict__)
@typed(name = 'lebron_james')
class Name:
pass
print(Name.__dict__)
>>>
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 23, 'y': 6, 'z': 24}
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Name' objects>, '__weakref__': <attribute '__weakref__' of 'Name' objects>, '__doc__': None, 'name': 'lebron_james'}
参数赋值类型限制的终极版
class Typed:
def __init__(self, key, type):
self.key = key
self.type = type
def __get__(self, instance, owner):
if not instance:
return self
return instance.__dict__[self.key]
def __set__(self, instance, value):
if not isinstance(value, self.type):
raise TypeError('传入的不是%s' % self.type)
instance.__dict__[self.key] = value
def __delete__(self, instance):
instance.__dict__.pop(self.key)
def deco(**kwargs): #kwarge={'name'=str,'age'=int,'salary'=float}
def add(obj):
for key, value in kwargs.items(): #kwargs.items=(('name','str'),('age','int'),('salary','float''))
setattr(obj, key, Typed(key, value)) #等同于setattr(People, name/*/*, Typed('name', str))
return obj
return add
@deco(name = str, age = int, salary = float) #等同于先运行deco(),再@add --->People=add(People)
class People:
# name = Typed('name', str)
# age = Typed('age', int)
# salary = Typed('salary', float)
def __init__(self, name, age, salary):
self.name = name
self.age = age
self.salary = salary
p1 = People('chen_jian', 25, 250000.1)
p2 = People('CHEN', 22, 14000.1)
print(p1.__dict__)
print(p2.__dict__)
>>>
{'name': 'chen_jian', 'age': 25, 'salary': 250000.1}
{'name': 'CHEN', 'age': 22, 'salary': 14000.1}
6 描述符总结
描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod, @staticmethd, @property甚至是__slots__属性
描述符是很多高级库和框架的重要工具之一, 描述符通常是使用到装饰器或者元类的大型框架中的一个组件.
7 利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)
#非数据描述符
class Lazyproperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
if not instance:
return self #当Room调用area方法时,传入的instance为空,此时返回值为Lazyproperty的对象
setattr(instance, self.func.__name__, self.func(instance)) # self.func.__name__可取得area的函数名,这一步可将计算得到的值添加到r1的属性字典中
return self.func(instance)
class Room:
def __init__(self, name, width, length):
self.name = name
self.width = width
self.length = length
@Lazyproperty # area = Lazyproperty(area) ,给类增加描述符, area被lazyproperty的对象代理
def area(self):
return self.width * self.length
r1 = Room('主卧', 9, 8)
print(r1.area)
print(r1.__dict__) # 第一次计算后,将'area':72直接放入r1的属性字典
print(r1.area) # r1再调用area方法时, 直接从自己的属性字典里面拿值,而不用再去触发装饰器Lazyproperty的get方法
print(r1.area)
print(Room.area)
>>>
72
{'name': '主卧', 'width': 9, 'length': 8, 'area': 72}
72
72
<__main__.Lazyproperty object at 0x0000017BC0DC69E8>
不将描述符设置为数据描述符的原因
class Lazyproperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
print('每次计算都来找get方法')
if not instance:
return self #当Room调用area方法时,传入的instance为空,此时返回值为Lazyproperty的对象
setattr(instance, self.func.__name__, self.func(instance)) # self.func.__name__可取得area的函数名,这一步可将计算得到的值添加到r1的属性字典中
return self.func(instance)
def __set__(self, instance, value):
pass
class Room:
def __init__(self, name, width, length):
self.name = name
self.width = width
self.length = length
@Lazyproperty # area = Lazyproperty(area) ,给类增加描述符, area被lazyproperty的对象代理
def area(self):
return self.width * self.length
r1 = Room('主卧', 9, 8)
print(r1.area)
print(r1.__dict__) # 第一次计算后,将'area':72直接放入r1的属性字典
print(r1.area) # r1再调用area方法时, 由于数据描述符优先级比实例高,所以又要去触发描述符的get方法
print(r1.area)
print(Room.area)
>>>
每次计算都来找get方法
72
{'name': '主卧', 'width': 9, 'length': 8}
每次计算都来找get方法
72
每次计算都来找get方法
72
每次计算都来找get方法
<__main__.Lazyproperty object at 0x0000020F87AC6A90>
8 利用描述符原理完成一个自定制@classmethod
class ClassMethod:
def __init__(self,func):
self.func=func
def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
def feedback():
print('在这里可以加功能啊...')
return self.func(owner)
return feedback
class People:
name='linhaifeng'
@ClassMethod # say_hi=ClassMethod(say_hi)
def say_hi(cls):
print('你好啊,帅哥 %s' %cls.name)
People.say_hi()
p1=People()
p1.say_hi()
#疑问,类方法如果有参数呢,好说,好说
class ClassMethod:
def __init__(self,func):
self.func=func
def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
def feedback(*args,**kwargs):
print('在这里可以加功能啊...')
return self.func(owner,*args,**kwargs)
return feedback
class People:
name='linhaifeng'
@ClassMethod # say_hi=ClassMethod(say_hi)
def say_hi(cls,msg):
print('你好啊,帅哥 %s %s' %(cls.name,msg))
People.say_hi('你是那偷心的贼')
p1=People()
p1.say_hi('你是那偷心的贼')
9 利用描述符原理完成一个自定制的@staticmethod
class StaticMethod:
def __init__(self,func):
self.func=func
def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
def feedback(*args,**kwargs):
print('在这里可以加功能啊...')
return self.func(*args,**kwargs)
return feedback
class People:
@StaticMethod# say_hi=StaticMethod(say_hi)
def say_hi(x,y,z):
print('------>',x,y,z)
People.say_hi(1,2,3)
p1=People()
p1.say_hi(4,5,6)
10. 再看property
用法一
class Foo:
@property
def AAA(self):
print('get的时候运行我啊')
@AAA.setter
def AAA(self,value):
print('set的时候运行我啊')
@AAA.deleter
def AAA(self):
print('delete的时候运行我啊')
#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA
>>>
get的时候运行我啊
set的时候运行我啊
delete的时候运行我啊
用法二
class Foo:
def get_AAA(self):
print('get的时候运行我啊')
def set_AAA(self, value):
print('set的时候运行我啊')
def delete_AAA(self):
print('delete的时候运行我啊')
AAA = property(get_AAA, set_AAA, delete_AAA) # 内置property三个参数与get,set,delete一一对应
f1=Foo()
f1.AAA
f1.AAA = 'aaa'
del f1.AAA
>>>
get的时候运行我啊
set的时候运行我啊
delete的时候运行我啊
案例一
class Goods:
def __init__(self):
# 原价
self.original_price = 100
# 折扣
self.discount = 0.8
@property
def price(self):
# 实际价格 = 原价 * 折扣
new_price = self.original_price * self.discount
return new_price
@price.setter
def price(self, value):
self.original_price = value
@price.deleter
def price(self):
del self.original_price
obj = Goods()
obj.price # 获取商品价格
obj.price = 200 # 修改商品原价
print(obj.price)
del obj.price # 删除商品原价
>>>
160.0
set------>
get------>
案例二
class People:
def __init__(self, name):
self.name = name
@property
def name(self):
return self.name
#p1 = People('alex') #property自动实现了set和get方法属于数据描述符,比实例属性优先级高,所以你这面写会触发property内置的set,抛出异常
class People:
def __init__(self, name):
self.name = name # 实例化就触发property
@property
def name(self):
# return self.name #无限递归
print('get------>')
return self.DouNiWan
@name.setter
def name(self, value):
print('set------>')
self.DouNiWan = value
@name.deleter
def name(self):
print('delete------>')
del self.DouNiWan
p1 = People('alex') # self.name实际是存放到self.DouNiWan里
print(p1.name)
print(p1.name)
print(p1.name)
print(p1.__dict__)
p1.name = 'egon'
print(p1.__dict__)
del p1.name
print(p1.__dict__)
>>>
alex
get------>
alex
get------>
alex
{'DouNiWan': 'alex'}
set------>
{'DouNiWan': 'egon'}
delete------>
{}