首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >在这种情况下,为什么__setattr__和__delattr__会引发AttributeError?

在这种情况下,为什么__setattr__和__delattr__会引发AttributeError?
EN

Stack Overflow用户
提问于 2021-03-22 21:27:07
回答 1查看 565关注 0票数 3

在Python中,如果类型的属性是没有object.__setattr__方法的数据描述符,那么type.__setattr__AttributeError在属性更新期间引发AttributeError的理由是什么?同样,如果类型的属性是一个没有object.__delattr__方法的数据描述符,那么在删除属性期间,type.__delattr__AttributeError的理由是什么?

我之所以问这个问题,是因为我注意到,如果类型有一个属性(即没有__get__方法的数据描述符),则在属性查找期间,type.__getattribute__AttributeError不会引发AttributeError

下面是一个简单的程序,说明了object.__getattribute__查找属性(不引发AttributeError)与object.__setattr__更新属性和object.__delattr__删除属性(提出AttributeError)之间的区别:

代码语言:javascript
代码运行次数:0
运行
复制
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)之间的区别:

代码语言:javascript
代码运行次数:0
运行
复制
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。属性查找返回实例字典中的值,因此我想知道为什么属性更新和属性删除也不使用实例字典(就像如果类型没有数据描述符的属性一样)。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-03-22 23:47:20

我认为这只是C级设计的一个结果,没有人真正想到或关心它。

在C级别,__set____delete__对应于相同的C级插槽tp_descr_set,而删除是通过传递一个空值来指定的。(这与__setattr____delattr__使用的设计类似,它们也对应于一个插槽,后者也通过NULL进行删除。)

如果您实现了__set____delete__,则C级插槽被设置为查找__set____delete__并调用它的包装函数

代码语言:javascript
代码运行次数:0
运行
复制
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;
}

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

如果__set____delete__有两个插槽,这种效果就不会发生,但是当他们设计API时,会有人关心这个问题,我怀疑有人会这么做。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66754075

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档