-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggest async with
when with
finds no __enter__
/__exit__
#128398
Comments
async with
when with
finds no __enter__
/__exit__
async with
when with
finds no __enter__
/__exit__
In trio extra https://github.com/python-trio/trio/blob/main/src/trio/_core/_run.py#L1057 It's a bit of a hack, and they have to be hidden from the type checking It would be nice to be able to avoid this |
Might be good to also do this with iteration, and the other direction (async used on a sync object)? |
Someone should look in the source code and see how easy this would be to implement first. Possibly a good first issue, but possibly not (I'm pretty sure that error comes from C code). |
Yes, that's why I removed the stdlib and used the other label (my bad for this one). It's here: Lines 375 to 379 in a327810
|
I've provisionally marked this as "easy", as we only need to change an error, but there's a very good chance that it's not. It might be a good issue for anyone wanting to learn about the interpreter core, though! |
I'm not sure it's really easy as the error is used here: Lines 3163 to 3166 in 58e9f95
(what's hard here is understanding how |
Hey, I would like to take up this issue. I am going to put a breakpoint in the file mentioned by @picnixz to follow trace of the error being as I can't find a reference to |
Go for it! That sounds like a fine way to approach it to me. |
You won't have anything in the docs I think. Those are essentially internal stuff. You'll likely need to modify A solution is to add a new error field that you could use if the [SPECIAL___ENTER__] = {
.name = &_Py_ID(__enter__),
.error = "'%.200s' object does not support the "
"context manager protocol (missed __enter__ method)",
.suggestion = "%T object does [...]"
}, |
Hi, so I started of by trying to put a break point on line 374 but for some reason(I would love to know why) gdb does not want to put a breakpoint there and moves it to line 412. I have also tried searching for |
Likely because it's at the end the static array declaration. Instead of doing it using GDB, here's what you should do:
_PyErr_Format(tstate, PyExc_TypeError,
_Py_SpecialMethods[oparg].error,
Py_TYPE(owner_o)->tp_name); Here, Now, let's have a look at the entire "block": inst(LOAD_SPECIAL, (owner -- attr, self_or_null)) {
assert(oparg <= SPECIAL_MAX);
PyObject *owner_o = PyStackRef_AsPyObjectSteal(owner);
PyObject *name = _Py_SpecialMethods[oparg].name;
PyObject *self_or_null_o;
PyObject *attr_o = _PyObject_LookupSpecialMethod(owner_o, name, &self_or_null_o);
if (attr_o == NULL) {
if (!_PyErr_Occurred(tstate)) {
_PyErr_Format(tstate, PyExc_TypeError,
_Py_SpecialMethods[oparg].error,
Py_TYPE(owner_o)->tp_name);
}
ERROR_IF(true, error);
}
attr = PyStackRef_FromPyObjectSteal(attr_o);
self_or_null = self_or_null_o == NULL ?
PyStackRef_NULL : PyStackRef_FromPyObjectSteal(self_or_null_o);
} The LHS of static PyObject *
lookup_special_method(PyObject *owner, uint8_t oparg)
{
PyObject *name = _Py_SpecialMethods[oparg].name;
PyObject *dummy;
PyObject *method = _PyObject_LookupSpecialMethod(owner, name, &dummy);
if (method == NULL) {
PyErr_Format(PyExc_TypeError, _Py_specialMethods[oparg].error, ...);
return NULL;
}
return method;
} Guido is suggesting that we change this to something like that: static PyObject *
lookup_special_method(PyObject *owner, uint8_t oparg)
{
PyObject *name = _Py_SpecialMethods[oparg].name;
PyObject *dummy;
PyObject *method = _PyObject_LookupSpecialMethod(owner, name, &dummy);
if (method == NULL) {
if (PyUnicode_EqualToUTF8(name, "__enter__") && supports_async_with(owner)) {
// use an alternative suggestion if the object supports __aenter__ instead
PyErr_Format(PyExc_TypeError, _Py_specialMethods[oparg].alterror, ...);
return NULL;
}
PyErr_Format(PyExc_TypeError, _Py_specialMethods[oparg].error, ...);
return NULL;
}
return method;
} The above is just a pseudo-code and there are still stuff to check (like |
(I'm removing the easy label because it's not that easy IMO; you need to navigate in a lot of internal parts, but we can definitely help you @abhijeetsharma200). I would advise first focusing on the following questions:
|
Thanks, appreciate the detailed comments! |
Hey, I have thought about the questions. Here are my thoughts on the questions you posed:
So from my understanding, we print the Initially, I only checked for Moving on to my implementation of testing this, I added a
|
So, we can add |
The algorithmic part looks fine (namely I also had that kind of thought), though we will probably use something else than
In order to understand Now,
Yes, the idea is simply to add a new field in the if (attr_o == NULL) {
if (!_PyErr_Occurred(tstate)) {
_PyErr_Format(tstate, PyExc_TypeError,
_Py_SpecialMethods[oparg].error,
Py_TYPE(owner_o)->tp_name);
}
ERROR_IF(true, error);
} Having Note that this function does not necessarily set an exception (hence if (!_PyErr_Occurred(tstate)) {
const char *fmt = _Py_SpecialMethods[oparg].error;
if (use_alterror_instead(...)) {
fmt = _Py_SpecialMethods[oparg].alterror;
}
_PyErr_Format(tstate, PyExc_TypeError, fmt,
Py_TYPE(owner_o)->tp_name);
} That's essentially the only thing we would do in static int
use_alterror_instead(PyObject *self, int oparg)
{
switch (oparg) {
case SPECIAL___ENTER__: {
assert(!hasattr(self, "__exit__"));
return hasattr(self, "__aenter__") && hasattr(self, "__aexit__");
}
...
}
} and Note that what I'm suggesting is essentially without me coding anything so maybe there are things that are not compatible. Since @markshannon added |
Thanks for helpful insights. I am going through Tools/cases_generator/interpreter_definition.md to better understand
I had misunderstood how errors were being propagated because I found occurrences of
Thanks for providing a clear description of the flow through this function. It has really helped in building a correct mental model for me and I really appreciate it! |
Bug report
Bug description:
(This is not an asyncio bug! I am just using
asyncio.TaskGroup()
as an example.)This currently gives an error ending in
That's not very clear about what's wrong. Maybe when issuing this
TypeError
we could check if the object supports__aexit__
and__aenter__
, and if so, suggest something like "maybe tryasync with
?".CPython versions tested on:
3.12, 3.13, 3.14
Operating systems tested on:
No response
The text was updated successfully, but these errors were encountered: