首页 > 其他分享 >描述器(Descriptor)

描述器(Descriptor)

时间:2024-12-05 16:00:33浏览次数:5  
标签:__ instance obj descriptor prop Descriptor 描述 cls

描述器(又称描述符)(Descriptor)

描述器:如果一个类中实现了__get__, __set__, __delete__三个方法中的任何一个,那么这样的类的实例就称为描述器。 当某一个类的类属性是一个描述器的时候,通过这个类或者类的实例来访问、修改或删除这个类属性时,就会分别触发描述器的__get__, __set__和__delete__方法。

class Descriptor():
    def __get__(self, instance, owner):
        return {'instance': instance, 'owner': owner, 'value': 12345}

glb_descriptor = Descriptor()  # 在模块全局声明一个描述器

class A:
    prop = glb_descriptor

class B:
    def __init__(self):
        self.prop = glb_descriptor  # 由于描述器不是B类的类属性,所以不会起作用。

class C:
    cls_descriptor = Descriptor()  # 在类定义中声明一个描述器
    def __init__(self):
        # 如下三种方式都能将描述器的__get__返回内容添加到实例的__dict__属性中,但内容略有不同,主要区别在于instance不同。
        self.prop = self.cls_descriptor  # c.prop: {'instance': <__main__.C object at 0x00000235799B6210>, 'owner': <class '__main__.C'>, 'value': 12345}
        # self.prop = C.cls_descriptor   # c.prop: {'instance': None, 'owner': <class '__main__.C'>, 'value': 12345}
        # self.prop = type(self).cls_descriptor  # c.prop: {'instance': None, 'owner': <class '__main__.C'>, 'value': 12345}

a = A()
b = B()
c = C()
print('a.prop:', a.prop)
print('b.prop:', b.prop)
print('c.prop:', c.prop)

print('-' * 25, '分隔线', '-' * 25)

print(A.prop)
print(C.cls_descriptor)

print('-' * 25, '分隔线', '-' * 25)

print(a.__dict__)  # a实例的__dict__没有内容
print(c.__dict__)  # c实例的__dict__中存储了描述器__get__方法的返回值。因为在c的实例化过程中,`__init__`方法里就已经通过"实例.属性"(self.cls_descriptor)的方式触发了描述器的`__get__`方法。

输出结果:

a.prop: {'instance': <__main__.A object at 0x0000017589326190>, 'owner': <class '__main__.A'>, 'value': 12345}
b.prop: <__main__.Descriptor object at 0x00000175893256D0>
c.prop: {'instance': <__main__.C object at 0x0000017589326210>, 'owner': <class '__main__.C'>, 'value': 12345}
------------------------- 分隔线 -------------------------
{'instance': None, 'owner': <class '__main__.A'>, 'value': 12345}
{'instance': None, 'owner': <class '__main__.C'>, 'value': 12345}
------------------------- 分隔线 -------------------------
{}
{'prop': {'instance': <__main__.C object at 0x0000017589326210>, 'owner': <class '__main__.C'>, 'value': 12345}}

非数据描述器: 只实现了__get__方法,未实现__set__和__delete__方法中的任何一个。这种情况下,实例优先访问自身的属性,如果不存在,再找描述器。

class Descriptor():
    def __get__(self, instance, owner) :
        return {'instance': instance, 'owner': owner, 'value': 12345}

class C:
    cls_descriptor = Descriptor()  # 在类定义中声明一个描述器
    def __init__(self, prop):
        self.cls_descriptor = prop  # cls_descriptor是非数据描述器,prop值会保存在实例自己的同名属性中。


c = C('88888')

print('c.cls_descriptor:', c.cls_descriptor)

print('-' * 25, '分隔线', '-' * 25)


print(C.cls_descriptor)

print('-' * 25, '分隔线', '-' * 25)
print(c.cls_descriptor)
print(c.__dict__)  # 由于是非数据描述器,给实例中与类属性描述器同名的属性赋值不会触发描述器,而是直接保存在实例属性中。

