描述器

描述器

|[Descriptors]
描述器的表现
  • 用到3个魔术方法:__get__()、__set__()、__delete__()
  • 方法用法:
  • object.__get__(self,instance,owner)
  • object.__set__(self,instance,value)
  • object.__delete__(self,instance)
  • self指代当前实例,调用者
  • instance是owner的实例
  • owner是属性所属的类
  • 看下面代码,思考执行流程。
class A:
def __init__(self):
self.a1 = ‘a1’
print(“A.init”)
class B:
x = A()
def __init__(self):
print(“B.init”)
print(‘-‘*30)
print(B.x.a1)
print(‘=’*30)
b = B()
print(b.x.a1)
—————————
A.init#先打印这个是因为在函数载入内存时已经完成初始化
——————————
a1
==============================
B.init
a1
  • 结论:
    • 类加载的时候,类变量需要先生成,而类B的x属性是类A的实例,所以类A先初始化,所以打印A.init
    • 然后执行到B.x.a1.然后打印实例化并初始化B的实例b
    • 打印b.x.a1,会查找属性b.x,指向A的实例,所以返回A实例的属性a1的值
__get__方法举例
class A:
def __init__(self):
self.a1 = ‘a1’
print(“A.init”)
def __get__(self, instance, owner):
print(“A.__get__ {}/{}/{}”.format(self,instance,owner))
class B:
x = A()
def __init__(self):
print(“B.init”)
print(‘-‘*30)
print(B.x)
#print(B.x.a1)#抛异常AttributeError:NoneType object has no attribute a1
print(‘=’*30)
b = B()
print(b.x)
+++++++++++++++++++++++++++++++++++++
A.init
——————————
A.__get__ <__main__.A object at 0x000000310E7387B8>/None/<class ‘__main__.B’>
None
==============================
B.init
A.__get__ <__main__.A object at 0x000000310E7387B8>/<__main__.B object at 0x000000310E738828>/<class ‘__main__.B’>
None
  • 因为定义了__get__方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对类A的实例的访问,就会调用get方法
  • 解决上面方法中B.x.a1抛错问题,报错是因为添加get方法后造成的,get三个参数的的含义:
  • slef都是A的实例
  • owner都是B类
  • instance说明:
    • None表示没有B类的实例,对应调用B.x
    • <__main__.B object at 0x000000310E738828>表示时B的实例,对应调用B().x
  • 使用返回值解决,返回self,就是A的实例,该实例有a1属性,返回正常
class A:
def __init__(self):
self.a1 = ‘a1’
print(“A.init”)
def __get__(self, instance, owner):
print(“A.__get__ {}/{}/{}”.format(self,instance,owner))
return self
class B:
x = A()
def __init__(self):
print(“B.init”)
print(‘-‘*30)
print(B.x)
print(B.x.a1)#上一版因为B.x的return默认为None所以None.a1肯定会报错
print(‘=’*30)
b = B()
print(b.x)
print(b.x.a1)
———————————-
A.init
——————————
A.__get__ <__main__.A object at 0x000000F4472A87B8>/None/<class ‘__main__.B’>
<__main__.A object at 0x000000F4472A87B8>
A.__get__ <__main__.A object at 0x000000F4472A87B8>/None/<class ‘__main__.B’>
a1
==============================
B.init
A.__get__ <__main__.A object at 0x000000F4472A87B8>/<__main__.B object at 0x000000F4472A8828>/<class ‘__main__.B’>
<__main__.A object at 0x000000F4472A87B8>
A.__get__ <__main__.A object at 0x000000F4472A87B8>/<__main__.B object at 0x000000F4472A8828>/<class ‘__main__.B’>
a1

