描述器

描述器

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

相关推荐

  • Linux三剑客之sed

     Sed简介       sed 是一种在线编辑器,它一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处 理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有 改变,除非你使用重定向存…

    Linux干货 2016-08-12
  • Linux 基础 (1)

    su nano shell type hash alias date cal screen echo $ tab (df bc rz(sz) ifconfig ping tty who whoami w) 1.用户  root为超级用户 1) useradd oracle  创建一个oracle用户     su &#…

    2017-07-13
  • dstat,htop,top,ps命令 (Blog 8)

      Linux哲学思想之一:一切皆文件:内核将其参数全部映射为文件;这些文件保存在/proc, /sys目录中;/proc目录:保存内核及进程状态信息,是内核参数的映射; 内核参数:可调参数:调整运行特性;/proc/sys不可调参数:输出内核统计信息及状态信息; 进程状态:/proc/# 与进程号同名的目录,目录下存放许多当前进程相关信息; 进程…

    Linux干货 2017-11-27
  • Linux 文件系统权限

    一、简述权限  文件系统的权限管理机制的建立,约束了用户对数据的操作。 1、对系统安全而言  管理员的操作权限非常大,足以破坏系统,权限机制将管理员与普通用户之间区分开,防止系统被随意破坏。 2、对用户而言  Linux是一个多用户的操作系统,不同用户间为了防止其他人破坏数据或访问数据,文件系统的权限管理是非常必要的。 二、文件…

    Linux干货 2016-08-04
  • 磁盘管理(一)磁盘分区表类型与文件系统

    概述:本章介绍磁盘分区和文件系统,深度了解linux存储数据所依赖到各个部分模块。 设备文件 I/O Ports: I/O设备地址 一切皆文件: Linux为所有的设备文件都提供了统一的操作函数接口,方法是使用数据结构struct file_operations。这个数据结构中包括许多操作函数的指针,如open()、close()、read()和write(…

    Linux干货 2016-09-01
  • 马哥教育网络20期+第7周练习博客

    1、创建一个10G分区,并格式为ext4文件系统;    (1) 要求其block大小为2048, 预留空间百分比为2, 卷标为MYDATA, 默认挂载属性包含acl;    (2) 挂载至/data/mydata目录,要求挂载时禁止程序自动运行,且不更新文件的访问时间戳; [root@7b ~]#&nbsp…

    Linux干货 2016-07-24