输出结果:

c.cls_descriptor: 88888
------------------------- 分隔线 -------------------------
{'instance': None, 'owner': <class '__main__.C'>, 'value': 12345}
------------------------- 分隔线 -------------------------
88888
{'cls_descriptor': '88888'}

数据描述器: 描述器实现了__set__或__delete__方法,此时通过实例访问属性时,优先触发描述器,而不是优先访问实例属性。

class Descriptor():
    def __get__(self, instance, owner) :
        return {'instance': instance, 'owner': owner, 'value': 12345}

    def __set__(self, instance, value):
        instance.papapa = value

class C:
    cls_descriptor = Descriptor()  # 在类定义中声明一个描述器
    def __init__(self, prop):
        self.cls_descriptor = prop  # 类属性cls_descriptor是描述器,通过实例访问这个属性会触发描述器的__set__方法,而不会在实例中存储一个同名属性。


c = C('88888')

print('c.cls_descriptor:', c.cls_descriptor)

print('-' * 25, '分隔线', '-' * 25)


print(C.cls_descriptor)

print('-' * 25, '分隔线', '-' * 25)
print(c.cls_descriptor)
print(c.__dict__)  # 由于cls_descriptor是数据描述器,所以实例在初始化时`self.cls_descriptor = prop`实际上会触发描述器的`__set__`方法,这样,实例自身的`__dict__`中并没有成功填加与描述器同名的属性。。

输出结果:

c.cls_descriptor: {'instance': <__main__.C object at 0x000001BBE5075B90>, 'owner': <class '__main__.C'>, 'value': 12345}
------------------------- 分隔线 -------------------------
{'instance': None, 'owner': <class '__main__.C'>, 'value': 12345}
------------------------- 分隔线 -------------------------
{'instance': <__main__.C object at 0x000001BBE5075B90>, 'owner': <class '__main__.C'>, 'value': 12345}
{'papapa': '88888'}

下面为了说明属性查找的优先级,我直接通过实例的__dict__字典来添加实例属性,但在通过实例.属性的方式来查找属性时,仍然会显示调用描述器的结果。
属性查找顺序: 数据描述器 > 实例属性 > 非数据描述器

class Descriptor():
    def __get__(self, instance, owner) :
        return {'instance': instance, 'owner': owner, 'value': 12345}

    def __set__(self, instance, value):
        instance.papapa = value

class C:
    cls_descriptor = Descriptor()  # 在类定义中声明一个描述器
    def __init__(self, prop):
        self.cls_descriptor = prop  # 类属性cls_descriptor是描述器,通过实例访问这个属性会触发描述器的__set__方法,而不会在实例中存储一个同名属性。


c = C('88888')
c.__dict__['cls_descriptor'] = '这是实例自己的属性' # 为了不触发描述器的__set__方法,直接通过实例的__dict__字典为其添加实例属性。
print('c.cls_descriptor:', c.cls_descriptor) # 这里仍然触发了描述器方法,而不是显示实例自身的属性的值
print('-' * 25, '分隔线', '-' * 25)
print(C.cls_descriptor)
print('-' * 25, '分隔线', '-' * 25)
print(c.cls_descriptor)
print(c.__dict__)  # 这里保存了实例初始化时触发描述器时生成的属性,以及直接通过__dict__字典添加的实例属性

输出结果:

c.cls_descriptor: {'instance': <__main__.C object at 0x000002189035BAD0>, 'owner': <class '__main__.C'>, 'value': 12345}
------------------------- 分隔线 -------------------------
{'instance': None, 'owner': <class '__main__.C'>, 'value': 12345}
------------------------- 分隔线 -------------------------
{'instance': <__main__.C object at 0x000002189035BAD0>, 'owner': <class '__main__.C'>, 'value': 12345}
{'papapa': '88888', 'cls_descriptor': '这是实例自己的属性'}

