描述符(__get__和__set__和__delete__)

一、描述符

  • 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__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 = 'randy'
f1.name
del f1.name

2.1 何时,何地,会触发这三个方法的执行

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 = 'randy'
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

三、两种描述符

3.1 数据描述符

  • 至少实现了__get__()和__set__()
class Foo:
def __set__(self, instance, value):
print('set') def __get__(self, instance, owner):
print('get')

3.2 非数据描述符

  • 没有实现__set__()
class Foo:
def __get__(self, instance, owner):
print('get')

四、描述符注意事项

  1. 描述符本身应该定义成新式类,被代理的类也应该是新式类

  2. 必须把描述符定义成这个类的类属性,不能为定义到构造函数中

  3. 要严格遵循该优先级,优先级由高到底分别是

    1.类属性

    2.数据描述符

    3.实例属性

    4.非数据描述符

    5.找不到的属性触发__getattr__()

五、使用描述符

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

5.1 牛刀小试

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 # 触发__set__
self.age = age
self.salary = salary p1 = People('randy', 18, 3231.3)

set---> <__main__.People object at 0x107a86198> randy

  • 调用
print(p1.__dict__)

print(p1.name)

{'name': 'randy', 'age': 18, 'salary': 3231.3}

get---> <__main__.People object at 0x107a86198> <class '__main__.People'>

randy

  • 赋值
print(p1.__dict__)
p1.name = 'randysun'
print(p1.__dict__)

{'name': 'randy', 'age': 18, 'salary': 3231.3}

set---> <__main__.People object at 0x107a86198> randysun

{'name': 'randysun', 'age': 18, 'salary': 3231.3}

  • 删除
print(p1.__dict__)
del p1.name
print(p1.__dict__)

{'name': 'randysun', 'age': 18, 'salary': 3231.3}

delete---> <__main__.People object at 0x107a86198>

{'age': 18, 'salary': 3231.3}

5.2 拔刀相助

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>

5.3 磨刀霍霍

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'>

5.4 大刀阔斧

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('randy', '18', 3333.3)
except Exception as e:
print(e)

set---> <__main__.People object at 0x1078dd438> randy

set---> <__main__.People object at 0x1078dd438> 18

Expected <class 'int'>

p1 = People('randy', 18, 3333.3)

set---> <__main__.People object at 0x1081b3da0> randy

set---> <__main__.People object at 0x1081b3da0> 18

set---> <__main__.People object at 0x1081b3da0> 3333.3

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

5.4.1 类的装饰器:无参

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('randy', 18, 3333.3)
类的装饰器开始运行啦------>

5.4.2 类的装饰器:有参

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('randy', 18, 3333.3)
类的装饰器开始运行啦------> {'name': <class 'str'>, 'age': <class 'int'>, 'salary': <class 'float'>}

5.5 刀光剑影

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('randy', 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> randy
set---> <__main__.People object at 0x1080b22e8> 18
set---> <__main__.People object at 0x1080b22e8> 3333.3

六、描述符总结

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

七、自定制@property

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

7.1 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

7.2 自定制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

7.3 实现延迟计算功能

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 = 'randy' @ClassMethod # say_hi=ClassMethod(say_hi)
def say_hi(cls):
print('你好啊,帅哥 %s' % cls.name) People.say_hi() p1 = People()
在这里可以加功能啊...
你好啊,帅哥 randy
p1.say_hi()
在这里可以加功能啊...
你好啊,帅哥 randy
  • 疑问,类方法如果有参数呢,好说,好说
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 = 'randy' @ClassMethod # say_hi=ClassMethod(say_hi)
def say_hi(cls, msg):
print('你好啊,帅哥 %s %s' % (cls.name, msg)) People.say_hi('你是那偷心的贼') p1 = People()
在这里可以加功能啊...
你好啊,帅哥 randy 你是那偷心的贼
p1.say_hi('你是那偷心的贼')
在这里可以加功能啊...
你好啊,帅哥 randy 你是那偷心的贼

一十、自定制@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

最新文章

  1. 引人瞩目的 CSS 变量(CSS Variable)
  2. YYStock开源----iOS股票K线绘制第二版
  3. [开源 .NET 跨平台 数据采集 爬虫框架: DotnetSpider] [二] 基本使用
  4. MJExtension 中model嵌套Model
  5. YWE复习
  6. android中使用PopupWindow实现弹出窗口菜单
  7. js实现class样式的修改、添加及删除的方法
  8. 最简单的视频编码器:基于libx265(编码YUV为H.265)
  9. 2014辛星完全解读html第五节
  10. Hibernate(一)
  11. 【ASP.NET MVC 学习笔记】- 13 Child Action
  12. 关于awk的多文件处理
  13. 吾八哥学Selenium(二):操作输入框/按钮的方法
  14. 微信小程序(七)文章详情页面动态显示
  15. Django REST framework 第六章 ViewSets &amp; Routers
  16. I2C驱动框架 (kernel-3.4.2)
  17. Atitit atiplat_reader 基于url阅读器的新特性
  18. c++ 用new创建二维数组~创建指针数组【转】
  19. HDFS上传数据的流程
  20. Android 目录结构

热门文章

  1. 黑马oracle_day01:02.oracle的基本操作
  2. Linux-课后练习(第二章命令)20200217-2
  3. PHP基础(9.27 第十三天)
  4. POJ - 1065 Wooden Sticks(贪心+dp+最长递减子序列+Dilworth定理)
  5. npm - 换淘宝源
  6. COGS1487 麻球繁衍
  7. jquery判断当前浏览器是否是IE
  8. swift中block的使用
  9. vue知识点散记
  10. XXE--XML外部实体注入漏洞