class A:
def __init__(self):
self.a1 = ‘a1’
print(“A.init”)
def __get__(self, instance, owner):
print(“A.__get__ {}/{}/{}”.format(self,instance,owner))
return self
class B:
x = A()
def __init__(self):
print(“B.init”)
self.b = A()#实例属性也指向一个A的实例
print(‘-‘*30)
print(B.x)
print(B.x.a1)
print(‘=’*30)
b = B()
print(b.x)
print(b.x.a1)
print(b.b)
———————-
A.init
——————————
A.__get__ <__main__.A object at 0x000000D7F03C87B8>/None/<class ‘__main__.B’>
<__main__.A object at 0x000000D7F03C87B8>
A.__get__ <__main__.A object at 0x000000D7F03C87B8>/None/<class ‘__main__.B’>
a1
==============================
B.init
A.init
A.__get__ <__main__.A object at 0x000000D7F03C87B8>/<__main__.B object at 0x000000D7F03C87F0>/<class ‘__main__.B’>
<__main__.A object at 0x000000D7F03C87B8>
A.__get__ <__main__.A object at 0x000000D7F03C87B8>/<__main__.B object at 0x000000D7F03C87F0>/<class ‘__main__.B’>
a1
<__main__.A object at 0x000000D7F03C8860>#这个结果并没有出发get方法
结论:只有类属性是类的实例才可以触发get
描述器定义
  • Python中,一个类实现了__get__、__set__、__delete__三个方法中的任何一个方法,就是描述器
  • 如果仅实现了__get__,就是非数据描述符non-data descriptor
  • 同时实现了__get__、__set__就是数据描述符data descriptor
  • 属性的访问顺序
class A:
def __init__(self):
self.a1 = ‘a1’
print(“A.init”)
def __get__(self, instance, owner):
print(“A.__get__ {}/{}/{}”.format(self,instance,owner))
return self
class B:
x = A()
def __init__(self):
print(“B.init”)
self.x = ‘b.x’
print(‘-‘*30)
print(B.x)
print(B.x.a1)
print(‘=’*30)
b = B()
print(b.x)
print(b.x.a1)#attributeerror:str object has no attribute a1
———————-
A.init
——————————
A.__get__ <__main__.A object at 0x000000BC91538860>/None/<class ‘__main__.B’>
<__main__.A object at 0x000000BC91538860>
A.__get__ <__main__.A object at 0x000000BC91538860>/None/<class ‘__main__.B’>
a1
==============================
B.init
b.x#用实例化的对象去调用x时,查看到本身字典中有key则直接返回b.x
  • b.x访问到了实例的属性,而不是描述器
  • 下面代码为A类增加set方法
class A:
def __init__(self):
self.a1 = ‘a1’
print(“A.init”)
def __get__(self, instance, owner):
print(“A.__get__ {}/{}/{}”.format(self,instance,owner))
return self
def __set__(self, instance, value):
print(‘A.__set__ {}-{}-{}’.format(self,instance,value))
self.data = value
class B:
x = A()
def __init__(self):
print(“B.init”)
self.x = ‘b.x’
print(‘-‘*30)
print(B.x)
print(B.x.a1)
print(‘=’*30)
b = B()
print(b.x)
print(b.x.a1)
—————————
A.init
——————————
A.__get__ <__main__.A object at 0x00000034F8688860>/None/<class ‘__main__.B’>
<__main__.A object at 0x00000034F8688860>
A.__get__ <__main__.A object at 0x00000034F8688860>/None/<class ‘__main__.B’>
a1
==============================
B.init
A.__set__ <__main__.A object at 0x00000034F8688860>-<__main__.B object at 0x00000034F8688898>-b.x
A.__get__ <__main__.A object at 0x00000034F8688860>/<__main__.B object at 0x00000034F8688898>/<class ‘__main__.B’>
<__main__.A object at 0x00000034F8688860>
A.__get__ <__main__.A object at 0x00000034F8688860>/<__main__.B object at 0x00000034F8688898>/<class ‘__main__.B’>
a1#可以返回a1
结论:属性查找顺序:
实例的__dict__优先于非数据描述器
数据描述器 优先于实例的__dict__
__delete__方法有同样的效果,有了这个方法,就是数据描述器
  • 增加b.x = 500,这是调用数据描述器的__set__方法,或调用那个非数据描述器的实例覆盖
  • B.x = 600,赋值即定义,这是覆盖类的属性。类的字典更新
本质(进阶)
  • python真的会做这么复杂吗,再来一套属性查找顺序规则?看看非数据描述器和数据描述器,类B及其__dict__的变化
  • 屏蔽和不屏蔽__set__方法,看看变化
