【1.137】描述符__set__ __get__ __delete__

1、描述符是什么:

描述符本质就是一个新式类,

在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,

这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发

2、

class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
    def __get__(self, instance, owner):
        pass
    def __set__(self, instance, value):
        pass
    def __delete__(self, instance):
        pass

3、描述符是干什么的:

描述符的作用

是用来 代理 另外一个 类的 属性的

(必须把描述符定义成这个类的类属性,不能定义到构造函数中)


引子:描述符类产生的实例进行属性操作并不会触发三个方法的执行

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='egon' f1.name del f1.name #疑问:何时,何地,会触发这三个方法的执行

 


描述符应用之何时?何地?
class Foo:
    def __set__(self, instance, value):
        print(instance)
        print(value)
    def __get__(self, instance, owner):
        print(instance)
        print(owner)
    def __delete__(self, instance):
        print(instance)

f1 = Foo()
f1.name = "abc"
f1.name
del f1.name

class Name:
    name = Foo()
    def __init__(self,name):
        self.name = name


n1 = Name("YAOMING")
# 执行了 set 函数
# <__main__.Name object at 0x0000000001E968D0>
# YAOMING
print (n1.__dict__)
# {}   执行的描述符 set  set 什么也没有写 所以dict 就为空
print(Name.__dict__)
# {'__module__': '__main__',
# 'name': <__main__.Foo object at 0x00000000023D6828>,
# '__init__': <function Name.__init__ at 0x00000000023A8D90>,
# '__dict__': <attribute '__dict__' of 'Name' objects>,
# '__weakref__': <attribute '__weakref__' of 'Name'objects>,
# '__doc__': None}

print(type(n1))    #<class '__main__.Name'>

# type(obj) 就是看看n1是哪一个类来的
# print(type(n1).__dict__) == print(Name.__dict__)    他们结果一致的


Name.name = "No.1,优先级最大,不会执行set,因为类属性大于一切"
print(Name.__dict__)
#{'__module__': '__main__',
# 'name': 'No.1,优先级最大,不会执行set,因为类属性大于一切',
# '__init__': <function Name.__init__ at 0x0000000002168D08>,
# '__dict__': <attribute '__dict__' of 'Name' objects>,
# '__weakref__': <attribute '__weakref__' of 'Name' objects>,
# '__doc__': None}

4、描述符 分为两种, 一种是数据描述符   一种是非数据描述符

如果描述符class中没有定义__get__()方法,那么访问描述符实例仅仅会返回一个描述符class的实例,而不会触发调用。
如果定义了__set__()和__delete__ ()其中之一就是一个数据描述符
如果都没有定义,即只定义__get__,那么就是一个非数据描述符

通常,数据描述符会同时定义__get__()和__set__(),非数据描述符仅定义__get__()。

其实,基础概念看到这里就可以了,弄清什么时候触发__get__()、什么时候触发__set__()即可。

 

 

数据描述 如果定义了__set__()和__delete__ ()其中之一就是一个数据描述符

class Foo:
    def __set__(self, instance, value):
        print(instance)
        print(value)
    def __get__(self, instance, owner):
        print(instance)
        print(owner)

非数据描述符  如果都没有定义,即只定义__get__,那么就是一个非数据描述符

class Foo:
    def __get__(self, instance, owner):
        print(instance)
        print(owner)

5、注意事项:
一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()
class Foo:
    def __set__(self, instance, value):
        print(instance)
        print(value)
    def __get__(self, instance, owner):
        print(instance)
        print(owner)
    def __delete__(self, instance):
        print(instance)

class Name:
    name = Foo()
    def __init__(self,name):
        self.name = name

Name.name = "yaoming"
print (Name.name)   #yaoming
print (Name.__dict__)

#类属性 的优先级高于一切
类的优先级最高
class Foo:
    def __init__(self,name):
        self.name = name
    def __set__(self, instance, value):
        print(instance)
        print(value)
        instance.__dict__[self.name] = value
    def __get__(self, instance, owner):
        print(instance)  #  对象 n1
        print(owner)   # 对象n1来至于哪一个类
        return instance.__dict__[self.name]
    def __delete__(self, instance):
        print(instance)
        instance.__dict__.pop(self.name)

class Name:
    name = Foo("name")
    def __init__(self,name):
        self.name = name

