Description
Bug report
Bug description:
Given the following code:
import asyncio
import time
class Foo:
def __repr__(self):
time.sleep(1)
print('i am a repr, i should not be called. ')
return '<Foo>'
async def get_foo():
return Foo()
asyncio.run(get_foo())
print('Done')
Output:
$ python t.py
i am a repr, i should not be called.
i am a repr, i should not be called.
Done
This was caused by the new SIGINT handler installed by asyncio.run here: f08a191
Upon investigation, changing the code with:
import asyncio
import time
class Foo:
def __repr__(self):
time.sleep(1)
print('i am a repr, i should not be called. ')
raise BaseException('where is this called???????')
return '<Foo>'
async def get_foo():
return Foo()
asyncio.run(get_foo())
print('Done')
It shows:
i am a repr, i should not be called.
Traceback (most recent call last):
File "t.py", line 15, in <module>
asyncio.run(get_foo())
File "lib/python3.12/asyncio/runners.py", line 194, in run
return runner.run(main)
^^^^^^^^^^^^^^^^
File "lib/python3.12/asyncio/runners.py", line 127, in run
and signal.getsignal(signal.SIGINT) is sigint_handler
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "lib/python3.12/signal.py", line 63, in getsignal
return _int_to_enum(handler, Handlers)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "lib/python3.12/signal.py", line 29, in _int_to_enum
return enum_klass(value)
^^^^^^^^^^^^^^^^^
File "lib/python3.12/enum.py", line 740, in __call__
return cls.__new__(cls, value)
^^^^^^^^^^^^^^^^^^^^^^^
File "lib/python3.12/enum.py", line 1152, in __new__
ve_exc = ValueError("%r is not a valid %s" % (value, cls.__qualname__))
^^^^^
File "lib/python3.12/reprlib.py", line 21, in wrapper
result = user_function(self)
^^^^^^^^^^^^^^^^^^^
File "lib/python3.12/asyncio/base_tasks.py", line 30, in _task_repr
info = ' '.join(_task_repr_info(task))
^^^^^^^^^^^^^^^^^^^^^
File "lib/python3.12/asyncio/base_tasks.py", line 10, in _task_repr_info
info = base_futures._future_repr_info(task)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "lib/python3.12/asyncio/base_futures.py", line 54, in _future_repr_info
result = reprlib.repr(future._result)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "lib/python3.12/reprlib.py", line 58, in repr
return self.repr1(x, self.maxlevel)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "lib/python3.12/reprlib.py", line 68, in repr1
return self.repr_instance(x, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "lib/python3.12/reprlib.py", line 170, in repr_instance
s = builtins.repr(x)
^^^^^^^^^^^^^^^^
File "t.py", line 8, in __repr__
raise BaseException('where is this called???????')
BaseException: where is this called???????
This looks like a series unfortunate events, running on 3.12.0:
- signal.getsignal tries to convert the handler function to enum in case this is part of Handlers:
Line 29 in 0fb18b0
- enum raises a ValueError with the repr of the handler function:
Line 1152 in 0fb18b0
- the handler asyncio.run installs uses a functools.partial, it's repr will include the repr of the task:
cpython/Lib/asyncio/runners.py
Line 105 in 0fb18b0
- when the repr is actually called at the end (one in
signal.getsignal
, the other insignal.signal
), the repr of the asyncio task will include the repr of the result:cpython/Lib/asyncio/runners.py
Lines 127 to 129 in 0fb18b0
While one can argument calling __repr__
shouldn't cause issues, but I think we could avoid them in the signal._int_to_enum
function completely, by only trying to convert to enum when it's an integer:
def _int_to_enum(value, enum_klass):
"""Convert a numeric value to an IntEnum member.
If it's not a known member, return the numeric value itself.
"""
if not isinstance(value, int):
return value
try:
return enum_klass(value)
except ValueError:
return value
This should be more efficient on its own anyway.
This function's doc is also inaccurate, since it also accepts non integers (usually a callable).
Am I missing something?
CPython versions tested on:
3.11, 3.12
Operating systems tested on:
Linux
Linked PRs
Metadata
Metadata
Assignees
Labels
Projects
Status