class A:
def __init__(self):
self.a1 = ‘a1’
print(“A.init”)
def __get__(self, instance, owner):
print(“A.__get__ {}/{}/{}”.format(self,instance,owner))
return self
# def __set__(self, instance, value):
# print(‘A.__set__ {}-{}-{}’.format(self,instance,value))
# self.data = value
class B:
x = A()
def __init__(self):
print(“B.init”)
self.x = ‘b.x’
self.y = ‘b.y’
print(‘-‘*30)
print(B.x)
print(B.x.a1)
—————————
#屏蔽set方法结果如下
A.init
——————————
A.__get__ <__main__.A object at 0x000000954E2F87B8>/None/<class ‘__main__.B’>
<__main__.A object at 0x000000954E2F87B8>
A.__get__ <__main__.A object at 0x000000954E2F87B8>/None/<class ‘__main__.B’>
a1
==============================
B.init
b.x
b.y
字典
{‘x’: ‘b.x’, ‘y’: ‘b.y’}
{‘x’: <__main__.A object at 0x000000954E2F87B8>, ‘__module__’: ‘__main__’, ‘__init__’: <function B.__init__ at 0x000000954E2EB840>, ‘__weakref__’: <attribute ‘__weakref__’ of ‘B’ objects>, ‘__doc__’: None, ‘__dict__’: <attribute ‘__dict__’ of ‘B’ objects>}
#不屏蔽set方法结果如下:
A.init
——————————
A.__get__ <__main__.A object at 0x0000006E4ED187B8>/None/<class ‘__main__.B’>
<__main__.A object at 0x0000006E4ED187B8>
A.__get__ <__main__.A object at 0x0000006E4ED187B8>/None/<class ‘__main__.B’>
a1
==============================
B.init
A.__set__ <__main__.A object at 0x0000006E4ED187B8>-<__main__.B object at 0x0000006E4ED18828>-b.x
A.__get__ <__main__.A object at 0x0000006E4ED187B8>/<__main__.B object at 0x0000006E4ED18828>/<class ‘__main__.B’>
<__main__.A object at 0x0000006E4ED187B8>
b.y
字典
{‘y’: ‘b.y’}###此处可见差别
{‘__weakref__’: <attribute ‘__weakref__’ of ‘B’ objects>, ‘x’: <__main__.A object at 0x0000006E4ED187B8>, ‘__dict__’: <attribute ‘__dict__’ of ‘B’ objects>, ‘__init__’: <function B.__init__ at 0x0000006E4ED0B8C8>, ‘__doc__’: None, ‘__module__’: ‘__main__’}
  • 原来不是什么数据描述器优先级高,而是把实例的属性从__dict__中去掉,造成了该属性如果是数据描述器优先访问的假象
  • 说到底,属性访问顺序从来没有变过
Python中的描述器
  • 描述器在python中广泛应用
  • python的方法(包括staticmethod()和classmethod())都实现为非数据描述器,因此,实例可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为?
  • property()函数实现为一个数据描述器,因此,实例不能覆盖属性的行为。
class A:
@classmethod#非数据类型
def foo(cls):
pass
@staticmethod#非数据类型
def bar():
pass
@property#数据类型
def z(self):
return 5
def getfoo(self):#非数据类型
return self.foo
def __init__(self):#非数据类型
self.foo = 100
self.bar = 200
#self.z = 300
a = A()
print(a.__dict__)
print(A.__dict__)
———————————-
{‘bar’: 200, ‘foo’: 100}
{‘__module__’: ‘__main__’, ‘getfoo’: <function A.getfoo at 0x000000C288C8B950>, ‘__doc__’: None, ‘__weakref__’: <attribute ‘__weakref__’ of ‘A’ objects>, ‘__init__’: <function A.__init__ at 0x000000C288C8B9D8>, ‘z’: <property object at 0x000000C288C96458>, ‘__dict__’: <attribute ‘__dict__’ of ‘A’ objects>, ‘bar’: <staticmethod object at 0x000000C288C98908>, ‘foo’: <classmethod object at 0x000000C288C988D0>}
  • foo、bar都可以在实例中覆盖,z不可以
