描述符`__get__,__set__, __delete__`

描述符__get__,__set__, __delete__

一、什么是描述符?

  • 描述符的本质就是一个新式类,在这个新式类中至少实现了__get__(), __set__(), __delete__()中的一个就称为描述符,也被称为描述符协议

    • __get__(self,inatance,owener) : 当访问一个属性的时候触发
    • __set__(self,instance,value) : 为一个属性赋值时触发
    • __delete__(self,instance) : 使用 del 删除一个属性的时候触发
    self描述符的对象
    instance使用描述符的对象
    owner设置了描述符的类(也就是instance的类)
    valueinstance的值
  • 定义一个描述符

    class MyDecriptor:
        def __get__(self,instance,owner):
            print("触发get~~~")
    
        def __set__(self, instance, value):
            print("触发set")
    
        def __delete__(self, instance):
            print("触发delete")
    
    
    #以下方法是描述符的实例进行的操作, 并不会触发上面三种方法,不要搞混了
    f1=MyDecriptor()
    f1.name="淘小欣"
    print(f1.name)
    del f1.name
    
  • 设置简单代理, 定义成另一个类的类属性

    #描述符一
    class Str:
        def __get__(self, instance, owner):
            print('--->触发 Str_get')
    
        def __set__(self, instance, value):
            print('--->触发 Str_set')
    
        def __delete__(self, instance):
            print('--->触发 Str_Delete')
    
    #描述符二
    class Int:
        def __get__(self, instance, owner):
            print("--->触发Int_get")
    
        def __set__(self, instance, value):
            print("————>触发Int_set")
    
        def __delete__(self, instance):
            print("---->触发Int_delete")
            
            
    #普通类
    class Person:
        name = Str()  # 将name给Str代理 (也可以说name是描述符对象)
        age = Int()   # 将age给Int代理  (也可以说age是描述符对象)
    
        def __init__(self, name, age):
            self.name = name  # 这里赋值就会触发描述符__set__方法
            self.age = age
            
    #示例对象
    P1=Person("淘小欣",3)
    '''输出结果
    --->触发 Str_set
    ———>触发 Int_set
    '''
    
    #查找属性
    print(P1.name)
    '''输出内容
    --->触发 Str_get
    None
    '''
    
    #查找属性
    print(P1.age)
    '''输出内容
    --->触发Int_get
    None
    '''
    
    #设置属性
    P1.name="派大星"
    '''输出内容
    --->触发 Str_set
    '''
    
    #查找属性
    print(P1.name)
    '''输出内容
    --->触发 Str_get
    None
    '''
    
  • 查找类与对象属性字典

    print(P1.__dict__)    # {} 为空
    
    print(Person.__dict__)
    '''从中取出了两个(name,age)
    'name': <__main__.Str object at 0x00000267479E7AC8>,
    'age': <__main__.Int object at 0x00000267479E7B08>
    '''
    
  • 小结

    • 只要类属性被描述符类代理了,以后使用 [对象] . [属性], 就会触发描述符类的__set__, __get__, __delete__方法
    • 并且被代理的属性只在类名称空间有,对象名称空间就没有给属性了

二、描述符是做什么的?

描述符的作用是用来代理另外一个类的属性的, 并且必须把描述符定义成这个类的类属性,不能定义到构造函数中

class Str:
    def __get__(self):
        pass
    
