描述符(__get__和__set__和__delete__)
-%E9%A2%84%E8%AD%A6.gif)
一、描述符
描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__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
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 = '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
三、两种描述符
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')
四、描述符注意事项
-%E6%B3%A8%E6%84%8F.jpg?x-oss-process=style/watermark)
描述符本身应该定义成新式类,被代理的类也应该是新式类
必须把描述符定义成这个类的类属性,不能为定义到构造函数中
要严格遵循该优先级,优先级由高到底分别是
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
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}
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('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,这时候我需要教你一招:独孤九剑
-%E7%8B%AC%E5%AD%A4%E4%B9%9D%E5%89%91.jpg?x-oss-process=style/watermark)
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('nick', 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('nick', 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('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__属性
描述父是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.
-%E7%A7%81%E4%BA%BA%E8%AE%A2%E5%88%B6.jpg?x-oss-process=style/watermark)
七、自定制@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 = '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
-%E6%94%BE%E5%81%87.jpg?x-oss-process=style/watermark)
描述符(__get__和__set__和__delete__)的更多相关文章
- 描述符__get__,__set__,__delete__
描述符__get__,__set__,__delete__ # 描述符:1用来代理另外一个类的属性 # __get__():调用一个属性时,触发 # __set__():为一个属性赋值时触发 # __ ...
- 描述符__get__(),__set__(),__delete__()(三十七)
http://www.cnblogs.com/linhaifeng/articles/6204014.html#_label12 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__ ...
- python小知识-__call__和类装饰器的结合使用,数据描述符__get__\__set__\__delete__(描述符类是Python中一种用于储存类属性值的对象)
class Decorator(): def __init__(self, f): print('run in init......') self.f = f def __call__(self, a ...
- python基础----再看property、描述符(__get__,__set__,__delete__)
一.再看property 一个静态属性property ...
- 描述符__get__,__set__,__delete__和析构方法__del__
描述符__get__,__set__,__delete__ 1.描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一 ...
- python 面向对象专题(八):特殊方法 (一)__get__、__set__、__delete__ 描述符(一)
https://www.cnblogs.com/flashBoxer/p/9771797.html 实现了 __get__.__set__ 或 __delete__ 方法的类是描述符.描述符的用法是, ...
- python 面向对象专题(十一):特殊方法 (四)__get__、__set__、__delete__ 描述符(四)描述符用法建议
使用特性以保持简单 内置的 property 类创建的其实是覆盖型描述符,__set__ 方法和__get__ 方法都实现了,即便不定义设值方法也是如此. 特性的__set__ 方法默认抛出 Attr ...
- python 面向对象专题(九):特殊方法 (二)__get__、__set__、__delete__ 描述符(二)覆盖型与非覆盖型描述符对比
前言 根据是否定义__set__ 方法,描述符可分为两大类. 实现 __set__ 方法的描述符属于覆盖型描述符,因为虽然描述符是类属性,但是实现 __set__ 方法的话,会覆盖对实例属性的赋值操作 ...
- Python描述符(__get__,__set__,__delete__)简介
先说定义,这里直接翻译官方英文文档: 一般来说,描述符是具有“绑定行为”的对象属性,该对象的属性访问将会被描述符协议中的方法覆盖.这些方法是__get__(),__set__(),和__delete_ ...
随机推荐
- windows 下使用批处理执行 postgresql 命令行操作
1.准备好命令文件 loraserver.sql create role loraserver_as with login password 'dbpassword'; create role lor ...
- SQL Server之批量清理数据库的死锁
DECLARE killspid CURSOR FOR (SELECT CONVERT(VARCHAR(100), request_session_id) FROM sys.dm_tran_l ...
- 如何通过 subprocess 持续获取输出内容
在实际应用中会用到subprocess的Popen方法执行一些命令,而我们需要通过执行这个命令的来获取输出进行一些信息记录或者分析使用,如果是很快就可以执行完的那还好,有时需要持续跟踪内容的输出,比如 ...
- ACM课内练习_1
题意很简单就是一个一个素数因子只有2,3,5,7的整数,让你求它的约束的个数(暴力会超时),一开始的思路是先计算2,3,5,7这四个素数因子的个数,求出来之后想了很久没有想出他们个数和约束个数之间的数 ...
- Ubuntu16.04重装NVIDIA驱动
Ubuntu系统 $ sudo apt update $ sudo apt upgrade 之后出现显卡驱动出现故障,nvidia-smi输出有错,检测不到相应的驱动.只好重装,记录一下,太多的教程根 ...
- pandas.apply()函数
1.介绍 apply函数是pandas里面所有函数中自由度最高的函数.该函数如下: DataFrame.apply(func, axis=0, broadcast=False, raw=False, ...
- 【开发工具】本机安装的JDK8,启动IDEA2019没反应
问题描述 本来开发工具安装的是IDEA2018,有天用着用着突然崩溃了,重启后死活用不了.心血来潮下载了2019版本,顺利安装完,但是点击快捷方式启动的时候一直没反应.后来咨询同事,在下面的启动脚本中 ...
- 【朝花夕拾】Android自定义View篇之(四)自定义View的三种实现方式及自定义属性使用介绍
前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/10979161.html],谢谢! 尽管Android系统提供了不少控件,但是有很多酷炫效果仍然 ...
- RobotFramework - IF、FOR语句使用
一.IF 语句 1. IF语句简单实现 < Log 111111 2. IF语句赋值实现 ${rst} Set Variable If 1 < 2 2 1 3. IF...ELSE实现 $ ...
- ubuntu18.04 安装 搜狗输入法
一.安装fcitx sudo apt-get install fcitx-bin 因为搜狗拼音依赖fcitx,相关的依赖库和框架都会自动安装上. sudo apt-get install fcitx- ...