练习
1.实现StaticMethod装饰器,完成staticmethod装饰器的功能
class StaticMethod:
def __init__(self,fn):
self._fn = fn
def __get__(self, instance, owner):
print(‘get’)
return self._fn
class A:
@StaticMethod#stmd = Staticmethod(stmd)
def stmd():
print(‘static method’)
A.stmd()
A().stmd()
print(A.__dict__)
—————————-
get
static method
get
static method
{‘__dict__’: <attribute ‘__dict__’ of ‘A’ objects>, ‘__module__’: ‘__main__’, ‘stmd’: <__main__.StaticMethod object at 0x000000BD760B8320>, ‘__weakref__’: <attribute ‘__weakref__’ of ‘A’ objects>, ‘__doc__’: None}
2.实现ClassMethod装饰器,完成classmethod装饰器的功能
from functools import partial
#类classmethod装饰器
class ClassMethod:
def __init__(self,fn):
self._fn = fn
def __get__(self, instance, owner):
ret = self._fn(owner)
return ret
class A:
@ClassMethod#clsmth = ClassMethod(clsmth)
def clsmth(cls):
print(cls.__name__)
print(A.__dict__)
A.clsmth
A.clsmth()
———————–
{‘clsmth’: <__main__.ClassMethod object at 0x000000E925388828>, ‘__dict__’: <attribute ‘__dict__’ of ‘A’ objects>, ‘__doc__’: None, ‘__weakref__’: <attribute ‘__weakref__’ of ‘A’ objects>, ‘__module__’: ‘__main__’}
A
File “F:/pycharm_product/python/1117/7.py”, line 37, in <module>
A
A.clsmth()
TypeError: ‘NoneType’ object is not callable
  • A.clsmtd()的意思就是None(),一定报错,如何改?
  • A.clsmtd()其实应该是A.clsmtd(cls)(),应该怎么处理?
  • A.clsmetd = A.clsmtd(cls)
  • 用partial函数
# class StaticMethod:
# def __init__(self,fn):
# self._fn = fn
#
# def __get__(self, instance, owner):
# print(‘get’)
# return self._fn
#
# class A:
#
# @StaticMethod#stmd = Staticmethod(stmd)
# def stmd():
# print(‘static method’)
#
# A.stmd()
# A().stmd()
# print(A.__dict__)
#
from functools import partial
#类classmethod装饰器
class ClassMethod:
def __init__(self,fn):
self._fn = fn
def __get__(self, instance, cls):
ret = partial(self._fn,cls)
return ret
class A:
@ClassMethod#clsmth = ClassMethod(clsmth)
def clsmth(cls):
print(cls.__name__)
print(A.__dict__)
print(A.clsmth)
A.clsmth()
—————————-
{‘__doc__’: None, ‘__module__’: ‘__main__’, ‘__dict__’: <attribute ‘__dict__’ of ‘A’ objects>, ‘__weakref__’: <attribute ‘__weakref__’ of ‘A’ objects>, ‘clsmth’: <__main__.ClassMethod object at 0x000000F5D31187F0>}
functools.partial(<function A.clsmth at 0x000000F5D31791E0>, <class ‘__main__.A’>)
A
  • 对实例的数据进行校验
class Person:
def __init__(self,name:str,age:int):
self.name = name
self.age = age
  • 对上面类的实例的属性,name、age进行数据校验
  • 方法一:写函数,在init中检查如果不合格,直接抛异常
#缺点:耦合度高,
class Person:
def __init__(self,name:str,age:int):
params = ((name,str),(age,int))
if not self.checkdata(params):
raise TypeError()
self.name = name
self.age = age
def checkdata(self,params):
for p,t in params:
if not isinstance(p,t):
return False
return True
p = Person(‘tom’,’20’)#传入类型错误
  • 方法二:装饰器,使用inspect模块完成
  • 方法三描述器