class Duck:
    name = Str()           # 正确 : name被Str代理
    def __init__(self):
        self.name = Str()  # 错误 : 只能代理类属性,不能代理对象属性
  • 代理属性小示例: 代理属性, 并限制传入数据的类型

    #数据描述符
    class Str:
    
        def __init__(self, agencyName, agencyType):
            '''
            :param agencyName: 被代理的属性名字
            :param agencyType: 被代理的属性类型
            '''
            self.name = agencyName
            self.type = agencyType
            # 将属性存在描述符的 __dict__ 里面
            self.date_dict = self.__dict__
    
        def __get__(self, instance, owner):
            return self.date_dict[instance]
    
        def __set__(self, instance, value):
            # 在这里可以进行属性类型的判断
            if isinstance(value, self.type):
                self.date_dict[instance] = value
            else:
                print("类型不正确")
    
        def __delete__(self, instance):
            del self.date_dict[instance]
            
            
    #普通类
    
    #普通类
    class Panda:
        name=Str("name",str)  #将描述符实例当做属性保存
        age=Str("age",int)
    
        def __init__(self,name,age):
            self.name=name
            self.age=age
    P1=Panda("淘小欣",18)
    print(P1.__dict__)    # {} 空的,因为全被代理了
    print(P1.name)     #淘小欣
    print(P1.age)       #18
    
    P1.name="派大星"
    P1.age=22
    
    print(P1.name)  #派大星
    print(P1.age)   #22
    print(P1.__dict__)  #{}
    
    #类型错误时
    P1.name=222 #类型不正确~~~
    P1.age="333"    #类型不正确~~~
    print(Panda.__dict__["name"].__dict__)  #{'name': 'name', 'type': <class 'str'>, 'date_dict': {...}, <__main__.Panda object at 0x000002079F577FA0>: '派大星'}
    
    

由上面示例我们可以知道, 一个类中被代理的属性将会保存在代理类(也就是描述符)的属性字典中, 而描述符对象又被当做属性保存在类的属性字典中

三、描述符的种类与属性查找优先级

  • 通常的只要定义了 __get__ 和 另一个或两个方法的类, 我们就称之为数据描述符
  • 如果一个类只定义了 __get__ 方法 (没有修改数据的功能) , 我们称之为非数据描述符

注意:

  1. 描述符本身应该定义成新式类,被代理的类也应该是新式类
  2. 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
  3. 要严格遵循该优先级,属性查找优先级
    • 无论有没有都会先触发 __getattribute__
    • 类属性
    • 数据描述符 (如果数据描述符中重写了__getattribute__可能会导致无法调用描述符)
    • 实例属性(对象属性)
    • 非数据描述符
    • 找不到触发 __getattr__() 的执行
  • 实例:类属性>数据描述符

    # 数据描述符
    class Str:
        def __get__(self, instance, owner):
            print("——————>触发 Str_get")
    
        def __set__(self, instance, value):
            print("----->触发Str__set")
    
        def __delete__(self, instance):
            print('--->触发 Str_Delete')
    
    
    # 普通类
    class Person:
        name = Str()
    
        def __init__(self, name):
            self.name = name
            Person.name = "淘小欣"  # 类的属性
    
    
    p = Person("name")  # ----->触发Str__set
    print(p.name)  # 淘小欣
    
    # 由上面的实验可知,最先找的是类属性
    
  • 实例:数据描述符>实例属性

    # 数据描述符
    class Str:
        def __get__(self, instance, owner):
            print("--->触发Str_get")
    
        def __set__(self, instance, value):
            print('--->触发 Str_set')
    
        def __delete__(self, instance):
            print('--->触发 Str_Delete')
    
    
    # 普通类
    class Person:
        name = Str()  # name是描述符对象
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
    
    P1 = Person("海大哥", 99)  # --->触发 Str_set
    
    P1.name = "海小弟"  # --->触发 Str_set
    del P1.name  # --->触发 Str_Delete
    print(P1.__dict__)  # {'age': 99} (查看对象属性字典,没有name属性)
    print(Person.__dict__)  # ['name'] (查看类属性字典,存在name,这个name就是描述符对象)
    
    
  • 实例:示例属性>非数据描述符

    # 以下比较并没有实际意义, 只是便于对描述符的理解
    # 非数据描述符
    class Str:
        def __get__(self, instance, owner):
            print("--->触发 Str__get")
    
        def __delete__(self, instance):
            print("---》触发Str__Delete")
    
    
    # 普通类
    class Person:
        name = Str()  # name是描述符对象
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
    
    P1 = Person("海老哥的小弟", 99)  # 抛出异常 : "AttributeError" 属性中没有__set__方法
    
    

四、python是强类型动态语言

  • 强类型:不同类型之间不允许直接运算

    res=1+"lili"  #python中不允许,类型转换后操作,在js中允许,Java:隐士类型转换,go语言也是强类型。
    
  • python是动态语言:

    • 动态语言----->解释型:js、php、python,

      不使用显示数据类型声明,声明变量不需要指定变量类型
      
    • **补充:**静态语言------>编译型:C、Java、go

