looping over multiple async_generator objects together - python

I started working out some problems to master python asyncio module. What I wanted to create is a 'clock' which basically just prints out the time after program started in Hour:Minute:Second format. I thought of making three async_generators and looping over those three async_generators using a for loop in a seperate async method. Using builtin zip() method for this gives me the following error.
TypeError: zip argument #1 must support iteration
code :
import asyncio
second = 1
async def seconds():
while True:
for i in range(1,61):
await asyncio.sleep(second)
yield i
async def minutes():
while True:
for i in range(1,61):
await asyncio.sleep(60*second)
yield i
async def hours():
while True:
for i in range(1,61):
await asyncio.sleep(60*60*second)
yield i
async def clock():
for s,m,h in zip(seconds(),minutes(),hours()):
print('{0}:H{1}:M{2}:S'.format(h,m,s))
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(clock())
My question is, doesn't async_genertator objects support iteration? When I checked, i could see that async_generator object hours() has ____aiter____ method. Isn't ____aiter____ iterable? What is wrong with the code I wrote.

Related

async function in Python, basic example

Can you help me see what I have understood wrong here please. I have two functions and I would like the second one to run regardless of the status of the first one (whether it is finished or not). Hence I was thinking to make the first function asynchronous. This is what I have done
import os
import asyncio
from datetime import datetime
async def do_some_iterations():
for i in range(10):
print(datetime.now().time())
await asyncio.sleep(1)
print('... Cool!')
async def main():
task = asyncio.create_task (do_some_iterations())
await task
def do_something():
print('something...')
if __name__ == "__main__":
asyncio.run(main())
do_something()
The output is:
00:46:00.145024
00:46:01.148533
00:46:02.159751
00:46:03.169868
00:46:04.179915
00:46:05.187242
00:46:06.196356
00:46:07.207614
00:46:08.215997
00:46:09.225066
Cool!
something...
which looks like the traditional way where one function has to finish and then move to the next call.
I was hoping instead to execute do_something() before the asynchronous function started generating the print statements (or at lease at the very top of those statements..)
What am I doing wrong please? How I should edit the script?
They both need to be part of the event loop the you created. asyncio.run() itself is not async, which means it will run until the loop ends. One easy way to do this is to use gather()
import asyncio
from datetime import datetime
async def do_some_iterations():
for i in range(10):
print(datetime.now().time())
await asyncio.sleep(1)
print('... Cool!')
async def do_something():
print('something...')
async def main():
await asyncio.gather(
do_some_iterations(),
do_something()
)
if __name__ == "__main__":
asyncio.run(main())
print("done")
This will print:
16:08:38.921879
something...
16:08:39.922565
16:08:40.923709
16:08:41.924823
16:08:42.926004
16:08:43.927044
16:08:44.927877
16:08:45.928724
16:08:46.929589
16:08:47.930453
... Cool!
done
You can also simply add another task:
async def main():
task = asyncio.create_task(do_some_iterations())
task2 = asyncio.create_task(do_something())
In both cases the function needs to be awaitable.

How would I run a while loop on a separate thread in Python?

I'm new to coroutines in python but I know in lua you would do this to create one
coroutine.wrap(function()
while true do
end
end)()
But I dont know how I would do this python.
I tried using this code which included a function from the slp_coroutine library which didnt work
import slp_coroutine
def test():
while True:
print("hi")
time.sleep(3)
r = slp_coroutine.await_coroutine(test())
and I also tried this and it also didnt work.
async def test():
while True:
print("hi")
await asyncio.sleep(3)
asyncio.run(test())

Asyncio.sleep causes script to End Immediately

In my simple asyncio Python program below, bar_loop is supposed to run continuously with a 1 second delay between loops.
Things run as expected when we have simply
async def bar_loop(self):
while True:
print('bar')
However, when we add a asyncio.sleep(1), the loop will end instead of looping.
async def bar_loop(self):
while True:
print('bar')
await asyncio.sleep(1)
Why does asyncio.sleep() cause bar_loop to exit immediately? How can we let it loop with a 1 sec delay?
Full Example:
import asyncio
from typing import Optional
class Foo:
def __init__(self):
self.bar_loop_task: Optional[asyncio.Task] = None
async def start(self):
self.bar_loop_task = asyncio.create_task(self.bar_loop())
async def stop(self):
if self.bar_loop_task is not None:
self.bar_loop_task.cancel()
async def bar_loop(self):
while True:
print('bar')
await asyncio.sleep(1)
if __name__ == '__main__':
try:
foo = Foo()
asyncio.run(foo.start())
except KeyboardInterrupt:
asyncio.run(foo.stop())
Using Python 3.9.5 on Ubuntu 20.04.
This behavior has nothing to do with calling asyncio.sleep, but with the expected behavior of creating a task and doing nothing else.
Tasks will run in parallel in the the asyncio loop, while other code that uses just coroutine and await expressions can be thought as if run in a linear pattern - however, as the are "out of the way" of the - let's call it "visible path of execution", they also won't prevent that flow.
In this case, your program simply reaches the end of the start method, with nothing left being "awaited", the asyncio loop simply finishes its execution.
If you have no explicit code to run in parallel to bar_loop, just await for the task. Change your start method to read:
async def start(self):
self.bar_loop_task = asyncio.create_task(self.bar_loop())
try:
await self.bar_loop_task
except XXX:
# handle excptions that might have taken place inside the task