#缺点:有硬编码,能否直接获取形参类型,使用inspect模块,如下个方法
class Type:
def __init__(self,name,type):
self.name = name
self.type = type
def __get__(self, instance, owner):
if instance is not None:
return instance.__dict__[self.name]
return self
def __set__(self, instance, value):
if not isinstance(value,self.type):
raise TypeError(value)
instance.__dict__[self.name] = value
class Person:
name = Type(‘name’,str)
age = Type(‘age’,int)
def __init__(self,name:str,age:int):
self.name = name
self.age = age
p = Person(‘tom’,’20’)
———————————–
Traceback (most recent call last):
File “F:/pycharm_product/python/1117/8.py”, line 40, in <module>
p = Person(‘tom’,’20’)
File “F:/pycharm_product/python/1117/8.py”, line 38, in __init__
self.age = age
File “F:/pycharm_product/python/1117/8.py”, line 29, in __set__
raise TypeError(value)
TypeError: 20
# class Person:
# def __init__(self,name:str,age:int):
# params = ((name,str),(age,int))
# if not self.checkdata(params):
# raise TypeError()
# self.name = name
# self.age = age
#
# def checkdata(self,params):
# for p,t in params:
# if not isinstance(p,t):
# return False
# return True
#
# p = Person(‘tom’,’20’)
class Typed:
def __init__(self,name,type):
self.name = name
self.type = type
def __get__(self, instance, owner):
if instance is not None:
return instance.__dict__[self.name]
return self
def __set__(self, instance, value):
if not isinstance(value,self.type):
raise TypeError(value)
instance.__dict__[self.name] = value
import inspect
def typeassert(cls):
params = inspect.signature(cls).parameters
print(params)
for name,param in params.items():
print(param.name,param.annotation)
if param.annotation != param.empty:#注入类型
setattr(cls,name,Typed(name,param.annotation))
return cls
@typeassert
class Person:
#name = Typed(‘name’,str)#装饰器注入
#age = Typed(‘age’,int)
def __init__(self,name:str,age:int):
self.name = name
self.age = age
p = Person(‘tom’,20)
————————-
OrderedDict([(‘name’, <Parameter “name:str”>), (‘age’, <Parameter “age:int”>)])
name <class ‘str’>
age <class ‘int’>

本文来自投稿,不代表Linux运维部落立场,如若转载,请注明出处:http://www.178linux.com/89076

(0)
Thunk_LeeThunk_Lee
上一篇 2017-11-28 10:04
下一篇 2017-11-29 12:52

相关推荐

  • 使用NFS服务和samba部署wordpress

             centos 7.3主机一台   centos 6.8主机一台  使用yum安装的mysql(7以后使用yum装mysql叫mariadb)         我事先查看了一…

    2017-05-02
  • 第六周作业

    1、复制/etc/rc.d/rc.sysinit文件至/tmp目录,将/tmp/rc.sysinit文件中的以至少一个空白字符开头的行的行首加#;          cp/etc/rc.d/rc.sysinit /tmp/     &nbs…

    2017-09-04
  • 软件包管理工具——yum

        在linux上安装管理软件包会遇到包依赖问题,使用yum工具就可以很好的解决这个问题,yum的工作原理基于c/s结构:客户端与服务器,搭建好客户端与服务器,就可以使用yum工具了。 一、搭建yum仓库客户端 1.yum仓库的路径有三种:http://, ftp://,file://,首先创建一个配置文件,用来存放yum仓库的路径,…

    2017-08-06
  • hello! 我的博客第一站

    大家好!  这是我进博客的第一天,一个刚进来的新司机。在这里我就不秀我的车技了,只希望各位老司机开车不要太快,我晕车      —— 生命不息,奋斗不止

    Linux干货 2017-07-11
  • Linux DNS服务系列之主从复制、子域授权和转发、view配置详解

    前言 上文我们讲解了DNS服务的原理及正反向解析配置,相信大家对DNS服务已经有了初步了解。接下来,让我们进一步了解DNS服务的其它功能,本文将详解主从复制、子域授权和转发以及view的相关配置。 主从服务器配置 主从服务器关系 如果公司内DNS服务器负载过重或者为了实现冗余这一类功能就需要用到一个备份DNS服务器,备份服务器和主DNS服务器就形成了主从关系…

    Linux干货 2015-04-13
  • shell编程之变量,数值计算,字符比较,文件测试小记

     变量     变量:能储存计算结果或能表示值抽象概念,其指向的内存空间中一段地址。        变量赋值:name=value    溢出:字符超过定义内存中间大小    变量类型:数据类型,存储的格式,参与的运算   &nb…

    Linux干货 2016-08-15