Reputation: 129
I'm looking for solution of this problem:
I have two functions:
def foo1():
while True:
print("x")
def foo2():
while True:
print("y")
I would like to run them alternately, like:
Functions can't work simultaneously, at the moment only one of them can work.
Upvotes: 0
Views: 263
Reputation: 123501
From what you say you don't want/need for them really both be running at the same time, but rather to be able to switch execution back and forth among them. What you've describe is what's called co-operative multitasking.
This can be accomplished by simply making each function a coroutine. In Python that can be accomplished by making them generator functions which is done by simply by adding a yield
statement to them at the right spot. That way they will each pause execution until next()
is called on them again.
The only slightly tricky part is remembering that each generator function will need to be "primed" once it's called the first time (which actually doesn't execute the code in the function). The first call to next()
of the value returned by the first call to the function itself will advances its execution to the location of the first yield
in it.
Here's what I mean:
def foo1():
while True:
yield
print("x")
def foo2():
while True:
yield
print("y")
# Sample usage.
def process():
f1 = foo1()
next(f1) # Prime generator.
f2 = foo2()
next(f2) # Prime generator.
while True:
next(f1)
next(f2)
process()
Output:
x
y
x
y
x
...
If you're going to do a lot of this, you can create a decorator that will make generator functions used as coroutines prime themselves automatically (taken from a tutorial David Beazley gave at Pycon 2009 titled Curious Course on Coroutines and Concurrency).
def coroutine(func):
""" Decorator for wrapping coroutine functions so they automatically
prime themselves.
"""
def start(*args,**kwargs):
cr = func(*args,**kwargs)
next(cr)
return cr
return start
@coroutine
def foo1():
while True:
yield
print("x")
@coroutine
def foo2():
while True:
yield
print("y")
# Sample usage.
def process():
f1, f2 = foo1(), foo2() # Each will be "primed" automatically.
for _ in range(10):
next(f1)
next(f2)
Upvotes: 1
Reputation: 53089
Edit: Leave it to @StefanPochmann to shoot down my beautiful answer: It doesn't matter for the question as it is posed, but it is certainly worth mentioning that zip works pair-by-pair. So while you can formally pause alter
defined below after an odd number of iterations, at this point 'y' will have been printed already.
If you care about this the solution also due to Stefan is
>>> alter = map(next, it.cycle((foo1(), foo2())))
which is actually pretty clever.
Edit ends
You could convert the functions into generators, then zip them.
>>> import itertools as it
>>>
>>> def foo1():
... while True:
... print("x")
... yield None
...
>>> def foo2():
... while True:
... print("y")
... yield None
...
>>> alter = it.chain.from_iterable(zip(foo1(), foo2()))
>>> while True:
... next(alter)
x
y
x
y
...
or:
>>> for a in alter:
... pass
x
y
x
y
...
Upvotes: 4
Reputation: 181
So this is my personal way of doing this but there's probably a better one.
def foo(i):
while(True):
i += 1
if(i%2 == 0):
print('x')
else:
print('y')
So the way this works is i
is working as an iterator. Then it checks it increments the iterator every time by 1 with i+= 1
. Next we check if i
is odd or even and because it will alternate. So with every even number you will get x
and every odd you you will get y
. Hope this helps!
Upvotes: 0
Reputation: 104772
There's no way to do what you want with the functions you've shown. However, there are some approaches that might come close, to differing degrees, depending on what details you're willing to change in your current code.
One option to get sort-of parallel execution is to use threads. Python doesn't actually allow more than one thread to be running Python code at once, so this isn't really useful in most situations, but it might be good enough for your specific use case. Unfortunately it does very badly with your current code, since one thread will tend to block the other for a long time, so you'll get lots of x
s, followed by lots of y
s, as they functions only swap the GIL irregularly.
Another option is to use multiprocessing
, which does allow parallel execution. It would do a better job of interleaving your two functions, but the exact timing of the swaps between the two processes will still not be exactly consistent, so you won't always get "xyxyxyxy", you might get something more like "xyxxyyxy" (the exact details may depend on your OS, CPU and how busy your machine is at the time you run the code).
Now we get into solutions that require modifications to the functions. An obvious solution is to simply rewrite them into a single function that does both steps:
def foo_combined():
while True:
print('x')
print('y')
Another option is to make the functions into generators, which you can combine yourself using zip
:
def foo1_gen():
while True:
yield 'x'
def foo2_gen():
while True
yield 'y'
You could consume the generators like this:
for x, y in zip(foo1_gen(), foo2_gen()):
print(x)
print(y)
Or, if you want a single iterable:
for val in itertools.chain.from_iterable(zip(foo1_gen(), foo2_gen())):
print(val)
Upvotes: 1
Reputation: 402902
I personally favour the itertools
approach by Paul Panzer. However, if it is an option, I'd strongly suggest moving the "infinite loop" out of these functions and instead putting it in another "driver" function that calls these two in an alternating fashion.
def f1():
print('x')
def f2():
print('y')
def driver():
while True:
f1()
f2()
I get that this won't be possible to implement in every situation, but it does lead to better design in the end if you can.
Upvotes: 3