__getattribute__魔术方法的等价实现: __getattribute__方法是用c实现的,如非必需要,尽量不要覆写此方法。此方法的内部实现也体现了python内部属性访问机制的复杂性。 但其内部本身不包含对 __getattr__ 方法的调用。即,只有当__getattribute__返回AttributeError时, __getattr__ 方法才会被触发。

如下为官方文档中使用python写的__getattribute__的内部机制的模拟实现。

def find_name_in_mro(cls, name, default):
    "Emulate _PyType_Lookup() in Objects/typeobject.c"
    for base in cls.__mro__:
        if name in vars(base):
            return vars(base)[name]
    return default

def object_getattribute(obj, name):
    "Emulate PyObject_GenericGetAttr() in Objects/object.c"
    null = object()
    objtype = type(obj)
    cls_var = find_name_in_mro(objtype, name, null)
    descr_get = getattr(type(cls_var), '__get__', null)
    if descr_get is not null:
        if (hasattr(type(cls_var), '__set__')
            or hasattr(type(cls_var), '__delete__')):
            return descr_get(cls_var, obj, objtype)     # data descriptor
    if hasattr(obj, '__dict__') and name in vars(obj):
        return vars(obj)[name]                          # instance variable
    if descr_get is not null:
        return descr_get(cls_var, obj, objtype)         # non-data descriptor
    if cls_var is not null:
        return cls_var                                  # class variable
    raise AttributeError(name)

下面是我写的一个类似的等价实现,当然没有官方写的好。

def find_in_mro(cls_obj, name):
    for cls in cls_obj.mro():
        if name in vars(cls):
            return cls, vars(cls)[name]
    else:
        return None, None


def object_getattribute(obj, name):
    cls_obj = type(obj)
    cls_prop, prop_obj = find_in_mro(cls_obj, name) # 查找类属性

    if prop_obj:  # 存在同名类属性
        _, descriptior_get = find_in_mro(type(prop_obj), '__get__')
        _, descriptior_set = find_in_mro(type(prop_obj), '__set__')
        _, descriptior_del = find_in_mro(type(prop_obj), '__delete__')
        if descriptior_get: # 类属性是描述器
            if descriptior_set or descriptior_del: # 类属性是数据描述器
                return descriptior_get(prop_obj, obj, cls_obj)
            else: # 类属性是 非数据描述器
                if name in vars(obj):  # 存在同名实例属性
                    return vars(obj)[name]
                else: # 不存在同名实例属性
                    return descriptior_get(prop_obj, obj, cls_obj)
        else:
            if name in vars(obj):  # 存在同名实例属性
                return vars(obj)[name]
            else:
                return prop_obj
    else: # 不存在同名类属性
        if name in vars(obj):  # 存在同名实例属性
            return vars(obj)[name]
        else: # 类和实例中都未找到该属性
            raise AttributeError(name)

下面是对自己写的方法的一个测试:

class Descriptor:
    def __get__(self, instance, owner):
        print('描述器 __get__ 被调用')
        return '描述器get返回值'

    def __set__(self, instance, value):
        print('描述器 __set__ 被调用')
        pass  # 让其成为 数据描述器

class A:
    cls_prop = Descriptor()
    pass

class B(A):
    b_cls_prop = 'value of b_cls_prop'
    pass

输出结果: 如果注释掉描述器的__set__方法,结果将会不同。

描述器 __get__ 被调用
描述器get返回值
{}
------------------------- 分隔线 -------------------------
描述器 __set__ 被调用
描述器 __get__ 被调用
描述器get返回值
{}
------------------------- 分隔线 -------------------------
描述器 __get__ 被调用
描述器get返回值
value of b_cls_prop

标签:__,instance,obj,descriptor,prop,Descriptor,描述,cls
From: https://www.cnblogs.com/rolandhe/p/18588769