Making asynchronous functions in Python

I have 4 functions that I wrote in python. I want to make first 3 functions asynchronous.
So it should look like this:
x = 3.13
def one(x):
return x**x
def two(x):
return x*x*12-314
def three(x):
return x+x+352+x**x
def final(x):
one = one(x)
two = two(x)
three = three(x)
return one, two, three
This is what I did:
async def one(x):
return x**x
async def two(x):
return x*x*12-314
async def three(x):
return x+x+352+x**x
def final(x):
loop = asyncio.get_event_loop()
one = loop.create_task(one(x))
two = loop.create_task(two(x))
three = loop.create_task(three(x))
#loop.run_until_complete('what should be here')
#loop.close()
return one, two, three
But I get this error (if lines above are not commented):
RecursionError: maximum recursion depth exceeded
I do not know what is wrong (Im new to this), I have also tried to add this:
await asyncio.wait([one,two,three])
but to be honest I do not know where and why should I add it.
Without that, my code works but it does not give me result, it prints this:
(<Task pending name='Task-1' coro=<one() running at /Users/.../Desktop/....py:63>>, <Task pending name='Task-2' coro=<two() running at /Users/.../Desktop/...py:10>>, <Task pending name='Task-3' coro=<three() running at /Users/.../Desktop/...py:91>>)
Any help?
The major purpose of async syntax and its libraries is to hide the details of event loops. Prefer to use high-level APIs directly:
def final(x):
loop = asyncio.get_event_loop()
return loop.run_until_complete( # run_until_complete fetches the task results
asyncio.gather(one(x), two(x), three(x)) # gather runs multiple tasks
)
print(final(x)) # [35.5675357348548, -196.43720000000002, 393.8275357348548]
If you want to explicitly control concurrency, i.e. when functions are launched as tasks, it is simpler to do this inside another async function. In your example, making final an async function simplifies things by giving direct access to await and the ambient loop:
async def final(x): # async def – we can use await inside
# create task in whatever loop this function runs in
task_one = asyncio.create_task(one(x))
task_two = asyncio.create_task(two(x))
task_three = asyncio.create_task(three(x))
# wait for completion and fetch result of each task
return await task_one, await task_two, await task_three
print(asyncio.run(final(x))) # (35.5675357348548, -196.43720000000002, 393.8275357348548)

yield from asyncio.sleep works, but yield from async def fails

The following code works fine:
import asyncio
loop = asyncio.get_event_loop()
async def a ():
print('hello')
def b ():
yield from asyncio.sleep(1)
loop.run_until_complete(b())
loop.close()
print('done')
But, the following fails:
import asyncio
loop = asyncio.get_event_loop()
async def a ():
print('hello')
def b ():
yield from a() # <=========== only 1 tiny change
loop.run_until_complete(b())
loop.close()
print('done')
Decorating b with #asyncio.coroutine makes it work.
But, the question is why does the first piece of code work fine without the #asyncio.coroutine decorator? The docs clearly say that asyncio.sleep is a coroutine, and so is a, so why does the code fail in one case and work fine for the other case?
Your code produces the following error:
...
yield from a() # <=========== only 1 tiny change
TypeError: cannot 'yield from' a coroutine object in a non-coroutine generator
As clearly stated in the error message, when using asyncio, you should either use #coroutine or async def to mark your coroutines. In async defs, await should be used instead of yield from:
import asyncio
async def a():
print('hello')
async def b():
await a()
loop = asyncio.get_event_loop()
loop.run_until_complete(b())
loop.close()
print('done')
or, for python 3.4:
import asyncio
#asyncio.coroutine
def a():
print('hello')
#asyncio.coroutine
def b():
yield from a()
loop = asyncio.get_event_loop()
loop.run_until_complete(b())
loop.close()
print('done')
Your first example is considered "buggy", but it is executing "properly" because run_until_complete calls iscoroutine which currently returns True for generators (any def with a yield/yield from), but this is an implementation detail that might change in a future version of python. Using #couroutine on def a() (instead of async def a()), or even just adding yield from asyncio.sleep(1) to a regular def a() would make your second example run as well. Currently python might be "merciful" when using generators which are not marked as coroutines in asyncio, but not when using async defs.

Categories