描述符

描述符

描述符也是面向进阶的一种,由于它的涉及比较广,所以单独讲。

一、描述符

描述符本质就是一个新式类,在这个新式类中,至少实现了__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__方法。在描述符里也规定了参数的赋值类型。

posted @ 2019-08-09 15:39  流浪代码  阅读(680)  评论(0编辑  收藏  举报