案例(了解):

  • 众所周知,python是强类型语言,即参数的赋值没有类型限制,我们可以通过描述符来是实现类型限制功能
#代码一:
class Typed:
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            print("类型不匹配~~~")

        instance.__dict__[self.name] = value
        print(instance.__dict__)  # 打印对象的名称空间

    def __delete__(self, instance):
        instance.__dict__.pop(self.name)


class People:
    name = Typed('name', str)
    age = Typed('age', int)
    salary = Typed('salary', float)

    def __init__(self, name, age, salary):
        self.name = name  # 触发Typed类的__set__
        self.age = age
        self.salary = salary


# 限制类People中的属性, name:str, age:int  salary:float
p = People("淘小欣", 19, 101.1)
'''输出内容

{'name': '淘小欣'}
{'name': '淘小欣', 'age': 19}
{'name': '淘小欣', 'age': 19, 'salary': 101.1}
'''



#代码二,将age修改为str

class Typed:
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            print("类型不匹配~~~")
            return

        instance.__dict__[self.name] = value
        print(instance.__dict__)  # 打印对象的名称空间

    def __delete__(self, instance):
        instance.__dict__.pop(self.name)


class People:
    name = Typed('name', str)
    age = Typed('age', int)
    salary = Typed('salary', float)

    def __init__(self, name, age, salary):
        self.name = name  # 触发Typed类的__set__
        self.age = age
        self.salary = salary


# 限制类People中的属性, name:str, age:int  salary:float
p = People("淘小欣", "19", 101.1)
'''输出内容
{'name': '淘小欣'}
类型不匹配~~~
{'name': '淘小欣', 'salary': 101.1}
{'name': '淘小欣', 'salary': 101.1, 'alary': '99'}

'''

五、类的装饰器:无参 (跟描述符没有关系)

#普通函数
def decorate(cls):
    print('类的装饰器开始运行啦------>')
    return cls

@decorate   #类的装饰器     People=decorate(People)  仍旧是 People=People
class People:
    pass

People()        #等同于decorate(People)()--等同于-->People()

六、有参装饰器

def typeassert(**kwargs):
    def decorate(cls):
        print('类的装饰器开始运行啦------>', kwargs)
        return cls
    return decorate

@typeassert(name='淘小欣',age=19)  # 执行typeassert(name='lqz',age=19)--->decorate内层函数--->People=decorate(People)
class People:
    pass

p=People()

七、使用类装饰器+ 描述符,实现对对象类型的控制

class Typed:
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type

    def __get__(self, instance, owner):
        print('get--->', instance, owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print('set--->', instance, value)
        if not isinstance(value, self.expected_type):
            print('类型不匹配')
            return
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        print('delete--->', instance)
        instance.__dict__.pop(self.name)


def typeassert(**kwargs):
    def decorate(cls):
        print('类的装饰器开始运行啦------>', kwargs)
        for name, expected_type in kwargs.items():
            '''
            # 通过反射,把 name,Typed的对象,放到类中
            # People.'name'=Typed('name',str)
            # People.'age'=Typed('age',int)
            # People.'salary'=Typed('salary',float)
            本质就是如下
            name = Typed('name', str)
            age = Typed('age', int)
            salary = Typed('salary', float)
            '''
            setattr(cls, name, Typed(name, expected_type))

        return cls

    return decorate


#### 这种写法和下面的写法,效果,完全一致
@typeassert(
    name=str, age=int, salary=float
)  # 有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People)
class People:
    def __init__(self, name, age, salary):
        self.name = name  # 描述符类的 __set__
        self.age = age
        self.salary = salary


# 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
print(People.__dict__)  # {'name':Typed的对象(属性不一样),'age':Typed的对象,'salary':Typed的对象}

# 实例化得到一个People对象
p = People('淘小欣', 19, 100.1)

p.age = '15'  ## 描述符类的 __set__
p.age = 15
print(p.__dict__)

八、描述符总结

  • 描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是slots属性
  • 描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件

九、自定制@property

  • 利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)

property的使用

class Room:
    def __init__(self, name, width, length):
        self.name = name
        self.width = width
        self.length = length

    @property
    def area(self):
        return self.width * self.length


