描述符
基本实例
不同于实例通用拦截方法(__getattr__等),描述符以单个属性出现,并针对该属性的不同访问行为自动做出响应。最重要的
是,描述符能'感知'通过什么引用该属性,从而和目标建立绑定关联。
完整的描述符实例
copyclass 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被自动调用,且会接收到类型和实例引用
这样就可以针对目标进行操作,比如,创建存储字段
copyx = 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大写,也就是类名
以类型引用进行赋值或删除操作,会导致描述符属性被替换或删除
copyX.data # get:None,<class '__main__.X'>
X.data = 100 # 以类型引用赋值,导致描述符属性被替换
# 赋值之后需打印才能显示出值
print(X.data) # 100
将描述符属性赋值给变量或传参时,实际结果是__get__方法的返回值
copyx = X()
x.data = 200 # 这里调用__set__方法
o = x.data # # 这里调用__get__方法返回值
print(o) # 200
数据描述符
如果定义了__set__或__delete__方法,那么我们便称其为数据描述符(data descriptor)
而仅有__set__的则是非数据描述符(non-data descriptor)。这两者的区别在于数据描述符属性的优先级高于实例名字空间中的同名成员
copyclass 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__依然存在,所以其优先级总是高于同名实例成员。
copyp = 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),如此就将函数包装
或绑定方法对象返回。实际会执行的,就是这个会隐式传入第一参数的包装品
copyclass 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学习笔记(上卷)
努力拼搏吧,不要害怕,不要去规划,不要迷茫。但你一定要在路上一直的走下去,尽管可能停滞不前,但也要走。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!