面向对象进阶3:迭代器协议、描述符

 一、__next__和__iter__实现迭代器协议

1、迭代器协议:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个stopiteration异常,以终止迭代。

2、可迭代对象:实现迭代器协议的对象(对象内部定义一个__iter__方法,来生成一个迭代器)

3、协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具使用迭代器协议访问对象

class Foo:
    def __init__(self,n):
        self.n=n
        
    def __iter__(self):
        return self

    def __next__(self):
        if self.n == 13:    #若没有设置终止条件,会一直自加1
            raise StopIteration('终止了')  #抛出异常
        self.n+=1
        return self.n

# l=list('hello')
# for i in l:
#     print(i)

f1=Foo(10)
# next会将上一次的值沿用,操作完后覆盖原来的值,原来的就消失
# print(f1.__next__())  #11   #等价于next(f1)
# print(f1.__next__())    #12
# print(f1.__next__())    #13
# print(f1.__next__())    #14

# 要想要调用for之类的迭代方法,必须要求Foo中含有迭代方法
# iter方法中必须有返回值,且返回值为可迭代的对象
for i in f1:  # obj=iter(f1)--->f1.__iter__()
     print(i)  #obj.__next_()
# 迭代器协议实现斐波那契数列
# 每一个值都是一个是前俩的和
class Fib:
    def __init__(self):
        self._a=1
        self._b=1

    def __iter__(self):
        return self
    def __next__(self):
        if self._a > 100:
            raise StopIteration('终止了')
        self._a,self._b=self._b,self._a + self._b
        return self._a

f1=Fib()
print(next(f1))     #1
print(next(f1))        #2
print(next(f1))        #3
print(next(f1))        #5
print(next(f1))        #8
print('==================================')
for i in f1:      #会继续从8继续进行for循环
    print(i)
#输出结果:
#13
#21
#34
#55
#89
#144

二、描述符(__get__,__set__,__delete__)

1 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发

#定义一个描述符
class
Foo: def __get__(self, instance, owner): print('===>get方法') def __set__(self, instance, value): print('===>set方法',instance,value) instance.__dict__['x']=value #b1.__dict__ def __delete__(self, instance): print('===>delete方法')

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

#引子:描述符类产生的实例进行属性操作并不会触发三个方法的执行
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 __get__(self, instance, owner):
        print('===>get方法')
    def __set__(self, instance, value):
        print('===>set方法',instance,value)   
        #instance为实例b1,value为传入Bar的数值10
        instance.__dict__['x']=value #修改b1.__dict__
    def __delete__(self, instance):
        print('===>delete方法')

#引子:描述符类产生的实例进行属性操作并不会触发三个方法的执行
class Bar:
    x=Foo()      #描述符,表示Bar对x属性的全部调用都去找Foo    #在何地?
    def __init__(self,n):
        self.x=n   #b1.x=10 
        #由于x被代理了,此行会去找Foo中的set来执行

b1=Bar(10)   #x被Foo代理,会去调用Foo中的set方法
print(b1.__dict__) 
#取决于set方法有没有修改b1的字典   
#set方法中如果什么都不做,那此字典必为空
#如果在Bar中对x不加描述符Foo,b1字典中会有x的属性,因为没有被代理
b1.x=11111111111111111   #触发Foo中的set方法,从而修改b1的值
print(b1.__dict__)    
# 因此,被代理的属性全归代理管,没有被代理的属性就是原来的形式

b1.y=1111
print(b1.__dict__)   #正常添加一个键值对{'y':1111}
#描述符Str
class Str:
    def __get__(self, instance, owner):
        print('Str调用')
    def __set__(self, instance, value):
        print('Str设置...')
    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()
    age=Int()
    def __init__(self,name,age): #name被Str类代理,age被Int类代理,
        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__)

3 描述符分两种
一 数据描述符:至少实现了__get__()和__set__()

1 class Foo:
2     def __set__(self, instance, value):
3         print('set')
4     def __get__(self, instance, owner):
5         print('get')

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

1 class Foo:
2     def __get__(self, instance, owner):
3         print('get')

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

