描述器的表现

用到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__、setdelete 三个方法中的任何一个方法,就是描述器

如果仅实现了__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')