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
Copy Markdown
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
Copy Markdown
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))
Comment thread
Joshua-Ward1 marked this conversation as resolved.
Outdated

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
Comment on lines +319 to +323
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well, this can also be moved forward by one block to avoid the time spent on obtaining the attribute when it is not necessary. I hope I have not bored you with these micro-optimizations.

Comment on lines +319 to +323
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think you should use functools._unwrap_partialmethod which handles both partial methods and partial functions (first it unwraps partial methods then unwraps partial functions)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Please read the discussion at #142505.


if isinstance(f, functools.partialmethod):
f = getattr(f, 'func')
continue
Comment thread
Joshua-Ward1 marked this conversation as resolved.
Outdated

if ismethod(f):
f = f.__func__
continue
Comment thread
Joshua-Ward1 marked this conversation as resolved.

if isinstance(f, functools.partial):
f = f.func
continue
Comment thread
Joshua-Ward1 marked this conversation as resolved.

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