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","")

描述器版本参考案例

上一篇:MySQL所有函数及操作符


下一篇:nginx配置入门