Skip to content
Open
12 changes: 9 additions & 3 deletions Doc/using/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -302,16 +302,22 @@ Miscellaneous options

.. option:: -i

Enter interactive mode after execution.
Enter interactive mode after execution, or force interactive mode even when
:data:`sys.stdin` does not appear to be a terminal.

Using the :option:`-i` option will enter interactive mode in any of the following circumstances\:

* When a script is passed as first argument
* When the :option:`-c` option is used
* When the :option:`-m` option is used

Interactive mode will start even when :data:`sys.stdin` does not appear to be a terminal. The
:envvar:`PYTHONSTARTUP` file is not read.
In these "execute then interact" cases, Python runs the script or command
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems unrelated changes

first and does not read the :envvar:`PYTHONSTARTUP` file before entering
interactive mode.

When :option:`-i` is used only to force interactive mode despite redirected
standard input (for example, ``python -i < /dev/null``), the interpreter
enters interactive mode directly and reads :envvar:`PYTHONSTARTUP` as usual.

This can be useful to inspect global variables or a stack trace when a script
raises an exception. See also :envvar:`PYTHONINSPECT`.
Expand Down
31 changes: 27 additions & 4 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,10 +306,33 @@ def isgeneratorfunction(obj):
_is_coroutine_mark = object()

def _has_coroutine_mark(f):
while ismethod(f):
f = f.__func__
f = functools._unwrap_partial(f)
return getattr(f, "_is_coroutine_marker", None) is _is_coroutine_mark
visited = set()
while True:
Copy link
Member

@picnixz picnixz Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please:

  • Wrap all lines under 80 characters.
  • Remove "obvious" comments. "Methods: unwrap first" is clear from the way you're doing it.
  • Avoid blank lines. The standard library usually tries to avoid expanding the code vertically.

if id(f) in visited:
return False
visited.add(id(f))

if getattr(f, "_is_coroutine_marker", None) is _is_coroutine_mark:
return True

pm = getattr(f, "__partialmethod__", None)
if isinstance(pm, functools.partialmethod):
f = pm
continue

if isinstance(f, functools.partialmethod):
f = getattr(f, 'func')
continue

if ismethod(f):
f = f.__func__
continue

if isinstance(f, functools.partial):
f = f.func
continue

return False

def markcoroutinefunction(func):
"""
Expand Down
21 changes: 21 additions & 0 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,27 @@ def do_something_static():

coro.close(); gen_coro.close(); # silence warnings

def test_marked_partials_are_coroutinefunctions(self):
def regular_function():
pass

marked_partial = inspect.markcoroutinefunction(
functools.partial(regular_function))
self.assertTrue(inspect.iscoroutinefunction(marked_partial))
self.assertFalse(
inspect.iscoroutinefunction(functools.partial(regular_function)))

class PMClass:
def method(self, /):
pass

marked = inspect.markcoroutinefunction(
functools.partialmethod(method))
unmarked = functools.partialmethod(method)

self.assertTrue(inspect.iscoroutinefunction(PMClass.marked))
self.assertFalse(inspect.iscoroutinefunction(PMClass.unmarked))

def test_isawaitable(self):
def gen(): yield
self.assertFalse(inspect.isawaitable(gen()))
Expand Down
1 change: 1 addition & 0 deletions Misc/NEWS.d/next/Library/2025-12-10-142418.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix inspect.iscoroutinefunction() not detecting marked functools.partial or functools.partialmethod objects.
Loading