Page 1 of 2
uasyncio - determining if a function is a coroutine
Posted: Fri Jul 16, 2021 7:38 pm
by oclyke
I'm trying to adapt the
pyasncinit decorator for use in MicroPython. Part of the implementation involves checking whether a function is a coroutine. This is done in CPython with the
inspect module. Checking out the micropython-lib implementation of inspect shows that there is no "inspect.iscoroutinefunction()" method available. Doing a little more digging into the CPython implementation I see that the coroutine inspection method checks for a "code flag" marking it as a coroutine.
My questions are:
- (how) can I determine if a function is a coroutine without checking its return value in micropython?
- how is the async keyword handled in micropython (more of a question for curiosity as above Q would solve my issues)
Code: Select all
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)
Re: uasyncio - determining if a function is a coroutine
Posted: Sat Jul 17, 2021 10:53 am
by Christian Walther
As far as I know, async functions are implemented as generator functions in MicroPython. So the following could work. It catches generator functions too though, I don’t know if these can be distinguished. Someone more familiar with uasyncio may have a better idea.
Code: Select all
async def proto():
pass
def iscoroutinefunction(obj):
return isinstance(obj, type(proto))
Code: Select all
>>> def s():
... pass
...
>>> async def a():
... pass
...
>>> def g():
... yield
...
>>> iscoroutinefunction(s)
False
>>> iscoroutinefunction(a)
True
>>> iscoroutinefunction(g)
True
Re: uasyncio - determining if a function is a coroutine
Posted: Sat Jul 17, 2021 1:06 pm
by pythoncoder
Yes, that is exactly how I do it
in my primitives library. The
launch function takes as args a callable and a tuple of args. If the callable is a function, it executes it. If the callable is a coroutine it converts it to a task and runs it.
Code: Select all
async def _g():
pass
type_coro = type(_g())
# If a callback is passed, run it and return.
# If a coro is passed initiate it and return.
# coros are passed by name i.e. not using function call syntax.
def launch(func, tup_args):
res = func(*tup_args)
if isinstance(res, type_coro):
res = asyncio.create_task(res)
return res
Re: uasyncio - determining if a function is a coroutine
Posted: Sat Jul 17, 2021 3:39 pm
by kevinkk525
The difference is that Christian's approach doesn't execute the function and purely inspects it "untouched", while pythoncoder's approach executes the function and then checks if the returned type is an active generator function which can be passed to uasyncio.create_task().
Pythoncoder's approach is a more "invasive" approach, but the only one working reliable!
Warning: Christian's approach is not (!) working correctly with bound methods! So any coroutine that is part of a class definition won't work because its type in micropython is "bound_method".
Try this:
Code: Select all
class T:
async def t(self):
pass
obj=T()
print(iscoroutine(obj.t))
It will return "False".
Re: uasyncio - determining if a function is a coroutine
Posted: Sun Jul 18, 2021 7:15 am
by pythoncoder
I think there is a fundamental issue here in that MicroPython doesn't distinguish between generators, generator functions, and coroutines defined with
async def.
Code: Select all
>>> async def g():
... pass
...
>>> type(g())
<class 'generator'>
>>> type(g)
<class 'generator'>
As you point out, I avoid some pitfalls by running the passed callable and checking its return type. In the context of
launch this is benign, but I agree it's not always so. However you could break
launch by passing a function which returns a generator (a generator function).
If you know a bomb-proof solution I'd be keen to hear it.
Re: uasyncio - determining if a function is a coroutine
Posted: Sun Jul 18, 2021 8:08 am
by kevinkk525
I don't know a better way either. It's probably not easy because of the changes to keep everything "micro".
Re: uasyncio - determining if a function is a coroutine
Posted: Mon Jul 19, 2021 2:04 am
by jimmo
I think this is on the TODO list (as there are some newer features that will require this... e.g. async generators). (And I note that CircuitPython have some patches to make the distinction between coroutine and generator clearer).
Re: uasyncio - determining if a function is a coroutine
Posted: Mon Jul 19, 2021 4:45 am
by kevinkk525
CircuitPython has no asyncio support afaik, why would they need a distinction between coroutines and generators?
Re: uasyncio - determining if a function is a coroutine
Posted: Mon Jul 19, 2021 5:41 am
by jimmo
kevinkk525 wrote: ↑Mon Jul 19, 2021 4:45 am
CircuitPython has no asyncio support afaik, why would they need a distinction between coroutines and generators?
There's a community-supported non-asyncio-but-asyncio-like scheduler.
https://github.com/WarriorOfWire/tasko
Re: uasyncio - determining if a function is a coroutine
Posted: Mon Jul 19, 2021 7:30 am
by kevinkk525
lol ok.. interesting. Why didn't they just use the "official" uasyncio module? .. (sorry I know you're not the right person for this question)