Python学习:面向对象描述符(__get__,__set__,__delete__),装饰器decorate

描述符(__get__,__set__,__delete__)

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

  • __get__():调用一个属性时,触发
  • __set__():为一个属性赋值时,触发
  • __delete__():采用del删除属性时,触发
  • __get__用于访问属性。它返回属性的值,或者在所请求的属性不存在的情况下出现 AttributeError 异常。类似于javabean中的get。
  • __set__将在属性分配操作中调用。不会返回任何内容。类似于javabean中的set。
  • __delete__控制删除操作。不会返回内容。
描述符协议
`__get__(self, instance, owner)`

		:param self: 描述符对象本身
		
		:param instance: 使用描述符的对象的实例
		
		:param owner: 使用描述符的对象拥有者

__set__(self, instance, value)

		:param value: 对描述符的赋值

__delete__(self, instance)

只实现__get__方法的对象是非数据描述符,意味着在初始化之后它们只能被读取。而同时实现__get__和__set__的对象是数据描述符,意味着这种属性是可读写的。

∗ ∗ 定 义 一 个 描 述 符 ∗ ∗ \color{HotPink}{**定义一个描述符**}

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

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

class name_des(object):
    def __init__(self):
        self.__name = None
    def  __get__(self, instance, owner):
        print('call __get__')
        return self.__name
    def  __set__(self, instance, value):
        print('call __set__')
        if  isinstance(value,str):
            self.__name = value
        else:
            raise TypeError("Must be an string")

class test(object):
     name = name_des()
t = test()
t.name          #--->call __get__

# t.name = 3
# #--->call __set__  (类型检查  报异常)
# Traceback (most recent call last):
#   File "<pyshell#99>", line 1, in <module>
#     t.name = 3
#   File "<pyshell#94>", line 12, in __set__
#     raise TypeError("Must be an string")
# TypeError: Must be an string

t.name = 'my name is chenyang'      # ---->call __set__

t.name       # ---->call __get__
  • 引子:描述符类产生的实例进行属性操作并不会触发三个方法的执行
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
#疑问:何时,何地,会触发这三个方法的执行
  • 描述符应用之何时?何地?
#描述符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设置...
Int设置...

#描述符Str的使用
p1.name       		#打印:Str调用
p1.name='egon'      #打印:Str设置...
del p1.name      	#打印: Str删除...


#描述符Int的使用
p1.age 				#输出:Int调用
p1.age=18			#输出:Int设置...
del p1.age			#输出:Int删除...

#到底发生了什么
print(p1.__dict__)     # 输出:{}
print(People.__dict__)
输出
{'__module__': '__main__', 'name': <__main__.Str object at 0x03475970>,
 'age': <__main__.Int object at 0x03475C70>, '__init__': <function People.__init__ at 
 0x0348D030>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': 
 <attribute '__weakref__' of 'People' objects>, '__doc__': None}


#补充
print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的
print(type(p1).__dict__ == People.__dict__)
输出:
True
True
  • 描述符分两种
    一 、数据描述符:至少实现了_ _get_ _()_ _set_ _()
class Foo:
     def __set__(self, instance, value):
         print('set')
     def __get__(self, instance, owner):
         print('get')

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

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

  • ∗ ∗ 类 属 性 ∗ ∗ > 数 据 描 述 符 \color{HotPink}{**类属性**>数据描述符} >

#描述符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 #同上
'''
  • 数 据 描 述 符 > ∗ ∗ 实 例 属 性 ∗ ∗ \color{HotPink}{数据描述符>**实例属性**} >
#描述符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
  • 实 例 属 性 > 非 数 据 描 述 符 \color{HotPink}{实例属性>非数据描述符} >
class Foo:
    def func(self):
        print('我胡汉三又回来了')
f1=Foo()
f1.func() #调用类的方法,也可以说是调用非数据描述符

#函数是一个非数据描述符对象(一切皆对象么)

print(dir(Foo.func))
print(hasattr(Foo.func,'__set__'))		#False
print(hasattr(Foo.func,'__get__'))		#True
print(hasattr(Foo.func,'__delete__'))	#False

#有人可能会问,描述符不都是类么,函数怎么算也应该是一个对象啊,怎么就是描述符了
#,描述符是类没问题,描述符在应用的时候不都是实例化成一个类属性么
#函数就是一个由非描述符类实例化得到的对象


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

del f1.func #删掉了非数据
f1.func()
  • 再 次 验 证 : 实 例 属 性 > 非 数 据 描 述 符 \color{HotPink}{再次验证:实例属性>非数据描述符} >
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='厨房'
#打印--->:
#set
#get
#set


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)  #  item为xxxx属性
f1=Foo()

f1.xxxx

输出:找不到了当然是来找我啦 xxxx  # 

描述符使用

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

#描述符
class Str:
    def __init__(self,name):
        self.name=name

    def __get__(self, instance, owner):
        print('get--->',instance,owner)
       return instance.__dic__[self.name]

    def __set__(self, instance, value):
        print('set--->',instance,value)
        instance.__dic__[self.name]=value

    def __delete__(self, instance):
        print('delete--->',instance)
        instance.__dic__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__)

case2

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) #完美,解决

拔刀相助

case3

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因不是字符串类型而抛出异常

磨刀霍霍

case 4

class Typed:
	# 参数expected_type为要传入的数据类型
    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 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):#kwargs={'name':str,'age':int,'salary':float}
    	    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)
    #打印:
    类的装饰器开始运行啦------> 
    {'name': <class 'str'>, 'age': <class 'int'>, 'salary': <class 'float'>}
    

Case

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):  #kwargs={'name':str,'age':int,'salary':float}
    def decorate(cls):
        print('类的装饰器开始运行啦------>',kwargs)
        for name,expected_type in kwargs.items(): #kwargs.items():(('name':str),('age':int).)
            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)


描述符总结

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

原文链接 :https://www.cnblogs.com/linhaifeng/articles/6204014.html#_label4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值