描述符
描述符 |
描述符也是面向进阶的一种,由于它的涉及比较广,所以单独讲。
一、描述符
描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议。
描述符的作用是用来代理另外一个类的属性,必须把描述符定义成一个类的类属性,不能定义到构造函数中。
描述符分为两种,一种是数据描述符:至少实现了__get__()和__set__();另一种是非数据描述符:没有实现__set__()。
1.描述符格式
class Foo: #在python3中任何类都是新式类,它只要实现了三种方法之一,那么这个类就被称作一个描述符 def __get__(self, instance, owner): #调用一个属性时触发 pass def __set__(self, instance, value): #为一个属性赋值时触发 pass def __delete__(self, instance): #采用del删除属性时触发 pass
2.描述符的使用
描述符该如何使用呢?具体代码如下:
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() #把描述符代理一个类的属性 def __init__(self,n): self.x = n b1 = Bar(10) b1.x = 11111 b1.x del b1.x
执行代码结果为:
我是set方法
我是set方法
我是get方法
我是delete方法
可以看出在实例化时也会触发__set__方法,因为在类Bar的初始化函数中self.x是被描述符代理的属性。那么对象b1中的属性字典中到底存不存在x这个值呢?具体代码如下所示:
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() def __init__(self,n): self.x = n b1 = Bar(10) print(b1.__dict__) b1.x = 11111 print(b1.__dict__)
执行结果为:
我是set方法
{}
我是set方法
{}
这是什么情况?无论是实例化操作还是赋值操作,在对象b1的属性字典中仍然为空。正是因为描述符的关系,它相当于把被描述符的类的调用属性操作、赋值操作、删除操作都赋予另一个类来实现,跟本身类并没有关系,当然这关系到优先级的问题。
3.描述符的优先级
我们要严格遵守优先级:类属性>数据描述符>实例属性>非数据描述符>找不到的属性触发__getattr__()。
(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.x=111 print(Bar.x) #结果为:111
上述代码打印结果仍为111,并没有执行数据描述符,是因为Bar类调用了本来要被描述符代理的属性x进行了修改。所有类属性的优先级比数据描述符高。
(2)数据描述符>实例属性的分析代码如下:
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() b1 = Bar() b1.x #结果为:我是get方法 b1.x = 111 #结果为:我是set方法
上述代码Foo类被定义成Bar类的类属性,即对Bar类进行实例化,实例属性只执行数据描述符的方法。说明了数据描述符的优先级高于实例属性。
(3)实例属性>非数据描述符的分析代码如下:
class Foo(): def __get__(self, instance, owner): print('我是get方法') class Bar: x = Foo() b1 = Bar() b1.x = 111 print(b1.__dict__) #结果为:{'x': 111}
上述代码可以看出实例给自己属性进行赋值操作,可以在实例的属性字典中找到,这说明了实例属性的优先级高于非数据描述符。
(4)非数据描述符>找不到的属性触发__getattr__()的分析代码如下:
class Foo(): def __get__(self, instance, owner): print('我是get方法') class Bar: x = Foo() def __getattr__(self, item): print('我是getattr方法') b1 = Bar() b1.x #结果为:我是get方法 b1.name #结果为:我是getattr方法
上述代码可以看出找得到时触发__get__方法,找不到就会触发__getattr__方法。说明了非数据描述符的优先级高于找不到的属性触发__getattr__()。
4.描述符的应用
描述符可以应用到哪些场合呢?我们就来举个例子,通过描述符机制来实现参数的赋值类型限制。即:
class Typed: def __init__(self,key,expected_type): self.key = key self.expected_type = expected_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.expected_type): raise TypeError('你传入的类型不是%s' %self.expected_type) instance.__dict__[self.key] = value def __delete__(self, instance): print('delete方法') 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('alex',18,6666.66) p2 = People(250,26,4568.55) #不符合赋值类型,抛出异常
上述代码对象p1是满足参数的赋值类型,所以会触发三次__set__方法。而对象p2不符合赋值类型,就会抛出异常。
5.propetry
一个静态属性property本质就是实现了__get__,__set__,__delete__三种方法。
propetry有两种用法,第一种即:
class Foo: @property #静态属性 def AAA(self): print('get的时候运行') @AAA.setter def AAA(self,value): print('set的时候运行',value) @AAA.deleter def AAA(self): print('delete的时候运行') f1 = Foo() f1.AAA f1.AAA='aaa' del f1.AAA
执行结果为:
get的时候运行
set的时候运行 aaa
delete的时候运行
上述代码中只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter。
第二种用法的代码实现如下:
class Foo: def get_AAA(self): print('get的时候运行') def set_AAA(self,value): print('set的时候运行',value) def del_AAA(self): print('delete的时候运行') AAA = property(get_AAA,set_AAA,del_AAA) #内置property三个参数与get,set,delete一一对应 f1 = Foo() f1.AAA f1.AAA='aaa' del f1.AAA
执行结果为:
get的时候运行
set的时候运行 aaa
delete的时候运行
上述两种用法的结果都是一样的。
我们也可以利用描述符自定制property,实现延时计算。我们先对一段代码进行分析,来看看我们利用描述符自定制property该如何实现相同的目的。即:
class Room: def __init__(self,name,width,length): self.name = name self.width = width self.length = length @property #area=property(area) def area(self): return self.width*self.length r1 = Room('别墅',15,16) print(r1.area) #结果为:240
在上述代码中@property相当于实现了area=property(area),而property它是一个类,这是不是相当于property类定义成Room类的类属性,这不就是描述符的性质吗?它既然是描述符,那么它是数据描述符还是非数据描述符呢?从结果它打印实例属性可以看出,它是非数据描述符,因为实例属性的优先级>非数据描述符。假如我们自定制property,不采用内置的静态属性property,该如何实现上述代码,即:
class Lazyproperty: def __init__(self,func): self.func = func def __get__(self, instance, owner): return self.func(instance) #instance实例本身 class Room: def __init__(self,name,width,length): self.name = name self.width = width self.length = length @Lazyproperty #area=Lazyproperty(area) def area(self): return self.width*self.length r1 = Room('别墅',15,16) print(r1.area) #结果为:240
上述代码中的@Lazyproperty是我自定制的,当然Lazyproperty类中我们只能定义成非数据描述符,否则不会执行area方法。
我们继续利用自定制property来实现延时计算功能:
class Lazyproperty: def __init__(self,func): self.func = func def __get__(self, instance, owner): print('get') if instance is None: #被类调用必须写,否则报错,因为类没有实例 return self res = self.func(instance) #instance实例本身 setattr(instance,self.func.__name__,res) #缓存 return res class Room: def __init__(self,name,width,length): self.name = name self.width = width self.length = length @Lazyproperty #area=Lazyproperty(area) def area(self): return self.width*self.length r1 = Room('别墅',15,16) print(r1.area) #从字典里先找,因为实例属性>非数据描述符,没有再去类的中找,然后出发了area的__get__方法 print(r1.area) #先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算
上述代码实现了延时计算功能,这样这不会每次都打印get的操作。描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性。
6.类的装饰器
类的装饰器与函数的装饰器性质是一样的,类的装饰器分为无参装饰器和有参装饰器。
我们先来定义一个无参的装饰器,即:
def deco(func): print('============') func.x = 1 func.y = 2 return func @deco #相当于Foo = deco(Foo) class Foo: pass print(Foo.__dict__)
执行结果为:
============ {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 1, 'y': 2}
上述代码当程序遇到@deco就马上执行了Foo=deco(Foo),可以在结果可以看出Foo类中的属性字典中有键值对x与y。
下面我们来介绍有参的装饰器该如何定义:
def Typed(**kwargs): def deco(obj): for key,val in kwargs.items(): setattr(obj,key,val) return obj return deco @Typed(x=1,y=2,z=3) #1.Typed(x=1,y=2,z=3)--->deco 2.@deco----->Foo=deco(Foo) class Foo: pass print(Foo.__dict__) @Typed(name='alex') #@deco---->Bar=deco(Bar) class Bar: pass print(Bar.name)
执行的结果为:
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 1, 'y': 2, 'z': 3} alex
上述代码定义了有参的装饰器,函数Typed是用来接收所有参数的。
下面我们利用装饰器的应用以及描述符来实现参数的赋值类型限制,即:
class Typed: def __init__(self,key,expected_type): self.key = key self.expected_type = expected_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.expected_type): raise TypeError('你传入的类型不是%s' %self.expected_type) instance.__dict__[self.key] = value def __delete__(self, instance): print('delete方法') instance.__dict__.pop(self.key) def deco(**kwargs): def wrapper(obj): for key,val in kwargs.items(): setattr(obj,key,Typed(key,val)) #Typed(key,val)描述符 return obj return wrapper @deco(name = str,age = int,salary = float) class People: def __init__(self,name,age,salary): self.name = name self.age = age self.salary = salary p1 = People('alex',20,6666.66)
实例化时触发了三次__set__方法。在描述符里也规定了参数的赋值类型。