class Foo:
    def __get__(self, instance, owner):
        print('===>get方法')
    def __set__(self, instance, value):
        print('===>set方法',instance,value)
        # instance.__dict__['x']=value #b1.__dict__
    def __delete__(self, instance):
        print('===>delete方法')

class Bar:
    x=Foo()    #被数据描述符Foo代理(含有set方法)

print(Bar.x)   #类调用被代理的变量x,触发描述符对应Foo中的get函数

# 类属性>数据描述符
# Bar.x=1    #不会触发get函数,类属性高于描述符,实质底层字典的赋值操作
# print(Bar.__dict__)   #原来x对应一个指向Foo的对象,现在直接变成一个数值x了
# print(Bar.x)   #1

# 数据描述符>实例属性
#实例在操作这个属性的时候,优先去操作数据描述符
print(Bar.__dict__)  #{   <__main__.Foo object at 0x00000000006CAF98>    }
b1=Bar()
b1.x      # 触发Foo中的get   
# b1现在自己的属性列表找,找不到去类Bar中找,类Bar中对应一个描述符对象
b1.x=1    # 触发Foo中的set
del b1.x  # 触发Foo中的delete 

del Bar.x
b1.x    #报错,删了之后就调用不了了

# 实例属性>非数据描述符(没有set方法的Foo)
class Foo:
    def __get__(self, instance, owner):
        print('===>get方法')

    # def __delete__(self, instance):
    #     print('===>delete方法')

class Bar:
    x=Foo() #在何地?
    def  __getattr__(self, item):
        print('----->')

b1=Bar()
b1.x=1    
#b1在自己的类中找不到,去类里找,被代理了,则去找Foo中的set方法
#Foo中没有set方法,因此直接当成实例属性,直接加到b1自己的属性字典中去了
print(b1.__dict__)    #{'x':1}

# 非数据描述符>找不到
b1.xxxxx   #找不到就触发Bar当中的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)

#如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,于p1本身无关了,相当于覆盖了实例的属性
p1.name='egonnnnnn'
p1.name
print(p1.__dict__)#实例的属性字典中没有name,因为name是一个数据描述符,优先级高于实例属性,查看/赋值/删除都是跟描述符有关,与实例无关了
del p1.name

数据描述符>实例属性
数据描述符>实例属性
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:
    def __set__(self, instance, value):
        print('set')
    def __get__(self, instance, owner):
        print('get')
class Room:
    name=Foo()
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length


#name是一个数据描述符,因为name=Foo()而Foo实现了get和set方法,因而比实例属性有更高的优先级
#对实例的属性操作,触发的都是描述符的
r1=Room('厕所',1,1)
r1.name
r1.name='厨房'



class Foo:
    def __get__(self, instance, owner):
        print('get')
class Room:
    name=Foo()
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length


#name是一个非数据描述符,因为name=Foo()而Foo没有实现set方法,因而比实例属性有更低的优先级
#对实例的属性操作,触发的都是实例自己的
r1=Room('厕所',1,1)
r1.name
r1.name='厨房'

再次验证:实例属性>非数据描述符
再次验证:实例属性>非数据描述符
class Foo:
    def func(self):
        print('我胡汉三又回来了')

    def __getattr__(self, item):
        print('找不到了当然是来找我啦',item)
f1=Foo()

f1.xxxxxxxxxxx

非数据描述符>找不到
非数据描述符>找不到

5 描述符使用

在定义描述符时,考虑是数据描述符还是非数据描述符的根据就是看你想要实现的优先级

众所周知,python是弱类型语言,即参数的赋值没有类型限制,下面我们通过描述符机制来实现类型限制功能

#描述符的应用
def test(x):
    print('===>',x)

test('alex')   #传字符串
test(111111)    #传数字
#不管传什么类型,都可以,都不会报错
#例:要求为python创建一个类型检测,控制传入的参数是相应的数据类型

class Typed:
    def __init__(self,key,expected_type):   
    #在这里指定要修改的变量名key,期望的数据类型expected_type
        self.key=key
        self.expected_type=expected_type
    
    def __get__(self, instance, owner):    #instance接收实例本身,owner接收实例所属的类
        print('get方法')
        # print('instance参数【%s】' %instance)    #打印传入实例p1.name中的p1对象内存地址
        # print('owner参数【%s】' %owner)       #打印传入实例p1所属的类People
        return instance.__dict__[self.key]
