Pthon魔术方法(Magic Methods)-描述器
Pthon魔术方法(Magic Methods)-描述器
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.描述器概述
1>.描述器定义
Python中,一个类实现了"__get__","__set__","__delete__"三个方法中的任何一个方法,就是描述器。 实现着三个中的某些方法,就支持了描述器协议:
仅实现了"__get__",就是非数据描述器,即non-data descriptor
实现了"__get__","__set__"就是数据描述器,即data descriptor
"__delete__"方法有同样的效果,有了这个方法,也是数据描述器。 如果一个类属性设置为描述器实例,那么它被称为owner属主。 当该类的该类属性被查找,设置,删除时,就会调用描述器相应的方法。
2>.非数据描述器案例
#!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class A:
def __init__(self):
self.a1 = "a1"
print("A.init") def __get__(self, instance, owner):
"""
因为定义了"__get__"方法,类A就是一个描述器,使用类B或者类B的实例来对x属性读取,就是对类A的实例的访问,就会调用"__get__"方法
参数说明如下:
self指代当前实例对象,即调用者,在本例中它对应的是A的实例
instance是owner的实例,在本例中它的值可能有两种:
None表示不是B类的实例,对应调用B.x
<__main__.B object at 0x00000284CBF675C8>表示是B的实例,对应调用B().x
owner是属性的所属的类
"""
print("A.__get__ {} {} {}".format(self,instance,owner))
return self class B:
x = A() #此时我们可以说x是非数据描述器,因为A()类中仅实现了"__get__"方法
def __init__(self):
print("B.init")
self.x = "b.x" #增加实例属性"x",由于这里的实例属性名称和上面的非数据描述器名称一致,此时赋值即定义,实例x变量会将类中的描述器标识符覆盖(因为A类中没有"__set__"方法可调用)。 print("-" * 20)
print(B.x)
print(B.x.a1) print("=" * 20)
b = B()
print(b.x)
# print(b.x.a1) #由于此时"b.x"访问到的是实例的属性,而不是非数据描述器,因此报错"AttributeError: 'str' object has no attribute 'a1'"
A.init
--------------------
A.__get__ <__main__.A object at 0x000001536AD65588> None <class '__main__.B'>
<__main__.A object at 0x000001536AD65588>
A.__get__ <__main__.A object at 0x000001536AD65588> None <class '__main__.B'>
a1
====================
B.init
b.x
以上代码执行结果戳这里
3>.数据描述器案例
#!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class A:
def __init__(self):
self.a1 = "a1"
print("A.init") def __get__(self, instance, owner):
"""
因为定义了"__get__"方法,类A就是一个描述器,使用类B或者类B的实例来对x属性读取,就是对类A的实例的访问,就会调用"__get__"方法
"""
print("A.__get__ {} {} {}".format(self,instance,owner))
return self def __set__(self, instance, value):
"""
因为同时定义了"__get__"和"__set__"方法,类A就是一个数据描述器,
"""
print("A.__set__ {} {} {}".format(self,instance,value))
self.data = value class B:
x = A() #这里就是一个数据描述器
def __init__(self):
print("B.init")
self.x = "b.x" #增加实例属性"x",由于这里的实例属性名称和上面的数据描述器名称一致,因此会调用上面的"__set__"方法 print("-" * 20)
print(B.x)
print(B.x.a1) print("=" * 20)
b = B()
print(b.x)
print(b.x.a1)
print(b.x.data)
b.x = 100 #这是调用数据描述器的"__set__"方法,或调用非数据描述器的实例覆盖
print(b.x)
B.x = 600 #赋值即定义,这是覆盖类属性,把描述器给替换了
print(B.x)
print(b.__dict__)
print(B.__dict__)
A.init
--------------------
A.__get__ <__main__.A object at 0x000001D054546708> None <class '__main__.B'>
<__main__.A object at 0x000001D054546708>
A.__get__ <__main__.A object at 0x000001D054546708> None <class '__main__.B'>
a1
====================
B.init
A.__set__ <__main__.A object at 0x000001D054546708> <__main__.B object at 0x000001D054546808> b.x
A.__get__ <__main__.A object at 0x000001D054546708> <__main__.B object at 0x000001D054546808> <class '__main__.B'>
<__main__.A object at 0x000001D054546708>
A.__get__ <__main__.A object at 0x000001D054546708> <__main__.B object at 0x000001D054546808> <class '__main__.B'>
a1
A.__get__ <__main__.A object at 0x000001D054546708> <__main__.B object at 0x000001D054546808> <class '__main__.B'>
b.x
A.__set__ <__main__.A object at 0x000001D054546708> <__main__.B object at 0x000001D054546808> 100
A.__get__ <__main__.A object at 0x000001D054546708> <__main__.B object at 0x000001D054546808> <class '__main__.B'>
<__main__.A object at 0x000001D054546708>
600
{}
{'__module__': '__main__', 'x': 600, '__init__': <function B.__init__ at 0x000001D054535438>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}
以上代码执行结果戳这里
4>.属性查找顺序
实例的"__dict__"优先于非数据描述器
数据描述器优先于实例的"__dict__"
5>.新增方法
#!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class A:
def __init__(self):
print("A.init") def __get__(self, instance, owner):
print(1,self,instance,owner)
return self def __set_name__(self, owner, name):
"""
提供在这个方法,就是可以知道主类和属主类的类属性名。
"""
print(2,self,owner,name)
self.name = name class B:
test_name = A() #类属性创建时调用描述器的"__set_name__"方法 print("=" * 30)
print(B().test_name.name)
A.init
2 <__main__.A object at 0x000002082F5F5488> <class '__main__.B'> test_name
==============================
1 <__main__.A object at 0x000002082F5F5488> <__main__.B object at 0x000002082F5F5588> <class '__main__.B'>
test_name
以上代码执行结果戳这里
二.Python中的描述器
#!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class A: def __init__(self): #非数据描述器
self.foo = 100
self.bar = 200
"""
由于foo和bar标识符调用的类装饰器非数据描述器,因此可以自行修改,而test标识符是数据描述器,此处修改会调用
"property"类的"__set__"方法,不允许修改test标识符,因此会报错:"AttributeError: can't set attribute"。
"""
# self.test = 300 def getfoo(self): #非数据描述器
return self.foo @classmethod #非数据描述器
def foo(cls):
pass @staticmethod #非数据描述器
def bar():
pass @property #数据描述器
def test(self):
return 100 a = A()
print(a.__dict__)
print(A.__dict__)
{'foo': 100, 'bar': 200}
{'__module__': '__main__', '__init__': <function A.__init__ at 0x000002ABBE125678>, 'getfoo': <function A.getfoo at 0x000002ABBE1251F8>, 'foo': <classmethod object at 0x000002ABBE136788>, 'bar': <staticmethod object at 0x000002ABBE1367C8>, 'test': <property object at 0x000002ABBE072228>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
以上代码执行结果戳这里
三.小试牛刀
1>.实现StaticMethod装饰器,完成staticmethod装饰器的功能
#!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class StaticMethod:
def __init__(self,fn):
self.fn = fn def __get__(self, instance, owner):
return self.fn class A:
@StaticMethod
def show():
print("A.show static method") A.show()
A().show() #以上代码执行结果如下:
A.show static method
A.show static method
2>.实现ClassMethod装饰器,完成classmethod装饰器的功能
#!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie from functools import partial class ClassMethod:
def __init__(self,fn):
self._fn = fn def __get__(self, instance, cls):
res = partial(self._fn,cls)
return res class A:
@ClassMethod
def show(cls):
print(cls.__name__) print(A.__dict__)
A.show
A.show() #以上代码执行结果如下:
{'__module__': '__main__', 'show': <__main__.ClassMethod object at 0x000001BB1C666548>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
A
3>.对实例的数据进行校验
#!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class Person:
def __init__(self,name:str,age:int):
self.name = name
self.age = age
"""
对上面的类的实例属性name,age进行数据校验 思路:
1>.写函数,在__init__中先检查,如果不合格,直接抛异常
2>.装饰器,使用inspect模块完成
3>.描述器
"""
#!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie class Person:
def __init__(self,name:str,age:int):
params = ((name,str),(age,int))
if not self.checkdata(params):
raise TypeError("传入参数树类型错误,请检查数据类型,要求传入参数为--->name:str,age:int")
self.name = name
self.age = age def checkdata(self,params):
for param,typ in params:
if not isinstance(param,typ):
return False
return True p1 = Person("Jason Yin","")
写检查函数参考案例(这种方法耦合度太高,不推荐使用)
#!/usr/bin/env python
#_*_conding:utf-8_*_
#@author :yinzhengjie
#blog:http://www.cnblogs.com/yinzhengjie import inspect class TypeCheck:
def __init__(self,name,typ):
self.name = name
self.type = typ def __get__(self, instance, owner):
print("TypeCheck.get")
if instance:
return instance.__dict__[self.name]
return self def __set__(self, instance, value):
print("TypeCheck.set")
if not isinstance(value,self.type):
raise TypeError(value)
instance.__dict__[self.name] = value class PropsInject:
def __init__(self,cls):
self.cls = cls
sig = inspect.signature(cls)
params = sig.parameters
for name,parm in params.items():
print(name,parm)
if parm.annotation != parm.empty: #注入类属性
setattr(cls,name,TypeCheck(name,parm.annotation)) def __call__(self, *args, **kwargs):
return self.cls(*args,**kwargs) #新构建一个新的Person对象 @PropsInject
class Person:
def __init__(self, name: str, age: int):
self.name = name
self.age = age print(Person.__dict__)
p1 = Person("Jason Yin",18)
p2 = Person("Jason Yin","")
描述器版本参考案例
Pthon魔术方法(Magic Methods)-描述器的更多相关文章
- php中的魔术方法(Magic methods)和魔术常亮
PHP中把以两个下划线__开头的方法称为魔术方法,这些方法在PHP中充当了举足轻重的作用. 魔术方法包括: __construct(),类的构造函数 __destruct(),类的析构函数 __cal ...
- Pthon魔术方法(Magic Methods)-反射
Pthon魔术方法(Magic Methods)-反射 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.反射概述 运行时,区别于编译时,指的时程序被加载到内存中执行的时候. 反射 ...
- Pthon魔术方法(Magic Methods)-上下文管理
Pthon魔术方法(Magic Methods)-上下文管理 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.上下文管理方法 __enter__: 进入与此对象相关的上下文.如果 ...
- Pthon魔术方法(Magic Methods)-可调用对象
Pthon魔术方法(Magic Methods)-可调用对象 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.可调用对象方法 __call__: 类中定义一个该方法,实例就可以像 ...
- Pthon魔术方法(Magic Methods)-容器相关方法
Pthon魔术方法(Magic Methods)-容器相关方法 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.容器相关方法汇总 __len__: 内建函数len(),返回对象的 ...
- Pthon魔术方法(Magic Methods)-运算符重载
Pthon魔术方法(Magic Methods)-运算符重载 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Python运算符对应的魔术方法 1>.比较运算符 <: ...
- Pthon魔术方法(Magic Methods)-bool
Pthon魔术方法(Magic Methods)-bool 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.bool方法 __bool__: 内建函数bool(),或者对象放在逻 ...
- Pthon魔术方法(Magic Methods)-hash
Pthon魔术方法(Magic Methods)-hash 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.hash方法 __hash__: 内建函数hash()调用的返回值,返 ...
- Pthon魔术方法(Magic Methods)-可视化
Pthon魔术方法(Magic Methods)-可视化 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.关于可视化的魔术方法简介 __str__: str()函数,format ...
随机推荐
- ["Visual Studio快捷键" ,"Vs","IDEA快捷键"]
描述说明 描述 说明 ↑ 方向键.上 ↓ 方向键.下 ← 方向键.左 → 方向键.右 快捷键大比拼 描述 Visual Studio 快捷键 IDEA快捷键 VisualStudio学名 IDEA学名 ...
- JKS转PFX
通过jks2pfx工具 请下载:JKS2PFX转换工具. 将压缩包解开到 c:\jks2pfx 目录下, 运行以下命令:JKS2PFX <导出文件名> [Java Runtime的目录]备 ...
- HTML5 VUE单页应用 SEO 优化之 预渲染(prerender-spa-plugin)
前言:当前 SPA 架构流行的趋势如日中天,前后端分离的业务模式已经成为互联网开发的主流方式,但是 单页面 应用始终存在一个痛点,那就是 SEO, 对于那些需要推广,希望能在百度搜索时排名靠前的网站而 ...
- Linux用户查询、新增&删除
1.查询用户tail -1 /etc/passwd 2.新增用户&用户组groupadd testgroup #组的添加useradd testuser #创建用户testuserpasswd ...
- MQTT的Res接口发布消息
MQTT(这里采用的V2版本)发布消息的常见方法: 1.通过MQTT客户端连接MQTT服务器,建立长连接,通过接口发布消息 最常见的客户端: <dependency> <groupI ...
- [转帖]记一次KUBERNETES/DOCKER网络排障
记一次KUBERNETES/DOCKER网络排障 https://coolshell.cn/articles/18654.html 记得之前在一个公众号里面看过这个文章 讲的挺好的.. 物理机直接跑d ...
- SACD-ISO音频镜像播放方式
SACD-ISO 音频文件不需要解压也不需要挂载光盘,可以直拖入播放器播放. 播放器下载 foobar2000https://www.foobar2000.org/download 解码插件下载 Su ...
- day09——初识函数
day09 函数的定义 # len() s = 'alexdsb' count = 0 for i in s: count += 1 print(count) s = [1,2,23,3,4,5,6] ...
- go 指针 通过指针修改int类型的值
指针的定义 :var p *int 取指针的值 :*p ------------------------------------------------------------------------ ...
- Navicat Premium 12 安装与破解,Navicat Premium通用的数据库管理工具
本文转自:https://blog.csdn.net/WYpersist/article/details/86530973 Navicat Premium 是一套数据库开发工具,让你从单一应用程序中同 ...