11.python描述符和类的装饰器

描述符
1.描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()这三个方法中的一个,描述符也被称为描述符协议
__get__()调用一个属性时触发
__set__()为一个属性赋值时触发
__delete__()采用del删除属性时触发
2.描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的属性,不能定义到构造函数)

class Foo:                                     #定义新式类
    def __get__(self, instance, owner):        #定义__get__方法:一个对象调用属性的时候会触发 print('===>get方法') def __set__(self, instance, value): #定义__set__方法 print('===>set方法') def __delete__(self, instance): #定义__delete__方法 print('===>delete方法') #描述符必须要有另外一个类属性里定义才会触发 class Bar: #定义一个Bar类 x=Foo() #在Bar类中定义一个类属性,这个类属性的值是Foo实例化的结果,类属性就是描述符的一个对象 b1=Bar() #b1通过Bar得到一个实例 #调用 b1.x #b1点x调用x这个属性就是Foo就可以触发描述符里的__get__方法:===>get方法 #赋值 b1.x=1 #b1点x=1调用x=1这个属性就是Foo就可以触发描述符里赋值的__set__方法:'===>set方法 #删除 del b1.x #del b1点x调用x这个属性就是Foo就可以触发描述符里赋值的__delete__方法:'===>delete方法

返回:
===>get方法
===>set方法
===>delete方法
3.描述符的执行步骤(利用描述符修改实例化传的值)

class Foo:                                     #定义描述符Foo用来代理另外一个Bar类
    def __set__(self, instance, value):        #第三步:instance传的是Bar的对象是b1,value是参数10
        print('===>set方法',instance,value)   #打印instance和value的值
        instance.__dict__['x']=value           #第四步把x的值修改成77777:instance.__dict__操作的是b1.__dict__,设置修改x的值,操作实例下的属性字典进行了一个真正的赋值操作

class Bar:           #Bar类里定义了一个描述符Foo,这个Foo就是上面的类,定义它以后以为在Bar这个类产生的实例x属性的操作全部去找Foo描述符里的__set__方法
    x=Foo()           #在Bar这个类中,我用Foo去描述Bar类当中的x属性,意味着x属性被Foo代理了  定义类属性值是Foo()实例化的结果的找到描述符里的 __set__方法
    def __init__(self,n):
        self.x=n      #第二步:b1.x=10,x这个属性被代理了,赋值触发的是x=Foo()下的__set__方法

b1=Bar(10)            #第一步:b1=Bar(10)触发__set__方法
print(b1.__dict__)

#修改x的值
b1.x=7777777        #x=7777777触发的是__set__方法
print(b1.__dict__)

返回:
===>set方法 <__main__.Bar object at 0x005747B0> 10
{'x': 10}
===>set方法 <__main__.Bar object at 0x005747B0> 7777777
{'x': 7777777}
4.描述符分数据描述符非数据描述符
(1)数据描述符:至少实现了__get__()和__set__()

class Foo:                                     #定义新式类
    def __get__(self, instance, owner):        #定义__get__方法: print('===>get方法') def __set__(self, instance, value): #定义__set__方法 print('===>set方法')

(2)非数据描述符:没有实现__set__()

class Foo:                                     #定义新式类
    def __get__(self, instance, owner):        #定义__get__方法: print('===>get方法')

5.注意:
(1)描述符本身应该定义成新式类,被代理的类也应该是新式类
(2)必须把描述符定义成这个类的属性,不能为定义到构造函数中
(3)要严格遵循该优先级,优先级由高到底分别是:类属性--->数据描述符--->实例属性--->非数据描述符--->找不到的属性出发__getatter__()
6.描述符的应用:基于描述符功能为python加上类型检测

#创建描述符Typed
class Typed:
    # 创造构造函数init方法接收俩个参数:1.self,key和2.期望的expected_type def __init__(self,key,expected_type): #第四步:__init__方法接收俩个参数:1.self,key和2.期望的expected_type self.key=key #接收的key:name self.expected_type=expected_type #接收期望的类型expected_type:str # 定义__get__方法 def __get__(self, instance, owner): # instance是p1实例本身,owner是p1的类 print('get方法') return instance.__dict__[self.key] # 把instance存到属性字典里,触发get方法得到返回值, #定义__set__方法(因为实例属性高于非数据描述符所以必须定义成数据描述符) def __set__(self, instance, value): #第三步:set方法会接收俩个参数,第一个参数就是p1实例本身instance,第二个参数就是赋的值value print('set方法') if not isinstance(value,self.expected_type): #第五步:存到instance的字典里之前判断value的类型如果不是expected_type接收到期望的类型的时候 raise TypeError('%s 传入的类型不是%s' %(self.key,self.expected_type)) #报错 instance.__dict__[self.key]=value #执行__set__方法后代理在操作把传过来的值传到底层instance字典 #定义People类 class People: #给name用到描述符传俩个参数一个是name另一个是期望的类型str name=Typed('name',str) #第二步:.name会被数据描述符Typed代理,会触发set方法(把传的值name和期望的类型传给def __init__(self,key,expected_type):) # 创造构造函数分别是名字年纪 def __init__(self,name,age): self.name=name #这一步触发的是代理而不是实例 self.age=age ####实例化触发set方法检测传入的值是否是str类型和int类型 p1=People('xixi',18) #第一步:实例化触发self.name的操作会触发set方法 #不是str类型报错 #p1=People(77,18) #TypeError: name 传入的类型不是<class 'str'> #不是int类型报错 #p1=People('xixi','18') #TypeError: age 传入的类型不是<class 'int'> print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') ####查询p1.name直接找到找到get方法里的instance里的字典找到key print(p1.name) #打印结果:xixi print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') ####修改name的名字 print(p1.__dict__) #查看修改前结果:{'name': 'xixi', 'age': 18} #调用set方法代理操作把传过来的值传到底层instance字典 p1.name='YAOYAO' #调用set方法代理操作把传过来的值传到底层instance字典完成修改 print(p1.__dict__) #打印修改结果

打印结果:
set方法
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
get方法
xixi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{'name': 'xixi', 'age': 18}
set方法
{'name': 'YAOYAO', 'age': 18}
类的装饰器
1.回顾函数调用装饰器
@语法糖的工作原理是装饰器函数括号把对象的名字放进来,对于函数调用装饰器(test)是函数的内存地址

#定义一个装饰器函数deco
def deco(func):
     print('==========')
     return func

#函数调用装饰器
#@deco做的事相当于test=deco(test),test做的事就是deco(test)运行了
@deco       #test=deco(test):deco括号把test传给def deco(func):进去得到一个返回值return func赋值给test
def test():
     print('test函数运行')
test()                          #test()运行得到:test函数运行

返回:
==========
2.类调用装饰器工作的基本原理:

#第一步:定义一个装饰器函数deco
def deco(obj):  #第三步:类名字Foo传过来以后
     obj.x=1     #利用类把属性加到Foo的属性字典里去了
     obj.y=2     #利用类把属性加到Foo的属性字典里去了
     return obj #返回类本身

#类调用装饰器
#@deco相当于运行Foo=deco(Foo),把Foo传给obj了,obj就是Foo这个类
@deco         #第二步:利用语法糖的形式,deco括号把类的名字Foo传进来,赋值给Foo,把类名字传到def deco(obj):的obj里
class Foo:   #语法糖修饰了一个类Foo
    pass
f1=Foo()      #f1用Foo实例化
print(f1)
print(Foo.__dict__)   #现在查看的是被deco处理过的Foo了

返回:
<__main__.Foo object at 0x02070550>
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 1, 'y': 2}
3.类调用装饰器修改版可以传多个类

def Typed(**kwargs):                #第二步:Typed外层会接收kwargs等于是一个字典{'x': 1, 'y': 2}
    def deco(obj):                  #第五步:运行deco局部作用域里面外层传了一个字典进来,这个obj就是类Foo
        #print('deco里的obj属性',kwargs)     #打印deco里obj的属性属性字典{'x': 1, 'y': 2}
        #print('类名', obj)                  #打印类名
        for key, val in kwargs.items():      #第六步:items把字典里{'x': 1, 'y': 2}转换成得到元祖形式(('x',1),('y',2))做一个for循环把x给key,把y给val
            setattr(obj, key, val)           #设置进去obj类名,key,val,obj是People类,key是x,val是1
        return obj                  #第七步返回obj
    print('Typed里的',kwargs)       #第二步执行结果打印kwargs:{'x': 1, 'y': 2}
    return deco                     #第三步:返回值是deco

#Foo类:传(x=1,y=2)
@Typed(x=1,y=2)                     #第一步:@Typed函数名加上小括号(x=1,y=2)运行函数Typed,   第四步:得到返回值是deco跟@deco连到一起去用进行@语法糖操作括号把类的名字Foo传进来,赋值给Foo,把类名字传到def deco(obj):的obj里
class Foo:   #语法糖修饰了一个类Foo
    pass
f1=Foo()      #f1用Foo实例化
print(Foo.__dict__)   #现在查看的是被deco处理过的Foo了

print('~~~~~~~~~~~~~~~~~~~~~~')

#Bar类:传(name='xixi')
@Typed(name='xixi')    #相当于@deco把Bar=deco(Bar),原理同上面
class Bar:
     pass
print(Bar.name)         #打印是否修改成功

打印结果:
Typed里的 {'x': 1, 'y': 2}
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 1, 'y': 2}
~~~~~~~~~~~~~~~~~~~~~~
Typed里的 {'name': 'xixi'}
xixi
4.通过定义类装饰器结合描述符给类查看属性不限制重复代码

class Typed:
    # 创造构造函数init方法接收俩个参数:1.self,key和2.期望的expected_type
    def __init__(self,key,expected_type):            #第四步:__init__方法接收俩个参数:1.self,key和2.期望的expected_type
        self.key=key                                 #接收的key:name
        self.expected_type=expected_type             #接收期望的类型expected_type:str

    # 定义__get__方法
    def __get__(self, instance, owner):  # instance是p1实例本身,owner是p1的类
        print('get方法')
        return instance.__dict__[self.key]  # 把instance存到属性字典里,触发get方法得到返回值,

    #定义__set__方法(因为实例属性高于非数据描述符所以必须定义成数据描述符)
    def __set__(self, instance, value):              #第三步:set方法会接收俩个参数,第一个参数就是p1实例本身instance,第二个参数就是赋的值value
        print('set方法')

        if not isinstance(value,self.expected_type): #第五步:存到instance的字典里之前判断value的类型如果不是expected_type接收到期望的类型的时候
            raise TypeError('%s 传入的类型不是%s' %(self.key,self.expected_type))   #报错
        instance.__dict__[self.key]=value             #执行__set__方法后代理在操作把传过来的值传到底层instance字典


#定义装饰器给类增加类属性,这个类属性比较特殊是描述符
def deco(**kwargs):                              #第二步:deco外层会接收kwargs等于是一个字典{'name':str,'age':int}
    def wrapper(obj):                            #第五步:运行wrapper局部作用域里面外层传了一个字典进来,这个obj拿到的就是People的类
        #print('wrapper里的obj属性',kwargs)       #打印wrapper里obj的属性属性字典
        for key, val in kwargs.items():          #第六步:items把字典{'name':str,'age':int}里key属性名和val类型变成元祖形式(('name',str),('age',int))做一个循环把name给key,把str给val
            #print(key,val)                       #打印key和val分别是name <class 'str'>和age <class 'int'>

            #setattr(People,'name',Typed('name',str)):obj是People类,key是name,val是Typed('name',str)相当于做了name=Typed('name',str)
            setattr(obj, key, Typed(key, val))    #第七步:给类key增加一个类属性,需要被描述符代理所以要产生一个描述符Typed做实例把key属性名和val想要的类型传进来
        return obj                               #第八步返回obj
    #print('deco里的',kwargs)                     #打印查看第二步执行结果是:kwargs:{'name':str,'age':int}
    return wrapper                               #第三步:直接返回值是wrapper

#应用装饰器deco
@deco(name=str,age=int)                            #第一步:定义name=str类型,age=int类型,deco()运行传给装饰器里的**kwargs   第四步:得到返回值是wrapper跟@wrapper连到一起去用进行@语法糖操作括号把类的名字People传进来,赋值给People,把类名字传到def  wrapper(obj):的obj里,即wrapper ===>People=wrapper(People)
class People:

    # name=Typed('name',str)   如果定义类的装饰器后查看类型限制不用在这里加
    # age=Typed('age',int)     如果定义类的装饰器查看类型限制不用在这里加

    def __init__(self,name,age):
        self.name=name
        self.age=age

p1=People('xixi',18)
print(People.__dict__)

返回:
set方法
set方法
{'__module__': '__main__', '__init__': <function People.__init__ at 0x021D98E8>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None, 'name': <__main__.Typed object at 0x021D49D0>, 'age': <__main__.Typed object at 0x021D4A10>}

猜你喜欢

转载自www.cnblogs.com/xixi18/p/9837842.html