#有set方法,就表明Typed被定义为数据描述符
    
    def __set__(self, instance, value):       #instance为p1实例
    #参数表中的self为Typed产生的一个对象(打印self就能看出来)
        print('set方法')
        # print('instance参数【%s】' % instance)
        # print('value参数【%s】' % value)
        # print('====>',self)  #====> <__main__.Typed object at 0x00000000007E9198>
        if not isinstance(value,self.expected_type):  #判断是不是期望的数据类型
            # print('你传入的类型不是字符串,错误')
            # return
            raise TypeError('%s 传入的类型不是%s' %(self.key,self.expected_type))
            #利用抛出异常,不满足条件直接结束程序
        instance.__dict__[self.key]=value    #Typed初始化时传入的参数列表第二项key
        #为了保存得到数值,只能操作实例instance属性字典实现,不是self字典
    
    def __delete__(self, instance):     #可以不要
        print('delete方法')
        # print('instance参数【%s】' % instance)
        instance.__dict__.pop(self.key)

#为了实现在操作实例属性之前进行数据类型检测,
class People:
    name=Typed('name',str) #t1.__set__()  self.__set__()
    age=Typed('age',int) #t1.__set__()  self.__set__()
    def __init__(self,name,age,salary):
        self.name=name      #触发代理的数据描述符中相应的set函数
        #如果非数据描述符触发的就是实例属性了
        self.age=age
        self.salary=salary

# p1=People(213,13,13.3)   #报错

# p1=People('alex',13,13.3)    #触发set方法
# print(p1.__dict__)    #{'salary':13.3,'name':'alex','age':13}
#通过set方法直接写入实例的底层字典中   
# p1=People(213,13,13.3)
# print(p1.__dict__)
# print(p1.__dict__)
# print(p1.name)

# print(p1.__dict__)    #{'salary':13.3,'name':'alex','age':13}
# p1.name='egon'        #p1.name触发get方法
# print(p1.__dict__)    #利用set方法修改成功 ##{'salary':13.3,'name':'egon','age':13}
#如果Typed的set方法不改实例的属性字典的话,对象字典当中不会直接有name,因为name被typed代理了

# print(p1.__dict__)    #{'salary':13.3,'name':'alex','age':13}
# del p1.name           #触发描述符中的delete方法,成功删去name属性
# print(p1.__dict__)    #{'salary':13.3,'age':13}
#所以上述三种操作均绕到代理描述符中去操作get、set、delete函数,看似就和直接操作实例属性一样
#但现在做好的铺垫,就可以进一步来实现数据类型的判断了
class Str:
    def __init__(self,name):
        self.name=name
    def __get__(self, instance, owner):
        print('get--->',instance,owner)
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print('set--->',instance,value)
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print('delete--->',instance)
        instance.__dict__.pop(self.name)


class People:
    name=Str('name')
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

p1=People('egon',18,3231.3)

#调用
print(p1.__dict__)
p1.name

#赋值
print(p1.__dict__)
p1.name='egonlin'
print(p1.__dict__)

#删除
print(p1.__dict__)
del p1.name
print(p1.__dict__)
牛刀小试
class Str:
    def __init__(self,name):
        self.name=name
    def __get__(self, instance, owner):
        print('get--->',instance,owner)
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print('set--->',instance,value)
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print('delete--->',instance)
        instance.__dict__.pop(self.name)


class People:
    name=Str('name')
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

#疑问:如果我用类名去操作属性呢
People.name #报错,错误的根源在于类去操作属性时,会把None传给instance

#修订__get__方法
class Str:
    def __init__(self,name):
        self.name=name
    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)
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print('delete--->',instance)
        instance.__dict__.pop(self.name)


class People:
    name=Str('name')
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary
print(People.name) #完美,解决
拔刀相助
class Str:
    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): #如果不是期望的类型,则抛出异常
            raise TypeError('Expected %s' %str(self.expected_type))
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print('delete--->',instance)
        instance.__dict__.pop(self.name)


class People:
    name=Str('name',str) #新增类型限制str
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

