Python 在这种情况下,为什么setattr和delattr会引发AttributeError?
在Python中,Python 在这种情况下,为什么setattr和delattr会引发AttributeError?,python,attributes,lookup,python-descriptors,python-datamodel,Python,Attributes,Lookup,Python Descriptors,Python Datamodel,在Python中,对象和类型的基本原理是什么?同样地,object.\uuuuu delattr\uuuuuu和type.\uuuuu delattr\uuuuuuu在属性删除过程中,如果类型具有作为数据描述符的属性而没有\uuu delete\uuuuuu方法,则该对象的基本原理是什么 我问这个问题是因为我注意到对象.\uuuuu getattribute\uuuuu和类型.\uuuuu getattribute\uuuuuu在属性查找过程中,如果类型有一个属性,而该属性是一个数据描述符,而没
对象和类型的基本原理是什么?同样地,object.\uuuuu delattr\uuuuuu
和type.\uuuuu delattr\uuuuuuu
在属性删除过程中,如果类型具有作为数据描述符的属性而没有\uuu delete\uuuuuu
方法,则该对象的基本原理是什么
我问这个问题是因为我注意到对象.\uuuuu getattribute\uuuuu
和类型.\uuuuu getattribute\uuuuuu
在属性查找过程中,如果类型有一个属性,而该属性是一个数据描述符,而没有\uu get\uuuu
方法,则不会引发属性错误
下面是一个简单的程序,它演示了通过对象进行属性查找的区别。一方面(AttributeError
未被提升),另一方面通过对象进行属性更新。另一方面,通过对象进行属性设置属性更新和属性删除(AttributeError
被引发):
类数据描述符1:#缺少uu获取__
定义集(自身、实例、值):通过
定义删除(自身,实例):通过
类DataDescriptor2:#缺少u集__
def uu get uu(self,instance,owner=None):通过
定义删除(自身,实例):通过
类数据描述符3:#缺少uu删除__
def uu get uu(self,instance,owner=None):通过
定义集(自身、实例、值):通过
A类:
x=数据描述符1()
y=数据描述符2()
z=数据描述符3()
a=a()
vars(a).update({'x':'foo','y':'bar','z':'baz'})
a、 x
#实际值:返回“foo”
#预期:返回“foo”
a、 y='qux'
#实际值:引发AttributeError:\uu集__
#预期值:vars(a)['y']=='qux'
德尔阿兹
#实际:引发AttributeError:\u删除__
#应为:“z”不在变量(a)中
下面是另一个简单的程序,说明了按类型查找属性和按类型删除属性之间的区别(AttributeError
被引发):
类数据描述符1:#缺少uu获取__
定义集(自身、实例、值):通过
定义删除(自身,实例):通过
类DataDescriptor2:#缺少u集__
def uu get uu(self,instance,owner=None):通过
定义删除(自身,实例):通过
类数据描述符3:#缺少uu删除__
def uu get uu(self,instance,owner=None):通过
定义集(自身、实例、值):通过
M类(类型):
x=数据描述符1()
y=数据描述符2()
z=数据描述符3()
A类(元类=M):
x='foo'
y=‘巴’
z='baz'
A.x
#实际值:返回“foo”
#预期:返回“foo”
A.y='qux'
#实际值:引发AttributeError:\uu集__
#预期值:vars(A)['y']=='qux'
德尔阿兹
#实际:引发AttributeError:\u删除__
#应为:“z”不在变量(A)中
对于属性更新和属性删除,我希望对实例字典进行变异,而不是获取AttributeError
。属性查找从实例字典返回一个值,因此我想知道为什么属性更新和属性删除也不使用实例字典(就像如果类型没有数据描述符的属性一样)。我认为这只是C级设计的结果,没有人真正考虑或关心它
在C级,\uuuuu set\uuuuuuu
和\uuuu delete\uuuuuuuuu
对应于相同的C级,tp\u descr\u set
,通过向set传递空值来指定删除。(这类似于用于\uuuuuu setattr\uuuuuuuu
和\uuuuuuu delattr\uuuuuuuu
的设计,这也对应于通过NULL
删除的设计。)
如果实现\uuuu set\uuuuu
或\uuu delete\uuuuu
,则C级插槽将设置为查找\uuuu set\uuuu
或\uuu delete\uuuuuu
并调用它:
static int
插槽描述集(PyObject*self、PyObject*target、PyObject*value)
{
PyObject*堆栈[3];
PyObject*res;
_Py_标识符(_删除__);
_Py_标识符(集合);
堆栈[0]=自身;
堆栈[1]=目标;
如果(值==NULL){
res=vectorcall\u方法(&PyId\uuuu\u delete\uuu,stack,2);
}
否则{
堆栈[2]=值;
res=向量调用方法(&PyId\uuuuuuuuuuuuuuuuuuuuu,堆栈,3);
}
如果(res==NULL)
返回-1;
Py_DECREF(res);
返回0;
}
插槽没有办法说“oops,没有找到方法,返回到正常处理”,它也没有尝试。它也没有尝试模拟正常处理-这很容易出错,因为“正常处理”依赖于类型,它不知道对所有类型模拟什么。如果插槽包装器找不到该方法,它只会引发异常
如果\uuu set\uuu
和\uu delete\uuuu
有两个插槽,这种效果就不会发生,但是在设计API时,有人会关心,我怀疑是否有人关心过。正如您所建议的,基本问题似乎是,在查找\uu set\uu
时,函数返回-1
或者\uuuu delete\uuuu
方法不存在,并且当查找的\uuu set\uuuu
或\uuuu delete\uuuu
方法存在时,但其调用会引发异常(通常是AttributeError
)。因此调用函数无法进行区分,并在前一种情况下返回实例。