首页 > Python资料 博客日记
python教程:属性查询顺序,数据描述符
2024-05-21 17:00:07Python资料围观164次
数据描述符,属性查找优先级
如果在一个类中定义了 __get__()
, __set__()
, __delete__()
这三种方法之一,那么这个类是一个描述符。
描述符分成两种:
- 如果这种类只定义了
__get__
方法,那么就是一个非数据描述符, - 定义了
__get__()
和__set__()
的数据描述符。
描述符的用处就是,当一个对象的某个属性是一个描述符时,你访问这个描述符类型的属性,就会调用这个描述符的方法。譬如你获取描述符的值时,会调用它的__get__()
.
我们先看一下这三个方法的docstring:
def __delete__(self, *args, **kwargs): # real signature unknown
""" Delete an attribute of instance. """
# 删除一个实例的属性
def __set__(self, *args, **kwargs): # real signature unknown
""" Set an attribute of instance to value. """
# 给实例的属性设置一个值
def __get__(self, *args, **kwargs): # real signature unknown
""" Return an attribute of instance, which is of type owner. """
# 返回实例的属性,该实例是 `owner` 类型的
实例:
class A(object):
def __init__(self):
self.value = None
def __set__(self, instance, value): # self:类A的实例,也是类B的属性a;instance:类 B 的实例 b;value:通过b.a的赋值
print('set: self,instance,value',self,instance,value)
self.value = value
return self.value
def __get__(self, instance, owner):# instance:类B的实例b;owner:类B
print('get: self,instance,owner',self,instance,owner)
return self.value
class B(object):
a = A()
def __init__(self):
self.val = 20
1.上述代码中,有两个类,A,B。先看类 B,有一个类属性 a , 且 a 是类 A 的实例,我们先来实例化一下类 B ,看一下 类 B 和实例 b 的属性:
b = B()
print(b.__dict__)
print(B.__dict__)
"""
{'val': 20}
{'__module__': '__main__', 'a': <__main__.A object at 0x0163FD70>, '__init__': <function B.__init__ at 0x07845078>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}
"""
可以看出,实例 b 的属性中,只有一个 val 属性 ;类 B 的属性中,则有一个 a ,且 a 是类 A 的一个对象。
2.接下来,我们调用一下实例 a:
b = B()
b.a
B.a
"""
get: self,instance,owner <__main__.A object at 0x03458E68> <__main__.B object at 0x03458F28> <class '__main__.B'>
get: self,instance,owner <__main__.A object at 0x03458E68> None <class '__main__.B'>
"""
我们看一下什么意思:
-
当调用 b.a 时,程序会自动去调用
b.__getattribute__('a')
, 也就是b.__dict__['a']
, 即通过对象 b 的字典去查找属性,但是在第一步我们已经知道, 对象 b 只有一个属性 {'val': 20} ,既然在实例 b 中找不到 a。 所以会去父类中找,调用:type(b).__dict__['a'].__get__(b,type(b))
, 也就是:B.__dict__['a'].__get__(b,B)
,打印了第一行的信息,并返回了None -
当调用 B.a 时,会直接调用
B.__dict__['a'].__get__(None,B)
,所以第二处打印的信息中间有个 None
3.现在,我们尝试给 b.a 赋值
b = B()
b.a = 11
print(b.__dict__)
print(B.__dict__)
B.a = 12
print(b.__dict__)
print(B.__dict__)
"""
set: self,instance,value <__main__.A object at 0x037CFD70> <__main__.B object at 0x037CFDD0> 11
{'val': 20}
{'__module__': '__main__', 'a': <__main__.A object at 0x037CFD70>, '__init__': <function B.__init__ at 0x07E85078>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}
{'val': 20}
{'__module__': '__main__', 'a': 12, '__init__': <function B.__init__ at 0x07E85078>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}
"""
可以看出,当调用了 b.a=11 时,调用了描述符的 __set__()
, 但是对象 b 的实例属性并没有改变,依然只有 val=20, 同时类B的类属性也没有改变。 但是当调用 B.a = 12 时,类属性 a 变成了12,并没有调用描述符的__set__()
方法。
所以,结合上面的 docstring,我们可以看出,数据描述符应该是给实例使用的,类使用它用处不大,至少没法调用它的 __set__()
-
如果类属性的描述符对象和实例对象的属性同名,如果查找?
-
也就是说,如果把类B改成:
class B(object):
a = A()
def __init__(self):
self.val = 20
self.a = 11 # 这里同名的属性
此时调用 b.a ,会如何?
当类A是一个数据描述符,也就是说类A包含 __set__
方法时,此时数据描述符优先级高,所以实例属性 self.a 其实就是对类属性 a 的赋值,会调用数据描述符的 __set__
方法:
set: self,instance,value <__main__.A object at 0x009DFD70> <__main__.B object at 0x009DFDD0> 11
get: self,instance,owner <__main__.A object at 0x009DFD70> <__main__.B object at 0x009DFDD0> <class '__main__.B'>
11
当类A是一个非数据描述符,那么实例的字典优先级高,所以会使用实例字典中的数据,即结果:
11
属性查询优先级:
-
obj.__getattribute__()
-
数据描述符
-
实例的字典
-
类的字典
-
非数据描述符
-
父类的字典
-
__getattr__
class Quantity1(object):
def __get__(self, instance, owner):
return 2
def __set__(self, instance, val):
pass
class Quantity2(object):
def __get__(self, instance, owner):
return 5
class A(object):
val = 6 # 6 父类属性
x = None
class B(A):
val = Quantity2() # 5 非覆盖型描述符
val = 4 # 4 类属性
val = Quantity1() # 2 覆盖型描述符
def __init__(self):
super(B, self).__init__()
self.val = 3
def __getattr__(self, name): # 7 __getattr__
return 7
def __getattribute__(self, name): # 1 __getattribute__
return 1
b = B()
print(b.val)
说了一堆有的没的,其实描述符就是一个特殊的实现,当你的一个对象的属性是描述符时,设置/赋值/读取 这个属性,都会触发这个描述符内部相应实现的方法。从而可以实现一些定制化的内容。
标签:
相关文章
最新发布
- 【Python】selenium安装+Microsoft Edge驱动器下载配置流程
- Python 中自动打开网页并点击[自动化脚本],Selenium
- Anaconda基础使用
- 【Python】成功解决 TypeError: ‘<‘ not supported between instances of ‘str’ and ‘int’
- manim边学边做--三维的点和线
- CPython是最常用的Python解释器之一,也是Python官方实现。它是用C语言编写的,旨在提供一个高效且易于使用的Python解释器。
- Anaconda安装配置Jupyter(2024最新版)
- Python中读取Excel最快的几种方法!
- Python某城市美食商家爬虫数据可视化分析和推荐查询系统毕业设计论文开题报告
- 如何使用 Python 批量检测和转换 JSONL 文件编码为 UTF-8
点击排行
- 版本匹配指南:Numpy版本和Python版本的对应关系
- 版本匹配指南:PyTorch版本、torchvision 版本和Python版本的对应关系
- Python 可视化 web 神器:streamlit、Gradio、dash、nicegui;低代码 Python Web 框架:PyWebIO
- 相关性分析——Pearson相关系数+热力图(附data和Python完整代码)
- Python与PyTorch的版本对应
- Anaconda版本和Python版本对应关系(持续更新...)
- Python pyinstaller打包exe最完整教程
- Windows上安装 Python 环境并配置环境变量 (超详细教程)