-
-
Notifications
You must be signed in to change notification settings - Fork 33.4k
Description
Bug report
Bug description:
According to the Python docs, annotate functions need to be actual functions. However, the C code doesn't enforce this, it enforces (using PyCallable_Check()) that the annotate 'function' is any callable:
Lines 865 to 888 in 9cb8c52
| static int | |
| function___annotate___set_impl(PyFunctionObject *self, PyObject *value) | |
| /*[clinic end generated code: output=05b7dfc07ada66cd input=eb6225e358d97448]*/ | |
| { | |
| if (value == NULL) { | |
| PyErr_SetString(PyExc_TypeError, | |
| "__annotate__ cannot be deleted"); | |
| return -1; | |
| } | |
| if (Py_IsNone(value)) { | |
| Py_XSETREF(self->func_annotate, value); | |
| return 0; | |
| } | |
| else if (PyCallable_Check(value)) { | |
| Py_XSETREF(self->func_annotate, Py_XNewRef(value)); | |
| Py_CLEAR(self->func_annotations); | |
| return 0; | |
| } | |
| else { | |
| PyErr_SetString(PyExc_TypeError, | |
| "__annotate__ must be callable or None"); | |
| return -1; | |
| } | |
| } |
This initially seems fine, but leads to some weird bugs.
class C:
def __call__(self, format, /):
if format > 2:
raise NotImplementedError
return {'x': int}
def f(x): ...
f.__annotate__ = C()
from annotationlib import get_annotations, Format
get_annotations(f, format=Format.STRING) # AttributeError: 'C' object has no attribute '__closure__' ...And similar for get_annotations(f, format=Format.FORWARDREF), trying to find the annotation function's __builtins__, which only exists on functions (not arbitrary callables).
I'm happy to make a PR to replace the PyCallable_Check() with PyFunction_Check() in the relevant C code for setting __annotate__, but I guess this might be a breaking change? I'm not sure, so I thought I'd check first before making it. The other option would be checking for the presence of __closure__/__builtins__/etc. before accessing them in annotationlib.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux