描述符(__get__,__set__,__delete__)
程序员文章站
2022-03-22 12:03:30
[TOC] 预警.gif) 描述符 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了\_\_get\_\_(),\_\_set\_\_(),\_\_delete\_\_()中的一个,这也被称为描述符协议 \_\_get\_\_():调用一个属性时,触发 \_\_set\_\_(): ......
目录
描述符
-
描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
定义一个描述符
class foo: # 在python3中foo是新式类,它实现了__get__(),__set__(),__delete__()中的一个三种方法的一个,这个类就被称作一个描述符 def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __delete__(self, instance): pass
描述符的作用
- 描述符是干什么的:描述符的作用是用来代理另外一个类的属性的,必须把描述符定义成这个类的类属性,不能定义到构造函数中
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 = 'nick' f1.name del f1.name
何时,何地,会触发这三个方法的执行
class str: """描述符str""" def __get__(self, instance, owner): print('str调用') def __set__(self, instance, value): print('str设置...') def __delete__(self, instance): print('str删除...') class int: """描述符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 p1.name = 'nick' del p1.name
str调用 str设置... str删除...
- 描述符int的使用
p1.age p1.age = 18 del p1.age
int调用 int设置... int删除...
- 我们来瞅瞅到底发生了什么
print(p1.__dict__) print(people.__dict__)
{} {'__module__': '__main__', 'name': <__main__.str object at 0x107a86940>, 'age': <__main__.int object at 0x107a863c8>, '__init__': <function people.__init__ at 0x107ba2ae8>, '__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__()
使用描述符
- 众所周知,python是弱类型语言,即参数的赋值没有类型限制,下面我们通过描述符机制来实现类型限制功能
牛刀小试
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('nick', 18, 3231.3)
set---> <__main__.people object at 0x107a86198> nick
- 调用
print(p1.__dict__)
{'name': 'nick', 'age': 18, 'salary': 3231.3}
print(p1.name)
get---> <__main__.people object at 0x107a86198> <class '__main__.people'> nick
- 赋值
print(p1.__dict__)
{'name': 'nick', 'age': 18, 'salary': 3231.3}
p1.name = 'nicklin' print(p1.__dict__)
set---> <__main__.people object at 0x107a86198> nicklin {'name': 'nicklin', 'age': 18, 'salary': 3231.3}
- 删除
print(p1.__dict__)
{'name': 'nicklin', 'age': 18, 'salary': 3231.3}
del p1.name print(p1.__dict__)
delete---> <__main__.people object at 0x107a86198> {'age': 18, 'salary': 3231.3}
拔刀相助
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 # 疑问:如果我用类名去操作属性呢 try: people.name # 报错,错误的根源在于类去操作属性时,会把none传给instance except exception as e: print(e)
get---> none <class '__main__.people'> 'nonetype' object has no attribute '__dict__'
- 修订__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) # 完美,解决
get---> none <class '__main__.people'> <__main__.str object at 0x107a86da0>
磨刀霍霍
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 try: p1 = people(123, 18, 3333.3) # 传入的name因不是字符串类型而抛出异常 except exception as e: print(e)
set---> <__main__.people object at 0x1084cd940> 123 expected <class 'str'>
大刀阔斧
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 try: p1 = people(123, 18, 3333.3) except exception as e: print(e)
set---> <__main__.people object at 0x1082c7908> 123 expected <class 'str'>
try: p1 = people('nick', '18', 3333.3) except exception as e: print(e)
set---> <__main__.people object at 0x1078dd438> nick set---> <__main__.people object at 0x1078dd438> 18 expected <class 'int'>
p1 = people('nick', 18, 3333.3)
set---> <__main__.people object at 0x1081b3da0> nick set---> <__main__.people object at 0x1081b3da0> 18 set---> <__main__.people object at 0x1081b3da0> 3333.3
- 大刀阔斧之后我们已然能实现功能了,但是问题是,如果我们的类有很多属性,你仍然采用在定义一堆类属性的方式去实现,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('nick', 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('nick', 18, 3333.3)
类的装饰器开始运行啦------> {'name': <class 'str'>, 'age': <class 'int'>, 'salary': <class 'float'>}
刀光剑影
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('nick', 18, 3333.3)
类的装饰器开始运行啦------> {'name': <class 'str'>, 'age': <class 'int'>, 'salary': <class 'float'>} {'__module__': '__main__', '__init__': <function people.__init__ at 0x10797a400>, '__dict__': <attribute '__dict__' of 'people' objects>, '__weakref__': <attribute '__weakref__' of 'people' objects>, '__doc__': none, 'name': <__main__.typed object at 0x1080b2a58>, 'age': <__main__.typed object at 0x1080b2ef0>, 'salary': <__main__.typed object at 0x1080b2c18>} set---> <__main__.people object at 0x1080b22e8> nick set---> <__main__.people object at 0x1080b22e8> 18 set---> <__main__.people object at 0x1080b22e8> 3333.3
描述符总结
描述符是可以实现大部分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('alex', 1, 1)
print(r1.area)
1
自定制property
class lazyproperty: def __init__(self, func): self.func = func def __get__(self, instance, owner): print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') if instance is none: return self 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) 相当于定义了一个类属性,即描述符 def area(self): return self.width * self.length r1 = room('alex', 1, 1)
print(r1.area)
这是我们自己定制的静态属性,r1.area实际是要执行r1.area() 1
实现延迟计算功能
class lazyproperty: def __init__(self, func): self.func = func def __get__(self, instance, owner): print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') if instance is none: return self else: print('--->') value = self.func(instance) setattr(instance, self.func.__name__, value) # 计算一次就缓存到实例的属性字典中 return value class room: def __init__(self, name, width, length): self.name = name self.width = width self.length = length @lazyproperty # area=lazyproperty(area) 相当于'定义了一个类属性,即描述符' def area(self): return self.width * self.length r1 = room('alex', 1, 1)
print(r1.area) # 先从自己的属性字典找,没有再去类的中找,然后出发了area的__get__方法
这是我们自己定制的静态属性,r1.area实际是要执行r1.area() ---> 1
print(r1.area) # 先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算
1
打破延迟计算
- 一个小的改动,延迟计算的美梦就破碎了
class lazyproperty: def __init__(self, func): self.func = func def __get__(self, instance, owner): print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') if instance is none: return self else: value = self.func(instance) instance.__dict__[self.func.__name__] = value return value # return self.func(instance) # 此时你应该明白,到底是谁在为你做自动传递self的事情 def __set__(self, instance, value): print('hahahahahah') class room: def __init__(self, name, width, length): self.name = name self.width = width self.length = length @lazyproperty # area=lazyproperty(area) 相当于定义了一个类属性,即描述符 def area(self): return self.width * self.length
print(room.__dict__)
{'__module__': '__main__', '__init__': <function room.__init__ at 0x107d53620>, 'area': <__main__.lazyproperty object at 0x107ba3860>, '__dict__': <attribute '__dict__' of 'room' objects>, '__weakref__': <attribute '__weakref__' of 'room' objects>, '__doc__': none}
r1 = room('alex', 1, 1) print(r1.area) print(r1.area) print(r1.area)
这是我们自己定制的静态属性,r1.area实际是要执行r1.area() 1 这是我们自己定制的静态属性,r1.area实际是要执行r1.area() 1 这是我们自己定制的静态属性,r1.area实际是要执行r1.area() 1
print( r1.area ) #缓存功能失效,每次都去找描述符了,为何,因为描述符实现了set方法,它由非数据描述符变成了数据描述符,数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了
这是我们自己定制的静态属性,r1.area实际是要执行r1.area() 1
自定制@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 = 'nick' @classmethod # say_hi=classmethod(say_hi) def say_hi(cls): print('你好啊,帅哥 %s' % cls.name) people.say_hi() p1 = people()
在这里可以加功能啊... 你好啊,帅哥 nick
p1.say_hi()
在这里可以加功能啊... 你好啊,帅哥 nick
- 疑问,类方法如果有参数呢,好说,好说
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 = 'nick' @classmethod # say_hi=classmethod(say_hi) def say_hi(cls, msg): print('你好啊,帅哥 %s %s' % (cls.name, msg)) people.say_hi('你是那偷心的贼') p1 = people()
在这里可以加功能啊... 你好啊,帅哥 nick 你是那偷心的贼
p1.say_hi('你是那偷心的贼')
在这里可以加功能啊... 你好啊,帅哥 nick 你是那偷心的贼
自定制@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()
在这里可以加功能啊... ------> 1 2 3
p1.say_hi(4, 5, 6)
在这里可以加功能啊... ------> 4 5 6
上一篇: Photoshop CS3教程:GIF图像格式的设置技巧
下一篇: __iter__