#没有类属性  那就是数据描述符的优先级较高


n1 = Name("liuxiang")
#会执行 set函数

print (n1.name)     #liuxiang
#会执行 get 函数 ,打印执行结果 ,没有return 返回为None

print (n1.__dict__)   #{'name': 'liuxiang'}

del n1.name
# 会执行 delete 函数
print (n1.__dict__)   #{}
第二 就是数据描述符,大于了实例属性
class Foo:  #这是非数据描述符  只是定义了 get 方法
    # def __init__(self,name):
    #     self.name = name
    # # def __set__(self, instance, value):
    #     print(instance)
    #     print(value)
    #     instance.__dict__[self.name] = value
    def __get__(self, instance, owner):
        print(instance)  #  对象 n1
        print(owner)   # 对象n1来至于哪一个类
        # return instance.__dict__[self.name]
    # def __delete__(self, instance):
    #     print(instance)
        # instance.__dict__.pop(self.name)

class Name:
    # name = Foo("name")
    name = Foo()
    def __init__(self,name):
        self.name = name

# 实例属性高于 非数据描述
n1 = Name("yaoming")
print(n1.name)   #yaoming   执行的是实例属性
print(n1.__dict__)   #{'name': 'yaoming'}
del n1.name
print(n1.__dict__)   #{}  
实例属性 高于非 数据属性
class Foo:
    def func(self):
        print('我胡汉三又回来了')
f1=Foo()
f1.func() #调用类的方法,也可以说是调用非数据描述符
#函数是一个非数据描述符对象(一切皆对象么)
print(dir(Foo.func))
print(hasattr(Foo.func,'__set__'))
print(hasattr(Foo.func,'__get__'))
print(hasattr(Foo.func,'__delete__'))
#有人可能会问,描述符不都是类么,函数怎么算也应该是一个对象啊,怎么就是描述符了
#笨蛋哥,描述符是类没问题,描述符在应用的时候不都是实例化成一个类属性么
#函数就是一个由非描述符类实例化得到的对象
#没错,字符串也一样


f1.func='这是实例属性啊'
print(f1.func)

del f1.func #删掉了非数据
f1.func()
实例属性>非数据描述符
class Foo:  #这是非数据描述符  只是定义了 get 方法
    # def __init__(self,name):
    #     self.name = name
    # # def __set__(self, instance, value):
    #     print(instance)
    #     print(value)
    #     instance.__dict__[self.name] = value
    def __get__(self, instance, owner):
        print(instance)  #  对象 n1
        print(owner)   # 对象n1来至于哪一个类
        # return instance.__dict__[self.name]
    # def __delete__(self, instance):
    #     print(instance)
        # instance.__dict__.pop(self.name)

class Name:
    # name = Foo("name")
    name = Foo()
    def __init__(self,name):
        self.name = name
    def __getattr__(self, item):    #这个也是非数据描述符
        print("找不到的就找 getattr。getattr 就是非数据描述符")


n1 = Name("yaoming")
print(n1.name)    #yaoming
print(n1.unknow)
#找不到的就找 getattr。getattr 就是非数据描述符
# None   getattr 返回None 
#如果 没有getattr 函数就会报错 unknow 找不到
#这里为什么 没有执行get 函数    因为 实例属性  优先级高于 非数据描述符
实例属性 高于 非数据属性 非数据属性 高于找不到

 6、描述符 使用    如果要灵活 就是要搞定 参数化输入  

,如期望的验证的值(name,age,salary 都要参数化为key)

,如期望的值得类型(int ,str ,float都要参数为 except_type)

1、基本格式

class Foo:  #这是非数据描述符  只是定义了 get 方法
    # def __init__(self,name):
    #     self.name = name
    # # def __set__(self, instance, value):
    #     print(instance)
    #     print(value)
    #     instance.__dict__[self.name] = value
    def __get__(self, instance, owner):
        print(instance)  #  对象 n1
        print(owner)   # 对象n1来至于哪一个类
        # return instance.__dict__[self.name]
    # def __delete__(self, instance):
    #     print(instance)
        # instance.__dict__.pop(self.name)

class Name:
    # name = Foo("name")
    name = Foo()
    def __init__(self,name):
        self.name = name
    def __getattr__(self, item):    #这个也是非数据描述符
        print("找不到的就找 getattr。getattr 就是非数据描述符")