p1=People(123,18,3333.3)#传入的name因不是字符串类型而抛出异常
磨刀霍霍
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):
            raise TypeError('Expected %s' %str(self.expected_type))
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print('delete--->',instance)
        instance.__dict__.pop(self.name)


class People:
    name=Typed('name',str)
    age=Typed('name',int)
    salary=Typed('name',float)
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

p1=People(123,18,3333.3)
p1=People('egon','18',3333.3)
p1=People('egon',18,3333)
大刀阔斧

大刀阔斧之后我们已然能实现功能了,但是问题是,如果我们的类有很多属性,你仍然采用在定义一堆类属性的方式去实现,low,这时候我需要教你一招:独孤九剑

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

# 例1:为函数中添加装饰器:
@deco          #test=deco(test),就会直接运行test,相当于test()
# 并将test传给deco后将返回值给test
def test():
    print('test函数运行')   

       
# 例2:为类添加装饰器 
@deco      #Foo=deco(Foo)
class Foo:
    pass
# 装饰器不仅能为函数加,还能加给类。因为他们都是对象
# obj=deco(obj)


#类的装饰器:装饰其他类
def deco(obj):
    print('==========',obj)
    #为类增加数据属性,写死了,给用户选要增加些什么属性
    obj.x=1
    obj.y=2
    obj.z=3
    return obj
@deco #Foo=deco(Foo)
class Foo:
    pass

print(Foo.__dict__)  #成功利用deco给Foo的类属性字典加入三个数据:x,y,z

# 一切皆对象:类和函数都这样处理
# @deco #test=deco(test)
def test():
    print('test函数')
test.x=1
test.y=1
print(test.__dict__)   #{'y':1,'x':1}
#既然类有属性字典,则同为对象的test函数应该也有自己的属性字典
#类的装饰器修订版
def Typed(**kwargs):   #参数为字典形式,用于指定参数
    def deco(obj):
        # print('--->',kwargs)
        # print('类名--->',obj)
        # 直接修改obj.__dict__不方便(用update可以实现),采用如下循环方法:
        for key,val in kwargs.items():
            # obj.key=val   #仅仅加了一个key属性,加入的字典中为   ‘key’:1  不是数据属性,达不到目的
            # obj.__dict__[key]=val  #报错,不支持
            setattr(obj,key,val)
        return obj   #加了属性的obj作为函数的返回值
    return deco   #转运行deco,deco现在在Typed的局部作用域中

@Typed(x=1,y=2,z=3)   #1.Typed(x=1,y=2,z=3)运行函数Typed --->deco   
# 2.@deco---->Foo=deco(Foo)  obj得到的参数为Foo,执行Foo
class Foo:
    pass
print(Foo.__dict__)

# 为Bar加一个name属性:
@Typed(name='egon')  #@deco   ---->Bar=deco(Bar)
class Bar:
    pass
print(Bar.name)
def decorate(cls):
    print('类的装饰器开始运行啦------>')
    return cls

@decorate #无参:People=decorate(People)
class People:
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

p1=People('egon',18,3333.3)
类的装饰器:无参
def typeassert(**kwargs):
    def decorate(cls):
        print('类的装饰器开始运行啦------>',kwargs)
        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
        self.age=age
        self.salary=salary

p1=People('egon',18,3333.3)
类的装饰器:有参

终极大招

#类的装饰器的应用
class Typed:
    def __init__(self,key,expected_type):
        self.key=key
        self.expected_type=expected_type
    def __get__(self, instance, owner):
        print('get方法')
        # print('instance参数【%s】' %instance)
        # print('owner参数【%s】' %owner)
        return instance.__dict__[self.key]
    def __set__(self, instance, value):
        print('set方法')
        # print('instance参数【%s】' % instance)
        # print('value参数【%s】' % value)
        # print('====>',self)
        if not isinstance(value,self.expected_type):
            # print('你传入的类型不是字符串,错误')
            # return
            raise TypeError('%s 传入的类型不是%s' %(self.key,self.expected_type))
        instance.__dict__[self.key]=value
    def __delete__(self, instance):
        print('delete方法')
        # print('instance参数【%s】' % instance)
        instance.__dict__.pop(self.key)

