1、描述符定义
先看下描述符的定义。
如果在一个新式类中至少实现了 __ get__ (),__ set __ (), __ delete __ ()中的一个,则称作这个的新式类为描述符,也称为描述符协议。
- __ get __ ():调用一个属性时触发;
- __ set __():一个属性赋值时触发;
- __ delete __ ():采用del删除属性时触发;
定义一个描述符:
class Foo: # 在python3中Foo是新式类,它实现了__get__(),__set__(),__delete__()中的一个三种方法的一个,这个类就被称作一个描述符
def __get__(self, instance, owner):
pass
print("__get__() is called")
def __set__(self, instance, value):
pass
def __delete__(self, instance):
pass
2、描述符作用
使用描述符能够干什么?
描述符的作用是用来代理另外一个类的属性的,必须把描述符定义成这个类的类属性,不能定义到构造函数中
class Foo:
def __get__(self, instance, owner):
print("__get__() is called")
def __set__(self, instance, value):
print("__set__() is called")
def __delete__(self, instance):
print("__delete__() is called")
f = Foo()
上面的代码定义了一个描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法。
f.name = "jack"
f.name
del f.name
# 并不会触发Foo类中的任何方法
什么时候会触发这三个方法的执行呢?看下面的示例:
class Str:
"""描述符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): # name被Str类代理
self.name = name
p = People("alex") # 打印出 “Str设置...”,即调用了Str类中的__set__方法
p.name # 打印 “Str调用”
p.name = "jack" # 打印 “Str设置...”
del p.name # 打印 “Str删除...”
从上面示例可以看出,只有当描述符被定义成另一个类的类属性时,并且不能定义到另一个类中的构造方法中。
3、两种描述符
- 数据描述符
至少实现了__ 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')
4、描述符注意事项
- 描述符本身应该定义成新式类,被代理的类也应该是新式类;
- 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
- 要严格遵循该优先级,优先级由高到底分别是
- 类属性
- 数据描述符
- 实例属性
- 非数据描述符
- 找不到的属性触发 __ getattr __ ()
5、使用描述符
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__'
修改代码,解决上述错误
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>
6、自定制@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) # 调用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): # 此时的self参数就是描述符中的instance 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(r1.area) print(r1.area) print(r1.area) #输出 这是我们自己定制的静态属性,r1.area实际是要执行r1.area() 1 这是我们自己定制的静态属性,r1.area实际是要执行r1.area() 1 这是我们自己定制的静态属性,r1.area实际是要执行r1.area() 1
此时再调用r1.area时,每次都会访问描述符,缓存功能失效。原因就是上述的描述符实现了 __ set __()方法,它由非数据描述符变成了数据描述符,数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了
7、描述符总结
描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethod,@property甚至是__slots__属性
描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件。
原文出自:https://blog.csdn.net/qdPython/article/details/129203306
标签:__,name,Python,self,instance,描述符,def From: https://www.cnblogs.com/zhangchangchang/p/17926496.html