当前位置: 代码网 > it编程>前端脚本>Python > python教程:属性查询顺序,数据描述符

python教程:属性查询顺序,数据描述符

2024年05月21日 Python 我要评论
数据描述符,属性查找优先级 如果在一个类中定义了 __get__() , __set__(), __delete__() 这三种方法之一,那么这个类是一个描述符。 描述符分成两种: 如果这种类只定义了 __get__ 方法,那么就是一个非数据描述符, 定义了__get__()和__set__()的数 ...

数据描述符,属性查找优先级

如果在一个类中定义了 __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

属性查询优先级:

  1. obj.__getattribute__()

  2. 数据描述符

  3. 实例的字典

  4. 类的字典

  5. 非数据描述符

  6. 父类的字典

  7. __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)

说了一堆有的没的,其实描述符就是一个特殊的实现,当你的一个对象的属性是描述符时,设置/赋值/读取 这个属性,都会触发这个描述符内部相应实现的方法。从而可以实现一些定制化的内容。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com