r1 = Room('淘小欣', 1, 1)
print(r1.area)  # 1

property的本质原理

  • 案例:自己定制一个个property装饰器

    class Lazyproperty:
        def __init__(self, func):
            self.func = func
    
        def __get__(self, instance, owner):
            # self.func是 Room类的area的内存地址,普通函数---需要传对象  area(instance)
            print('我被执行')
            return self.func(instance)  # 此时你应该明白,到底是谁在为你做自动传递self的事情
    
    
    class Room:
        def __init__(self, name, width, length):
            self.name = name
            self.width = width
            self.length = length
    
        @Lazyproperty  # area=Lazyproperty(area)--->Lazyproperty类的__init__执行,把area放到了Lazyproperty的对象中然后返回了-->area代指Lazyproperty的对象
        def area(self):
            return self.width * self.length
    
    
    r1 = Room('淘小欣', 2, 3)
    # r1.area是Lazyproperty的对象,类是描述符类---->r1.area会触发描述符类的 __get__方法
    print(r1.area)  # 触发描述符类的 __get__ 的执行
    print(r1.area)  # 触发描述符类的 __get__ 的执行
    print(r1.area)  # 触发描述符类的 __get__ 的执行
    
  • 案例:实现延迟计算功能

    #想缓存一下
    
    class Lazyproperty:
        def __init__(self, func):
            self.func = func
    
        def __get__(self, instance, owner):
            print('我被执行')
            res = self.func(instance)
            # instance是r1对象,向r1对象中放了一个 r1.'函数名字'=6
            instance.__dict__[self.func.__name__] = res
            # 相当于
            # instance.__dict__['area']=6
            return res
    
        # def __set__(self, instance, value):  # 加了它,变成数据描述符,查找顺序就不对了,不能加它
    
    
    class Room:
        def __init__(self, name, width, length):
            self.name = name
            self.width = width
            self.length = length
    
        @Lazyproperty  # area=Lazyproperty(area)--->Lazyproperty类的__init__执行,把area放到了Lazyproperty的对象中然后返回了-->area代指Lazyproperty的对象
        def area(self):
            return self.width * self.length
    
    
    r1 = Room('淘小欣', 2, 3)
    # r1.area是Lazyproperty的对象,类是描述符类---->r1.area会触发描述符类的 __get__方法
    print(r1.__dict__)
    print(r1.area)  # 触发描述符类的 __get__ 的执行
    print(r1.__dict__)  # {'name': '淘小欣', 'width': 2, 'length': 3, 'area': 6}
    
    print(r1.area)  # 直接先从对象自身找
    print(r1.area)  # 直接从对象自身找
    
    

十、自定制@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:
    def __init__(self, name):
        self.name = name

    @ClassMethod  # get_obj=ClassMethod(get_obj)--->ClassMethod的对象
    def get_obj(cls):
        print(cls)
        print('这是类的绑定方法')


# People.get_obj   # 触发 描述符类的__get__,--->返回feedback的内存地址
People.get_obj()  # 其实执行的是feedback()--->self.func--->People类的get_obj方法,self.func(类)--->get_obj(People传入了)

十一、自定制@staticmethod

class StaticMethod:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance,owner):
        def feedback():
            print('在这里可以加功能啊...')
            return self.func()

        return feedback

class People:
    def __init__(self,name):
        self.name=name

    @StaticMethod    # get_obj=StaticMethod(get_obj)--->StaticMethod的对象
    def get_obj():
        print('这是静态方法')


# People.get_obj # --->触发__get__执行---->feedback内存地址

People.get_obj()  # feedback()


#带参数的情况下
class StaticMethod:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance,owner):
        def feedback(*args,**kwargs):
            print('在这里可以加功能啊...')
            return self.func(*args,**kwargs)

        return feedback

class People:
    def __init__(self,name):
        self.name=name

    @StaticMethod    # get_obj=StaticMethod(get_obj)--->StaticMethod的对象
    def get_obj(a,b):
        print(a,b)
        print('这是静态方法')


# People.get_obj # --->触发__get__执行----feedback内存地址

People.get_obj(7,8)  # feedback()
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贾维斯Echo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值