n1 = Name("yaoming")
print(n1.name)    #yaoming
print(n1.unknow)
#找不到的就找 getattr。getattr 就是非数据描述符
# None   getattr 返回None 
#如果 没有getattr 函数就会报错 unknow 找不到
#这里为什么 没有执行get 函数    因为 实例属性  优先级高于 非数据描述符
基本格式使用
class Str:
    def __init__(self,name):      #为接收被描述对象传来的参数  定义一个init
        self.name = name
    def __set__(self, instance, value):
        print ("set方法")
        instance.__dict__[self.name]=value
    def __get__(self, instance, owner):
        print("get方法")
        if instance is None:   #   因为用类获取name就会传来None  会报错
            return  self
        return instance.__dict__[self.name]
    def __delete__(self, instance):
        print("del方法")
        instance.__dict__.pop(self.name)
class People:
    name = Str("name")     #这里定义了 有参数的name   那在描述符 就有一个接收的初始化函数 init
    def __init__(self,name,age,salary):
        self.name =name
        self.age = age
        self.salary = salary


p1 = People("yaoming",18,28.5)   #set方法  get方法
print(People.name)    #这里用类来调用name   那描述符中 instance 获取到的值就是None 
类访问类的属性(描述符),需要判断instance
class Str:
    def __init__(self,name):      #为接收被描述对象传来的参数  定义一个init
        self.name = name
    def __set__(self, instance, value):
        print ("set方法")
        instance.__dict__[self.name]=value
    def __get__(self, instance, owner):
        print("get方法")
        if instance is None:   #   因为用类获取name就会传来None  会报错
            return  self
        return instance.__dict__[self.name]
    def __delete__(self, instance):
        print("del方法")
        instance.__dict__.pop(self.name)
class People:
    name = Str("name")     #这里定义了 有参数的name   那在描述符 就有一个接收的初始化函数 init
    def __init__(self,name,age,salary):
        self.name =name
        self.age = age
        self.salary = salary


p1 = People("yaoming",18,28.5)   #set方法  get方法
print(People.name)    #这里用类来调用name   那描述符中 instance 获取到的值就是None 
用描述符限制输入值类型
class Str:
    def __init__(self,key,except_type):      #为接收被描述对象传来的参数  定义一个init
        self.key = key
        self.except_type = except_type
    def __set__(self, instance, value):
        print ("set方法")
        if not isinstance(value,self.except_type):  #判断给定值 是否是期待的类型
            raise TypeError("%s except %s" %(self.key,self.except_type))
        instance.__dict__[self.key] = value

    def __get__(self, instance, owner):
        print("get方法")
        if instance is None:   #   因为用类获取name就会传来None  会报错
            return  self
        return instance.__dict__[self.key]
    def __delete__(self, instance):
        print("del方法")
        instance.__dict__.pop(self.key)
class People:
    name = Str("name",str)
    age = Str("age",int)
    salary = Str("salary",float)
    #这里定义了 有参数的name   那在描述符 就有一个接收的初始化函数 init
    #这里增加了期望类型 str
    def __init__(self,name,age,salary):
        self.name =name
        self.age = age
        self.salary = salary


p1 = People("yaoming",18,28.5)   #set方法  get方法
# p1.name = 213   #报错 name  需要 str 类型的数据
# p1.age = "13"     #报错age 需要 int 类型的数据
# p1.salary = 13    #报错salary 需要 float 类型的数据
用描述符限制多个输入值的类型

2、如果像上面最后一种来使用  输入 类型限制  看起来也很不错了 

但是在类里面还是要直接定义 多个 类的属性,显得很累赘

可以试一试  装饰器来简化重复

def deco(obj):
    print("只要有函数被我修饰,就会执行deco函数")
    return obj

@deco
def func():
    print("被修饰的函数 func")


func()
基本装饰器---函数对象的(无参)
def deco(obj):
    print("只要有函数被我修饰,就会执行deco函数")
    return obj

@deco   #就会执行函数deco,   func = deco(func)
def func():
    print("被修饰的函数 func")

func()
# 只要有函数被我修饰,就会执行deco函数
# 被修饰的函数 func

@deco
class Foo():
    def __init__(self,name,age):
        self.name = name
        self.age = age