def deco(**kwargs): #kwargs={'name':str,'age':int}
    def wrapper(obj): #obj=People
        for key,val in kwargs.items():#(('name',str),('age',int))
            setattr(obj,key,Typed(key,val))  #将key和val给描述符Typed生成一个类属性
            # setattr(People,'name',Typed('name',str)) #People.name=Typed('name',str)
        return obj
    return wrapper
    
@deco(name=str,age=int)  #可以自定义deco中传入些什么参量
# @wrapper ===>People=wrapper(People)
class People:
    name='alex'  #不影响setattr的操作,仍然成功修改name,与优先级也无关
    #以下两步由上三行中的装饰器@deco来直接替代
    # name=Typed('name',str)
    # age=Typed('age',int)
    def __init__(self,name,age,salary,gender,heigth):
        self.name=name
        self.age=age
        self.salary=salary
# p1=People('213',13.3,13.3,'x','y')
print(People.__dict__)
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):
            raise TypeError('Expected %s' %str(self.expected_type))
        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():
            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
        self.age=age
        self.salary=salary

print(People.__dict__)
p1=People('egon',18,3333.3)
刀光剑影

6 描述符总结

描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性

描述符是很多高级库框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.

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

#利用描述符自定制property
#@语法糖后面接的不一定是函数,可以是类。
#只要类名加括号就是在运行,例如@Lazypropery(x=1)就会先执行函数,将返回值放在这个位置
# Lazyproperty实现延迟计算
class Lazyproperty:   #把这个变成一个描述符  
    def __init__(self,func):
        # print('==========>',func)
        self.func=func
    def __get__(self, instance, owner):
        print('get')
        # print(instance)   #<__main__.Room object at 0x0000000001159198>  就是r1对象的内存地址
        # print(owner)       #<class '__main__.Room'>        #产生r1的类
        if instance is None:
            return self
        res=self.func(instance)   #此处就指明了func函数是需要一个参数的。
        ##如果不传实例instance到func的参数表中,势必会报错
        #这也就解释了为什么对象调用类方法是自动帮你把对象传入方法中,实际就是内部帮你像此句一样定义好了
        setattr(instance,self.func.__name__,res)   
        #将运算执行的结果res保存到实例字典中:函数名self.func.__name__,
        return res
    #不能定义set,一旦定义Lazyproperty就是数据描述符了,其优先级就高于实例属性了
    # def __set__(self, instance, value):  
    #     pass

class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length
    # @property #area=property(area)
    
    # 原来用area=Lazyproperty(area)来定义描述符
    # 现在@Lazyproperty就是为area增加了一个描述符Lazyproperty
    @Lazyproperty  #area=Lazypropery(area)   实例化,area将传给Lazypropery
    # 此句就会把area代理给描述符Lazyproperty
    def area(self):
        return self.width * self.length
        
    @property  #test=property(test) 就是得到一个实例对象 
    #加上@property之后将areal方法伪装成一个类属性了
    def area1(self):    #静态属性不能传参数,不能加括号运行,只能直接加点调用
    #括号内只有self用于接收一个对象
        return  self.width * self.length


# 问题一:执行步骤流程        
print(Room.__dict__)    #类字典中有area这个函数方法 (被代理了)
r1=Room('厕所',1,1)   
print(r1.__dict__)   #{'width':1,'length':1,'name':'厕所'}
print(r1.area)       # 1
#r1在自己字典找不到area,找非数据描述符Lazyproperty
#r1.area将调用lazypropertyde的get方法,r1.area接收get函数的返回值
# 目的是在调用area的时候,能直接触发area的运行,并不是要你去手动触发:print(r1.area.func(r1))
print(r1.__dict__)   #其中: 'area':<__main__.Lazyproperty object at 0x0000000000B3F0B8>

# 问题二:调用函数
#实例调用
print(r1.areal)
print(Room.__dict__)    #‘areal’:<property object at 0x0000000000A61A48>
#加上@property之后将areal方法伪装成一个类属性了

# 类调用python内置property定义的静态属性函数
print(r1.areal)
print(Room.areal)   # <property object at 0x0000000000B21AE8>
# 类名Room来调用test函数,结果返回的是一个对象
#这说明静态属性areal就是给对象去使用的

