描述器

描述器

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

相关推荐

  • 第二周博客作业

    1、Linux上的文件管理类命令都有哪些,其常用的使用方法及其相关示例演示? cat(concatenate)#从头开始看     文本文件查看工具 SYNOPSIS:     cat [OPTION]… [FILE]… -A 输出行最后加上$号 -n 输出行号 例…

    Linux干货 2016-12-12
  • 第五周练习

    1、显示当前系统上root、fedora或user1用户的默认shell 2、找出/etc/rc.d/init.d/functions文件中某单词后面跟一小组括号的行,刑如:hello() 3、使用echo命令输出一个绝对路径,使用grep取出基名 4、找出ifconfig命令结果中的1-255之间的数字 5、查找/var目录下属主为root,且属组为mai…

    2017-10-29
  • Linux进程篇16.4top命令:进程管理工具

    top命令:进程管理工具

    2017-12-18
  • inode 与 block

    iNode:索引节点(index node) iNode是用来存储数据属性信息的,iNode包含的属性包括:文件大小属组归属的用户组读写权限文件类型修改时间指向文件实体的指针功能(iNode节点和block的对应关系)但是,iNode不包括文件名 iNode小结: 磁盘分区格式化为ext4文件系统后会生成一定数量的iNode和block iNode是索引节点…

    Linux干货 2017-07-18
  • Shell脚本-循环基础

    Shell脚本-循环基础 背景: 正在学习Shell脚本之循环,发现Shell的循环和其他编程语言大同小异,逻辑上都是相通的,但在使用格式上却有点不同,在学习完Shell循环后,将学习的心得体会记录下来,以备今后复习。 介绍: 什么是Shell脚本:       shell script是利用shell的功能…

    2017-08-26
  • Linux文件管理和Bash特性

    一、Linux基本文件管理命令 主要介绍cp、mv、rm命令的基本用法以及使用示例 cp命令 cp命令用来将一个或多个源文件或者目录复制到指定的目的文件或目录 cp (选项) (参数) 常用选项: -a:此参数的效果和同时指定”-dpR”参数相同,用于实现归档; -d:复制符号链接文件本身,而非其指向的源文件; -f:强行复制文件或目录,不论目标文件或目录是…

    Linux干货 2017-07-09