Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partial of a class coroutine isn't a coroutine. Why?

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?

like image 577
krokoziabla Avatar asked Apr 09 '21 11:04

krokoziabla


People also ask

What is Functools partial?

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.

Is coroutine deprecated?

coroutine was also deprecated in python 3.7 and scheduled for removal in python 3.10. It already issues a deprecation warning if used.

What is a coroutine object?

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.


Video Answer


2 Answers

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)
like image 140
Axe319 Avatar answered Oct 24 '22 05:10

Axe319


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.

like image 1
user4815162342 Avatar answered Oct 24 '22 03:10

user4815162342