面向对象进阶(三)----------描述符

描述符(__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------>
{}

 

 

 

posted @ 2019-04-07 10:11  sword23  阅读(191)  评论(0编辑  收藏  举报