f1 = Foo("yaoming",20)
#只要有函数被我修饰,就会执行deco函数
基本装饰器-装饰对象为函数或者类,他们都是对象(无参)
def wapper(**kwargs):
    print("wai---->",kwargs)
    def deco(obj):
        print("只要有函数被我修饰,就会执行deco函数")
        print("nei---->",kwargs)
        return obj
    return deco

@wapper(f1 = 1)   #就会执行函数deco,   func = wapper(f1=1)
def func():
    print("被修饰的函数 func")


func()
# wai----> {'f1': 1}
# 只要有函数被我修饰,就会执行deco函数
# nei----> {'f1': 1}
# 被修饰的函数 func



@wapper(name = str,age = int)
class Foo():
    def __init__(self,name,age):
        self.name = name
        self.age = age

f1 = Foo("yaoming",20)
#wai----> {'name': <class 'str'>, 'age': <class 'int'>}
# 只要有函数被我修饰,就会执行deco函数
# nei----> {'name': <class 'str'>, 'age': <class 'int'>}
基本装饰器-带参-对象(函数|类)
class Typed():
    def __init__(self,key,excpet_type):
        self.key = key
        self.excpet_type = excpet_type
    def __set__(self, instance, value):
        if not isinstance(value,self.excpet_type):
            raise TypeError("%s is not excpet_type: %s "%(value,self.excpet_type))
        instance.__dict__[self.key] = value
    def __get__(self, instance, owner):
        return instance.__dict__[self.key]
    def __delete__(self, instance):
        instance.__dict__.pop(self.key)

def wapper(**kwargs):
    def deco(obj):
        for key,val in kwargs.items():
            setattr(obj,key,Typed(key,val))
        return obj
    return deco


@wapper(name = str,age = int,salary=float)
class Foo():
    def __init__(self,name,age,salary):
        self.name = name
        self.age = age
        self.salary = salary

f1 = Foo("yaoming",20,30.3)
print (f1.__dict__)
# f1.name = 213    #报错
# f1.salary = "12"   #报错
# f1.age = 3.2   #报错

del f1.salary
print (f1.__dict__)  #{'name': 'yaoming', 'age': 20}

print (f1.age)  #20
描述符+带参装饰器(简化重复代码)

 

 

7、描述符再利用来重写功能

描述符  是可以实现    大部分python类   特性中的底层魔法,

包括@classmethod  类方法  ,@staticmethd   静态方法  ,@property  静态属性    

甚至是__slots__属性

描述符是很多高级库和框架的重要工具之一,

描述符通常是使用到装饰器 或  元类的大型框架中的一个组件.

1、 利用描述符原理完成一个自定制@property, 

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

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

    @property
    def area(self):
        return ("%s's area is %s "%(self.name,self.lenth * self.width))
r1 = Room("kitchen",3,5)
r2 = Room("toilet",3,1.8)

print(r1.area)
print(r2.area)
# kitchen's area is 15
# toilet's area is 5.4
静态属性 @property 基本用法
#让我们来重做 property
# @ 的意思就是装饰  @property   obi==property(obj)  obj 为被修饰的对象
# 但这里又涉及到描述符  (要被描述符修饰,就又必须是类属性 也就 类属性名 = 描述函数(类属性名))

class Lazyproperty:
    def __init__(self,func):
        self.func = func
    def __get__(self, instance, owner):
        if  instance is None:
            return self
        return self.func(instance)    #这里就是实现   area(self)  这里的self 就是类产生的对象



class Room:    #self 就是实例对象  r1或者r2
    def __init__(self,name,lenth,width):
        self.name = name
        self.lenth = lenth
        self.width = width

    @Lazyproperty   #area = Lazyproperty(area)   这里用了装饰器 ,然后产生了类属性,并用到了描述符
    def area(self):
        return ("%s's area is %s "%(self.name,self.lenth * self.width))

r1 = Room("kitchen",3,5)       #这就是self
r2 = Room("toilet",3,1.8)      #这就是self

print(r1.area)
print(r2.area)
#kitchen's area is 15 
# toilet's area is 5.4 

