class Foo:
async def foo(self, a):
return a
async def bar(b):
return b
asyncio.iscoroutinefunction(functools.partial(bar, 1)) # returns True, OK
asyncio.iscoroutinefunction(functools.partial(Foo().foo, 1)) # returns False, WHY???
I need to find a way to wrap a coroutine inside a class into partial so that the result is also a coroutine. How do I do that?
You can create partial functions in python by using the partial function from the functools library. Partial functions allow one to derive a function with x parameters to a function with fewer parameters and fixed values set for the more limited function.
coroutine was also deprecated in python 3.7 and scheduled for removal in python 3.10. It already issues a deprecation warning if used.
Coroutine objects are what functions declared with an async keyword return. type PyCoroObject. The C structure used for coroutine objects. PyTypeObject PyCoro_Type. The type object corresponding to coroutine objects.
The why is the way the inspect
module checks for this.
def iscoroutinefunction(obj):
"""Return true if the object is a coroutine function.
Coroutine functions are defined with "async def" syntax.
"""
return _has_code_flag(obj, CO_COROUTINE)
If we look at the definition for _has_code_flag
:
def _has_code_flag(f, flag):
"""Return true if ``f`` is a function (or a method or functools.partial
wrapper wrapping a function) whose code object has the given ``flag``
set in its flags."""
while ismethod(f):
f = f.__func__
f = functools._unwrap_partial(f)
if not isfunction(f):
return False
return bool(f.__code__.co_flags & flag)
We see that it first it tries to unwrap a bound method from and get its .func
attribute(which contains the function object) and then after that, unwrap the partial
. Finally if the result isn't a function return False
otherwise return the result of a flag check on the underlying function's __code__
attribute.
The problem is that while ismethod(f)
does nothing since at that point it is still a partial
object. Then after it unwraps if from the partial
, isfunction
returns False
because it is only a bound method there.
That is the why.
I do not know if this could be considered a bug or if it was done so by design.
The fact that the docstring for _has_code_flag
leaves out functools.partial
wrapped methods in its description leads me to believe it was by design.
However, you can borrow from functools._unwrap_partial
and use their method of checking for a coroutine
by checking the .func
attribute.
def _unwrap_partial(func):
while isinstance(func, partial):
func = func.func
return func
Taken from this answer:
def iscoroutinefunction_or_partial(object):
while isinstance(object, functools.partial):
object = object.func
return inspect.iscoroutinefunction(object)
Based on your comment, you need to create a coroutine:
def async_partial(async_fn, *args):
async def wrapped():
return await async_fn(*args)
return wrapped
foo1 = async_partial(Foo().foo, 1)
assert inspect.iscoroutinefunction(foo1)
assert asyncio.run(foo1()) == 1
The difference between this and the "real" functools.partial
is that the callable returned by the latter can be invoked multiple times.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With