为什么在这种情况下__setattr__和__delattr__引发AttributeError? [英] Why do __setattr__ and __delattr__ raise an AttributeError in this case?
问题描述
在Python中, object .__ setattr __
和 type .__ setattr __
在属性 update <期间引发 AttributeError
的理由是什么?/em>如果类型具有作为 data 描述符的属性,但没有 __ set __
方法?同样,如果在属性删除中, object .__ delattr __
和 type .__ delattr __
引发 AttributeError
的理由是什么?类型具有作为 data 描述符的属性,而没有 __ delete __
方法?
In Python, what is the rationale for which object.__setattr__
and type.__setattr__
raise an AttributeError
during attribute update if the type has an attribute which is a data descriptor without a __set__
method? Likewise, what is the rationale for which object.__delattr__
and type.__delattr__
raise an AttributeError
during attribute deletion if the type has an attribute which is a data descriptor without a __delete__
method?
我之所以这样问,是因为我注意到 object .__ getattribute __
和 type .__ getattribute __
确实会引发 AttributeError 在属性 lookup 期间插入code>,如果该类型具有的属性是 data 描述符,而没有
__ get __
方法.
I am asking this because I have noticed that object.__getattribute__
and type.__getattribute__
do not raise an AttributeError
during attribute lookup if the type has an attribute which is a data descriptor without a __get__
method.
这是一个简单的程序,说明了一方面通过 object .__ getattribute __
查找属性(未引发 AttributeError
)与通过更新属性之间的差异.另一方面,object .__ setattr __
和属性被 object .__ delattr __
删除(引发 AttributeError
):
Here is a simple program illustrating the differences between attribute lookup by object.__getattribute__
on the one hand (AttributeError
is not raised), and attribute update by object.__setattr__
and attribute deletion by object.__delattr__
on the other hand (AttributeError
is raised):
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
):
Here is another simple program illustrating the differences between attribute lookup by type.__getattribute__
on the one hand (AttributeError
is not raised), and attribute update by type.__setattr__
and attribute deletion by type.__delattr__
on the other hand (AttributeError
is raised):
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
来进行属性更新和属性删除.属性查询从实例字典中返回一个值,所以我想知道为什么属性更新和属性删除也不使用实例字典(就像如果类型不具有作为数据描述符的属性那样,它们也会这样做).>
I would expect the instance dictionary to be mutated instead of getting an AttributeError
for attribute update and attribute deletion. Attribute lookup returns a value from the instance dictionary, so I am wondering why attribute update and attribute deletion do not use the instance dictionary as well (like they would do if the type did not have an attribute which is a data descriptor).
推荐答案
我认为这只是C级设计的结果,没人真正想到或关心过.
I think it's just a consequence of the C-level design that no one really thought or cared much about.
在C级别, __ set __
和 __ delete __
对应于同一C级别单个插槽,该插槽也会传递 NULL
进行删除.)
At C level, __set__
and __delete__
correspond to the same C-level slot, tp_descr_set
, and deletion is specified by passing a null value to set. (This is similar to the design used for __setattr__
and __delattr__
, which also correspond to a single slot that also gets passed NULL
for deletion.)
如果实现 __ set __
或 __ delete __
,则C级插槽将设置为
If you implement either __set__
or __delete__
, the C-level slot gets set to a wrapper function that looks for __set__
or __delete__
and calls it:
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;
}
插槽无法说哎呀,没找到方法,回到正常处理状态",并且它不会尝试.它还不尝试模仿正常处理,因为正常处理"会导致错误,因此容易出错.是类型相关的,并且它不知道所有类型都可以模拟什么.如果插槽包装程序找不到该方法,则只会引发异常.
The slot has no way to say "oops, didn't find the method, go back to normal handling", and it doesn't try. It also doesn't try to emulate the normal handling - that would be error-prone, since "normal handling" is type-dependent, and it can't know what to emulate for all types. If the slot wrapper doesn't find the method, it just raises an exception.
如果 __ set __
和 __ delete __
有两个插槽,则不会发生这种效果,但是在设计API时有人会在意,我怀疑做到了.
This effect wouldn't happen if __set__
and __delete__
had gotten two slots, but someone would have had to care while they were designing the API, and I doubt anyone did.
这篇关于为什么在这种情况下__setattr__和__delattr__引发AttributeError?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!