JDS
JDS

Reputation: 16998

Python capture user input while in loop executing other code?

I am running sequential code in a loop that is doing various computations, and occasionally printing out results that I monitor in the console.

What I'd like to do is be able to hit keyboard buttons, while the program is running, and then save and process that input (e.g. as a command to change some parameter) on the next start iteration of that loop.

Here's the structure of the code I'm running:

for i in range(0, itrs):
    # ideally at the start of each loop: 
    # check to see if user pressed keybutton during prev loop itr, 
    # but don't wait / poll for it! 

    # userKeyInput = checkIfUserPressedKey()
    # param = modify(userKeyInput)

    doSequentialComputation(param)

Would the solution involve some notion of threading or interrupt? I could probably come up with a solution that involves file I/O, which wouldn't be terrible, but I was thinking maybe Python has something simpler that would work.

Upvotes: 2

Views: 14819

Answers (2)

user3657941
user3657941

Reputation:

Use a thread to run the doSequentialComputation function and pass param to it as a thread argument:

import threading
t = threading.Thread(target=doSequentialComputation, args=(param,))
t.daemon = True
t.start()

Any modification to param in the main thread will be seen by the code in the thread. Be sure to protect access to param using a threading.Lock:

param_lock = threading.Lock()
userKeyInput = checkIfUserPressedKey()
with param_lock:
    parm = modify(userKeyInput)

Putting it all together:

import threading

class Parameter(object):
    pass

def doSequentialComputation(param, param_lock):
    itrs = 1000
    param_copy = None
    with param.lock:
        param_copy = param
    for i in range(0, itrs)
        with param.lock:
            if param.has_changed:
                param_copy = param
                param.has_changed = False
        compute(param_copy)

def main():
    param = Parameter()
    param.has_changed = False
    param.lock = threading.Lock()
    args=(param, param_lock)
    compute_thread = threading.Thread(target=doSequentialComputation, args=args)
    compute_thread.daemon = True
    compute_thread.start()
    while True:
        userKeyInput = checkIfUserPressedKey()
        with param.lock:
            param = modify(userKeyInput, param)
            param.has_changed = True

The Parameter class allows us to create an object to which we can add arbitrary attributes.

Upvotes: 1

Aaron
Aaron

Reputation: 11075

If you want python to do two things at once (get user input and compute) at the same time the easiest thing to do is use separate threads (separate processes are harder and not necessary in this instance). The high level threading library is quite easy to get started with and I suggest a once-over of the docs, but here's a quick example:

from threading import Thread,Lock
import time

class globalVars():
    pass

G = globalVars() #empty object to pass around global state
G.lock = Lock() #not really necessary in this case, but useful none the less
G.value = 0
G.kill = False

def foo(n): #function doing intense computation
    for i in range(n):
        if G.kill:
            G.kill = False
            return
        time.sleep(n) #super intense computation
        with G.lock:
            G.value += i

t = Thread(target=foo, args=(10,))
t.start()

def askinput():
    #change to raw_input for python 2.7
    choice = input("1: get G.value\n2: get t.isAlive()\n3: kill thread\nelse: exit\ninput: ")
    if choice == "1":
        with G.lock:
            print(G.value)
    elif choice == "2":
        print(t.is_alive())
    elif choice == "3":
        G.kill = True
    else:
        return 0
    return 1

while askinput():
    pass

Upvotes: 4

Related Questions