python描述器
描述器的表现
用到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('-'*20)
print(B.x.a1)
print('='*20)
b = B(0)
print(b.x.al)
# 运行结果
A.init
--------------------
a1
====================
B.init
a1
可以看出执行的先后顺序吧?
类加载的时候 ,类变量需要先生成,而类B的x属性是类A的实例,所以类A先初始化,所以打印
A.init.
然后执行到打印B.x.a1。
然后实例化并初始化日的实例b。
打印b.x.a1,会查找类属性b.x,指向A的实例,所以返回A实例的属性a1的值。
看懂执行流程了 ,再看下面的程序,对类A做一些改造。
如果在类A中实现__get__方法,看看变化
class A:
def __init__(self):
self.al = 'al'
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('-'*20)
print(B.x)
# print(B.x.a1) # 抛异常AttributeError: 'NoneType' object has no attribute 'a1'
print('=' * 20)
b = B()
print(b.x)
#print(b.x.a1) # 抛异常AttributeError: 'NoneType' object has no attribute 'a1'
# 运行结果
A.init
--------------------
A.__get__<__main__.A object at 0x000000000184E48> None <class '__main__.B'>
None
====================
B.init
None
B.init
A.__get__<__main__.A object at 0x0000000001084E48><__main__.B object at 0x00000000001084F28><class '__main__.B'>
None
因为定义了__get__方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对类
A的实例的访问,就会调用__get__方法
如何解决上例中访问报错的问题,问题应该来自__get__方法
self, instance, owner这个三个参数
<main.A object at 0x000000000184E48> None <class 'main.B'>
<main.A object at 0x0000000001084E48><main.B object at 0x00000000001084F28><class 'main.B'>
self都是A的实例
owner都是B类
instance是B的实例,None表示是没有B类的实例
使用返回值解决。返回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('-'*20)
print(B.x)
print(B.x.a1)
print('=' * 20)
b = B()
print(b.x)
print(b.x.a1)
那么类B的实例属性也可以
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('-'*20)
print(B.x)
print(B.x.a1)
print('=' * 20)
b = B()
print(b.x)
print(b.x.a1)
print(b.b) # 并没有触发__get__
从运行结果可以看出,只有类属性是类的实例才行
描述器定义
Python中,一个类实现了__get__、set、delete 三个方法中的任何一个方法,就是描述器
如果仅实现了__get__就是非数据描述符 non-data descriptor
同时实现了__get__、__set__就是数据描述符 data descriptor
如果一个类的类属性设置为描述器,那么它被称为owner属主
属性的访问顺序
为上例中的类B增加实例属性x
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' # 增加实例属性x
print('-'*20)
print(B.x)
print(B.x.a1)
print('=' * 20)
b = B()
print(b.x)
print(b.x.a1) # AttributeError: 'str' object has no attribute 'a1'
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.__get__{} {} {}".format(self, instance, value))
self.data = value
class B:
x = A()
def __init__(self):
print('B.init')
self.x = 'b.x' # 增加实例属性x
print('-'*20)
print(B.x)
print(B.x.a1)
print('=' * 20)
b = B()
print(b.x)
print(b.x.a1) # 返回a1
返回变成了a1,访问到了描述器的数据
属性查找顺序
实例的__dict__优先于 非数据描述器
数据描述器 优先于 实例的__dict__
delete 方法有同样的效果
本质
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.__get__{} {} {}".format(self, instance, value))
# self.data = value
class B:
x = A()
def __init__(self):
print('B.init')
self.x = 'b.x' # 增加实例属性x
self.y = 'b.y'
print('-'*20)
print(B.x)
print(B.x.a1)
print('=' * 20)
b = B()
print(b.x)
print(b.x.a1) # 返回a1
print(b.y)
print(b.__dict__)
print(B.__dict__)
# 屏鼓 __set__方法结果如下
字典
{x': 'b.x', 'y': 'b.y'}
{...}
# 不屏蔽__set__方法结果如下
{'y': 'b.y'}
{...}
原来不是什么 数据描述器 优先级高,而是把实例的属性从__dict__中给去除掉了,造成了该属性如果是数据描述器优先访问的假象说到底,属性访问顺序从来就没有变过。
python中的描述器
描述器在Python中应用非常广泛。
Python的方法(包括staticmethod(和classmethod0),都实现为非数据描述器。因此,实例可以重
新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为.
property()函数实现为一个数据猫述器。因此,实例不能覆盖属性的行为。
class A:
@classmethod
def foo(cls): #非数据描述器
pass
@staticmethod # 非数据描述㦛
def bar():
pass
@property # 数据描述器
def z(self):
return 5
def __init__(self): # 非数据描述器
self.foo = 100
self.bar = 200
self.z = 300
a = A()
print(a.__dict__)
print(A.__dict__)
foo、bar都可以在实例中覆盖,但是z不可以
练习
对实例的数据类型进行校验
class Typed:
def __init__(self, type):
self.type = type
def get_(self, instance, owner):
pass
def __set__(self, instance, value):
print('T.set', self, instance, value)
if not isinstance(value, self.type):
raise ValueError(value)
import inspect
class TypeAssert:
def __init__(self, cls):
params = inspect.signature(self.cls).parameters
print(params)
for name, param in params.items():
print(name, param.annotation)
if param.annotation != param. empty:
setattr(self.cls, name, Typed(param.annotation))# 注入类属性
print(self.cls.__dict__)
def __call__(self, name, age):
p= self.cls(name, age)# 重新构建一个新的Person对象
return p
@TypeAssert
class Person: # Person = TypeAssert(Person)
# name = Typed(str)
# age = Typed (int)
def __init__(self, name: str, age: int):
self.name = name
self.age = age
p1 = Person('tom', 18)
print(id(p1))
p2 = Person('tom', 20)
print(id(p2))
p3 = Person('tom', '20')