在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
)之间的区别:
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
。属性查找返回实例字典中的值,因此我想知道为什么属性更新和属性删除也不使用实例字典(就像如果类型没有数据描述符的属性一样)。
发布于 2021-03-22 23:47:20
我认为这只是C级设计的一个结果,没有人真正想到或关心它。
在C级别,__set__
和__delete__
对应于相同的C级插槽、tp_descr_set
,而删除是通过传递一个空值来指定的。(这与__setattr__
和__delattr__
使用的设计类似,它们也对应于一个插槽,后者也通过NULL
进行删除。)
如果您实现了__set__
或__delete__
,则C级插槽被设置为查找__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;
}
插槽没有办法说"oops,没有找到方法,回到正常的处理“,也没有尝试。它也不试图模仿正常的处理--这很容易出错,因为“正常处理”是依赖于类型的,而且它也不知道对所有类型都要模仿什么。如果槽包装器找不到该方法,它只会引发异常。
如果__set__
和__delete__
有两个插槽,这种效果就不会发生,但是当他们设计API时,会有人关心这个问题,我怀疑有人会这么做。
https://stackoverflow.com/questions/66754075
复制相似问题