# 类调用自定制的area,会报错
print(Room.area)   # 也会去调用area代理描述符的get方法
# 类调用该属性的时候传入instance位置的不是Room,而是None,owner不变还是Room
# 因此在执行的时候传入area函数的是空,从而函数没法执行而报错 
# 为了避免报错,模仿python内置property的操作,“类调用时自动返回一个property对象”
# 在Lazyproperty中加入if判断语句,当instance为空时,返回self,即类Lazyproperty自己创建的对象

# 问题三:实现延迟计算
#对于多次执行相同计算时,希望下一次计算的时候不用再去调用函数重新执行一遍了
#而是直接执行一遍函数后将运算结果放到到某个地方,以后调用的时候直接取出结果
# 存放的这个地方就是:实例字典,数据结果对应的数据属性名就是函数名。
# 对于计算逻辑很复杂的程序,此法很重要   
# print(r1.area1)
# print(r1.area1)   
# print(r1.area1)
# print(r1.area1)
#在自定义的描述符Lazyprperty的get方法return之前
#加入setattr(instance,self.func.__name__,res)
print(r1.area)        #1
print(r1.__dict__)  #{'width':1,'length':1,'area':1,'name':'厕所'}
#之后的调用就不再是调用函数了,因为对象r1的数据字典中已经包含了一个area数据了
#之后所有的调用都是直接调用r1的数据属性area了
print(r1.area)  #不再去调用函数area,不运行get函数了
print(r1.area)
print(r1.area)
print(r1.area)
print(r1.area)
print(r1.area)
print(r1.area)
print(r1.area)
#原因是实例属性高于非数据描述符,所以调用不再去找数据描述符中的area了,而是直接用实例中的
# 变:
#当代理的Lazyproperty中含有set方法时,Lazyproperty就是数据描述符了
#实例属性低于数据描述符,没办法实现我们想要的先从实例属性找
#因此要实现每次不重复计算的一个前提就是,不能定义set方法,Lazyproperty只能是一个非数据描述符
 @property回顾
 自己做一个@property
 实现延迟计算功能
 一个小的改动,延迟计算的美梦就破碎了 

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('你是那偷心的贼')
实现@classmethod

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)
实现staticmethod

三、再看property

一个静态属性property本质就是实现了get,set,delete三种方法

#方法一:
class Foo:
    @property
    def AAA(self):
        print('get的时候运行我啊')

    @AAA.setter   # 在用赋值语句设置AAA的数值时,会触发这个方法
    def AAA(self,val):
        print('set的时候运行我啊',val)
    @AAA.deleter
    def AAA(self):
        print('del的时候运行我啊')
#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA    #直接触发AAA运行
#f1.AAA='aaa'  
#如果不定义 @AAA.setter,只定义一个AAA,是不允许这样直接给AAA设置值的
del f1.AAA
#如果不定义 @AAA.deleter,只定义一个AAA,是不允许这样直接删除AAA

#方法二:
class Foo:

    def get_AAA(self):
        print('get的时候运行我啊')
    def set_AAA(self,val):
        print('set的时候运行我啊',val)
    def del_AAA(self):
        print('del的时候运行我啊')

    AAA=property(get_AAA,set_AAA,del_AAA)
#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA
 用法一
 用法二

怎么用?

# 商品价格的操作
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()
print(obj.price)  # 获取商品价格,触发get
obj.price = 200   # 修改商品原价,set
print(obj.price)
del obj.price     # 删除商品原价,del
# print(obj.price)  #删除之后调用报错
例1:商品价格
#案例2:实现类型检测功能

#第一关:
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__)


#第三关:加上类型检查
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------>')
        if not isinstance(value,str):
            raise TypeError('必须是字符串类型')
        self.DouNiWan=value

    @name.deleter
    def name(self):
        print('delete------>')
        del self.DouNiWan

p1=People('alex') #self.name实际是存放到self.DouNiWan里
p1.name=1

案例二
例2:类型检测功能

# 只要在setter类型中操作即可

原文地址:https://www.cnblogs.com/Josie-chen/p/8885738.html