相关文章

  • 进程描述和创建
    进程描述操作系统通过进程控制块PCB来描述进程,对应Linux内核数据结构structtask_struct在Linux3.18.6内核中,定义于include/linux/sched.h#1235pid和tgid标识进程state进程状态stack进程堆栈CONFIG_SMP在多处理器时使用fs文件系统描述tty控制台files进程打开文件的文件描......
  • 请描述处TCP的三次握手和四次挥手
    TCP的三次握手和四次挥手是前端面试中经常被问到的网络基础知识。它们保证了可靠的连接建立和断开。下面我分别描述一下这两个过程:三次握手(Three-wayhandshake)三次握手的目的是同步连接双方的序列号和确认号,并交换TCP窗口大小信息。SYN(Synchronize):客户端发送一个S......
  • matlab描述一个涉及多种粒子状态及其相互作用的物理系统,可能用于求解该系统在给定条件
    %定义粒子状态,能级对应的未知数n_states={'n1','1s5';'n2','1s4';'n3','1s3';'n4','1s2';'n5','2p10';'n6','2p9'......
  • ORB-SLAM2源码学习:MapPoint.cc:MapPoint::ComputeDistinctiveDescriptors()计算地图点
    前言地图点在投影匹配时只能对应一个特征描述子,选择具有代表性的描述子是必要的。1.函数声明/*由于一个地图点会被许多相机观测到,因此在插入关键帧后,需要判断是否更新代表当前点的描述子先获得当前点的所有描述子,然后计算描述子之间的两两距离,最好的描述子与其他描述子......
  • 恶补英语初级第12天,《描述进行中的动作》
    对话Where’sSally,Jack?She’sinthegarden,Jane.What’sshedoing?she’ssittingunderthetree.IsTiminthegarden,too?Yes,heis.He’sclimbingthetree.Ibegyourpardon?Who’sclimbingthetree?Timis.Whataboutthedog?Thedog’s......
  • 计算机操作系统进程的描述与控制选择题
    1、正在执行的进程由于其时间片用完而被暂停运行,此时该进程应从运行态变为()。A、就绪态B、运行态C、等待态D、终止态2、某单处理器系统中若同时存在5个进程,则处于等待状态的进程最多可有()个。A、1B、0C、5D、43、一个进程退出等待队列而进入就绪队列,是因为进程()。A、获得了......
  • 原生Math的方法有哪些?请列举并描述其功能
    原生JavaScript的Math对象提供了一系列用于数学运算的属性和方法。以下是一些常用的方法及其功能:基本运算:Math.abs(x):返回x的绝对值。Math.ceil(x):返回大于或等于x的最小整数。Math.floor(x):返回小于或等于x的最大整数。Math.round(x):返回x四舍五入后......
  • 请描述下application cache的更新过程?
    ApplicationCache,或者说是AppCache,是一个已经被废弃的HTML5特性,用于离线存储网页资源。由于其更新机制复杂且容易出错,它已经被ServiceWorkers和CacheAPI取代。尽管如此,如果您仍然需要了解其更新过程,以下是其工作原理:manifest文件检查:浏览器会定期检查manifest......
  • 分别写出防抖和节流的两个函数,并描述它们分别有什么运用场景?
    //防抖函数(Debounce)functiondebounce(func,delay){lettimer;returnfunction(){clearTimeout(timer);timer=setTimeout(()=>{func.apply(this,arguments);},delay);};}//节流函数(Throttle)functionthrottle(func,delay)......
  • 请描述一下cookies、sessionStorage和localStorage的区别?
    在前端开发中,cookies、sessionStorage和localStorage都是用于在浏览器中存储数据的机制,但它们之间存在显著的区别:1.数据的生命周期:Cookies:Cookie的生命周期可以通过expires或max-age属性设置。如果没有设置过期时间,Cookie会在浏览器会话结束时(关闭浏览器)被删除,这......