2

在 Python 中,如果类型的属性是没有方法的数据描述符,那么在属性更新object.__setattr__期间引发并type.__setattr__引发的基本原理是什么?同样,如果类型的属性是没有方法的数据描述符,那么在属性删除期间引发和引发的基本原理是什么?AttributeError__set__object.__delattr__type.__delattr__AttributeError__delete__

我之所以问这个问题是因为我注意到了这一点,object.__getattribute__并且如果该类型具有一个属性,该属性是一个没有方法的数据描述符,type.__getattribute__则不要在属性查找期间引发。AttributeError__get__

object.__getattribute__这是一个简单的程序,说明一方面属性查找(AttributeError未引发)与属性更新object.__setattr__和属性删除object.__delattr__另一方面(引发)之间的差异AttributeError

class DataDescriptor1:  # missing __get__
    def __set__(self, instance, value): pass
    def __delete__(self, instance): pass

class DataDescriptor2:  # missing __set__
    def __get__(self, instance, owner=None): pass
    def __delete__(self, instance): pass

class DataDescriptor3:  # missing __delete__
    def __get__(self, instance, owner=None): pass
    def __set__(self, instance, value): pass

class A:
    x = DataDescriptor1()
    y = DataDescriptor2()
    z = DataDescriptor3()

a = A()
vars(a).update({'x': 'foo', 'y': 'bar', 'z': 'baz'})

a.x
# actual: returns 'foo'
# expected: returns 'foo'

a.y = 'qux'
# actual: raises AttributeError: __set__
# expected: vars(a)['y'] == 'qux'

del a.z
# actual: raises AttributeError: __delete__
# expected: 'z' not in vars(a)

type.__getattribute__这是另一个简单的程序,说明一方面属性查找(AttributeError未引发)与另一方面属性更新和type.__setattr__属性删除(引发)之间的差异:type.__delattr__AttributeError

class DataDescriptor1:  # missing __get__
    def __set__(self, instance, value): pass
    def __delete__(self, instance): pass

class DataDescriptor2:  # missing __set__
    def __get__(self, instance, owner=None): pass
    def __delete__(self, instance): pass

class DataDescriptor3:  # missing __delete__
    def __get__(self, instance, owner=None): pass
    def __set__(self, instance, value): pass

class M(type):
    x = DataDescriptor1()
    y = DataDescriptor2()
    z = DataDescriptor3()

class A(metaclass=M):
    x = 'foo'
    y = 'bar'
    z = 'baz'

A.x
# actual: returns 'foo'
# expected: returns 'foo'

A.y = 'qux'
# actual: raises AttributeError: __set__
# expected: vars(A)['y'] == 'qux'

del A.z
# actual: raises AttributeError: __delete__
# expected: 'z' not in vars(A)

我希望实例字典发生突变,而不是获取AttributeError属性更新和属性删除。属性查找从实例字典返回一个值,所以我想知道为什么属性更新和属性删除也不使用实例字典(如果类型没有作为数据描述符的属性,他们会这样做)。

4

1 回答 1

3

我认为这只是 C 级设计的结果,没有人真正考虑或关心过。

在 C 级别,__set__并且__delete__对应于相同的 C 级别slottp_descr_set和删除是通过传递一个空值来指定的。(这类似于用于__setattr__and的设计__delattr__,它也对应于一个也被传递NULL给删除的槽。)

如果您实现__set__or __delete__,则 C 级插槽将设置为查找or并调用它的包装函数:__set____delete__

static int
slot_tp_descr_set(PyObject *self, PyObject *target, PyObject *value)
{
    PyObject* stack[3];
    PyObject *res;
    _Py_IDENTIFIER(__delete__);
    _Py_IDENTIFIER(__set__);

    stack[0] = self;
    stack[1] = target;
    if (value == NULL) {
        res = vectorcall_method(&PyId___delete__, stack, 2);
    }
    else {
        stack[2] = value;
        res = vectorcall_method(&PyId___set__, stack, 3);
    }
    if (res == NULL)
        return -1;
    Py_DECREF(res);
    return 0;
}

slot 没办法说“哎呀,没找到方法,回到正常处理”,也没有尝试。它也不会尝试模拟正常处理 - 这很容易出错,因为“正常处理”是依赖于类型的,并且它不知道要为所有类型模拟什么。如果插槽包装器没有找到该方法,它只会引发异常。

__set__如果并且已经获得了两个插槽,这种效果就不会发生__delete__,但是有人在设计 API 时必须关心,我怀疑有人这样做。

于 2021-03-22T23:47:20.107 回答