Larry Hastings <[email protected]> added the comment:
This isn't a CPython bug. It's a change in CPython behavior that wrapt needs
to accommodate. In particular, it isn't 'causing a regression for C subclasses
of heap types when the parent class has an "__annotations__" descriptor', nor
do child classes have any difficulty inheriting the descriptor of their parent
classes. That's unsurprising; after all, if I had broken child classes
inheriting the descriptors of their parent classes, a lot more would have
broken than just wrapt.
The problem is in WraptObjectProxy_setattro(). (Which--just to drive my point
home--*is* getting called when you set __annotations__ on one of wrapt's
various proxy objects.) WraptObjectProxy_setattro() proxies setattr calls for
wrapped objects to the original object--if "o" is a wrapt proxy object wrapping
"fn", and you run "o.__annotations__ = x", it should actually execute
"fn.__annotations__ = x" under the covers.
Except WraptObjectProxy_setattro() executes *this* code first, starting at line
1531 in my copy of _wrapped.c:
if (PyObject_HasAttr((PyObject *)Py_TYPE(self), name))
return PyObject_GenericSetAttr((PyObject *)self, name, value);
If the *type* has the attribute, then it doesn't proxy the setattr to the
wrapped object. Instead it does a "generic setattr" on the object itself.
PyObject_HasAttr works by attempting a getattr on the type. If that getattr
call succeeds, PyObject_HasAttr returns true. The type here is FunctionWrapper
(WraptFunctionWrapper_Type). Since we're now looking it up on this type
object, we use the type of the type object, which is "type", to access the
attribute. And getting the "__annotations__" attribute from an object of type
"type" means calling type_get_annotations(), a new descriptor which ensures
that the annotations dict always exists, which means the HasAttr call succeeds
and returns true. In short, this change to the semantics of the
"__annotations__" attribute means wrapt no longer proxies the setattr to the
underlying wrapped object when setting the "__annotations__" attribute on *any*
of its objects.
In my opinion, wrapt needs to accommodate this new behavior. In my testing I
changed the above code to this:
if (!annotations_str) {
annotations_str = PyUnicode_InternFromString("__annotations__");
}
if (PyObject_RichCompareBool(name, annotations_str, Py_NE)
&& PyObject_HasAttr((PyObject *)Py_TYPE(self), name))
return PyObject_GenericSetAttr((PyObject *)self, name, value);
I also declared
static PyObject *annotations_str = NULL;
at the top of the function. With that change in place, the tests now passed.
My hunch is, this approach is more or less what wrapt should do. It *might* be
undersophisticated; it's possible that there are classes out there playing
their own weird descriptor tricks with the "__annotations__" attribute.
Perhaps the fix needs to be on a case-by-case basis, based on the type of the
wrapped object. Anyway this is obviously up to Graham, which is for the best
anyway--he has far more experience than I do with this sort of object proxying
wizardry.
----------
resolution: -> third party
stage: test needed -> resolved
status: open -> closed
_______________________________________
Python tracker <[email protected]>
<https://bugs.python.org/issue45319>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com