#若果不用静态属性 就是把装饰器屏蔽
# 调用方法就是 r1.area()  ()里面的self 自动传入的    print(r1.area())
重写property静态函数 静态函数就把类函数进行类属性化
#让我们来重做 property
# @ 的意思就是装饰  @property   obi==property(obj)  obj 为被修饰的对象
# 但这里又涉及到描述符  (要被描述符修饰,就又必须是类属性 也就 类属性名 = 描述函数(类属性名))

class Lazyproperty:
    def __init__(self,func):
        self.func = func
    def __get__(self, instance, owner):
        if  instance is None:
            return self
        else:
            value = self.func(instance)
            setattr(instance,self.func.__name__,value)
            return value    #这里就是实现   area(self)  这里的self 就是类产生的对象



class Room:    #self 就是实例对象  r1或者r2
    def __init__(self,name,lenth,width):
        self.name = name
        self.lenth = lenth
        self.width = width

    @Lazyproperty   #area = Lazyproperty(area)   这里用了装饰器 ,然后产生了类属性,并用到了描述符
    def area(self):
        return ("%s's area is %s "%(self.name,self.lenth * self.width))

r1 = Room("kitchen",3,5)       #这就是self
# r2 = Room("toilet",3,1.8)      #这就是self

print (Room.__dict__)
print (r1.__dict__)    #{'name': 'kitchen', 'lenth': 3, 'width': 5}
print(r1.area)
print (Room.__dict__)
print (r1.__dict__)   #{'name': 'kitchen', 'lenth': 3, 'width': 5, 'area': "kitchen's area is 15 "}
print(r1.area)

#  来就从自己(实例)的属性里面来找, 找不到就去类里面的属性字典找,然后再触发area 然后再执行描述符的 get 函数
#若果找到了 就不再触发area 去执行get 函数

# print(r2.area)
# kitchen's area is 15
# toilet's area is 5.4

#若果不用静态属性 就是把装饰器屏蔽
# 调用方法就是 r1.area()  ()里面的self 自动传入的    print(r1.area())
将area 计算结果写到 add 到 实例的字典里
#让我们来重做 property
# @ 的意思就是装饰  @property   obi==property(obj)  obj 为被修饰的对象
# 但这里又涉及到描述符  (要被描述符修饰,就又必须是类属性 也就 类属性名 = 描述函数(类属性名))
# 用非数据描述符实现(只有get),但用了数据描述符(有set 和 delete之一) 就会每一次执行 lazyproperty  ,可以用断点测试
class Lazyproperty:
    def __init__(self,func):
        self.func = func
    # def __set__(self, instance, value):
    #     print("set方法")
    def __get__(self, instance, owner):
        if  instance is None:
            return self
        else:
            value = self.func(instance)
            instance.__dict__[self.func.__name__]=value
            #setattr(instance,self.func.__name__,value)   #上面的方式和这句话一样的效果
            return value    #这里就是实现   area(self)  这里的self 就是类产生的对象
    # def __delete__(self, instance):
    #     print("delete方法")



class Room:    #self 就是实例对象  r1或者r2
    def __init__(self,name,lenth,width):
        self.name = name
        self.lenth = lenth
        self.width = width

    @Lazyproperty   #area = Lazyproperty(area)   这里用了装饰器 ,然后产生了类属性,并用到了描述符
    def area(self):
        return ("%s's area is %s "%(self.name,self.lenth * self.width))

r1 = Room("kitchen",3,5)       #这就是self
# r2 = Room("toilet",3,1.8)      #这就是self

print (Room.__dict__)
print (r1.__dict__)    #{'name': 'kitchen', 'lenth': 3, 'width': 5}
print(r1.area)
print (Room.__dict__)
print (r1.__dict__)   #{'name': 'kitchen', 'lenth': 3, 'width': 5, 'area': "kitchen's area is 15 "}
print(r1.area)

#  来就从自己(实例)的属性里面来找, 找不到就去类里面的属性字典找,然后再触发area 然后再执行描述符的 get 函数
#若果找到了 就不再触发area 去执行get 函数

# print(r2.area)
# kitchen's area is 15
# toilet's area is 5.4

#若果不用静态属性 就是把装饰器屏蔽
# 调用方法就是 r1.area()  ()里面的self 自动传入的    print(r1.area())
注意实例属性和数据描述符优先级,看看延迟计算是否生效

 

posted @ 2019-07-11 17:22  科学小怪癖  阅读(132)  评论(0)    收藏  举报