描述符

基本实例

不同于实例通用拦截方法(__getattr__等),描述符以单个属性出现,并针对该属性的不同访问行为自动做出响应。最重要的
是,描述符能'感知'通过什么引用该属性,从而和目标建立绑定关联。
完整的描述符实例

class descriptor:
    def __set_name__(self,owner,name):
        print(f'name:{owner.__name__}.{name}')
        self.name = f'__{name}__'

    def __get__(self,instance,owner):
        print(f'get:{instance},{owner}')
        return getattr(instance,self.name,None)

    def __set__(self,instance,value):
        print(f'set:{instance},{value}')
        setattr(instance,self.name,value)
    def __delete__(self,instance):
        print(f'del:{instance}')
        raise AttributeError('delete is disabled')
class X: # name:X.data
    data = descriptor()

描述符的工作特性

描述符属性必须定义为类型成员,所以其自身不适合存储实例相关的状态
在创建属性时,__set__name__方法被调用,并通过参数获知目标类型(owner),以及属性名称
以类型或实例访问描述符属性时,get被自动调用,且会接收到类型和实例引用
这样就可以针对目标进行操作,比如,创建存储字段

x = X()
x.data = 100 # set:<__main__.X object at 0x000001D9BE3F09B0>,100
x.data # get:<__main__.X object at 0x000001D9BE3F09B0>,<class '__main__.X'>
print(x.data) #get:<__main__.X object at 0x000001D9BE3F09B0>,<class '__main__.X'>  100

方法__set__、__delete__仅在实例引用时被调用。

这里的X大写,也就是类名
以类型引用进行赋值或删除操作,会导致描述符属性被替换或删除

X.data # get:None,<class '__main__.X'>
X.data = 100 # 以类型引用赋值,导致描述符属性被替换
# 赋值之后需打印才能显示出值
print(X.data) # 100

将描述符属性赋值给变量或传参时,实际结果是__get__方法的返回值

x = X()
x.data = 200 # 这里调用__set__方法
o = x.data #  # 这里调用__get__方法返回值
print(o) # 200

数据描述符

如果定义了__set__或__delete__方法,那么我们便称其为数据描述符(data descriptor)
而仅有__set__的则是非数据描述符(non-data descriptor)。这两者的区别在于数据描述符属性的优先级高于实例名字空间中的同名成员

class descriptor:

    def __get__(self,instance,owner):
        print('__get__')
    def __set__(self,instance,value):
        print('__set__')
    

class X:
    data = descriptor()

x = X()
x.__dict__['data'] = 0 # __dict__又是什么
x.data = 100 # __set__
x.data # __get__

# 没有__set__
class descriptor:

    def __get__(self,instance,owner):
        print('__get__')
class X:
    data = descriptor()

x = X()
x.data = 10 # 同名实例成员的优先级高于非数据描述符
print(x.data) # 10 
print(vars(x)) # vars又是什么  {'data': 10}

属性property就是数据描述符。

就算没有提供setter方法,但__set__依然存在,所以其优先级总是高于同名实例成员。

p = property()
print(p.__get__) # <method-wrapper '__get__' of property object at 0x0000021E31C13E08>
print(p.__set__) # <method-wrapper '__set__' of property object at 0x0000021E31C13E08
print(p.__delete__) # <method-wrapper '__delete__' of property object at 0x0000021E31C13E08>
print(p.__delattr__) # <method-wrapper '__delattr__' of property object at 0x0000021E31C13E08>

方法绑定

函数默认实现了描述符协议,以实例或类型访问方法时
get__首先被调用
类型和实例作为参数被传入__get
。从而截获绑定目标(self),如此就将函数包装
或绑定方法对象返回。实际会执行的,就是这个会隐式传入第一参数的包装品

class X:
    def test(self,o):
        print(o)
x = X()
# 我理解的函数执行
# test被调用,返回内存地址
print(x.test) # <bound method X.test of <__main__.X object at 0x000001F5478606D8>>

# 传参的话;这个牵扯的是函数内部的执行顺序及与描述符的关系
x.test(123) # 123
# 执行顺序怎么牵扯到__get__呢,函数默认实现了描述符协议,描述符协议是什么
m = x.test.__get__(x,X) # 这里的X代表type(x),将函数包装绑定方法
print(m) # <bound method X.test of <__main__.X object at 0x000001F5478606D8>>
print(m.__self__,m.__func__) # <__main__.X object at 0x000001F5478606D8> <function X.test at 0x000001F547849840>

笔记来源Python3学习笔记(上卷)

原文地址:https://www.cnblogs.com/wkhzwmr/p/15580878.html