2

This is an example code from the pynput documentation. It basically prints the key you are pressing.

from pynput import keyboard

def on_press(key):
    print('You pressed {}'.format(key))

    if key == keyboard.Key.esc:
        return False

with keyboard.Listener(
    on_press=on_press) as listener:
listener.join()

I need to pass an argument to the on_press function, but it's called without parentheses. I don't understand why and what this does. Ideally, this is what I'd like to make work:

from pynput import keyboard

def on_press(key, addition):
    print('You pressed {}, {}'.format(key, addition))

    if key == keyboard.Key.esc:
        return False

string = 'congrats!'

with keyboard.Listener(
    on_press=on_press(key, string)) as listener:
listener.join()
0

4 Answers 4

3
with keyboard.Listener(on_press=on_press) as listener:

This doesn't actually call on_press, it passes it to keyboard.Listener. (Python functions are first-class, i.e. they can be passed around.) Then the Listener object will call it later. This is called a callback.

You could use a higher-order function to set the addition:

def outer(addition):
    def on_press(key):
        print('You pressed {}, {}'.format(key, addition))
        ...
    return on_press

with keyboard.Listener(on_press=outer('congrats!')) as listener:
    listener.join()

A partial would also work just fine.

Sign up to request clarification or add additional context in comments.

Comments

2

You could use partial:

from pynput import keyboard
from functools import partial

def on_press(addition, key):
    print('You pressed {}, {}'.format(key, addition))

    if key == keyboard.Key.esc:
        return False

string = 'congrats!'
p_on_press = partial(on_press, string)

with keyboard.Listener(
    on_press=p_on_press) as listener:
    listener.join()

Comments

2

It's not called without parentheses; it's passed as an argument without being called. The on_press parameter should be a function, which will be called by the Listener class; the code for that class will internally have something like on_press(key) to call the function that you provide, with one argument.

Since the function you pass will be called with one argument, you need to provide a function which (1) takes key as an argument, and (2) calls on_press(key, string) with those two arguments. One solution is to use the lambda keyword:

on_press_func = lambda key: on_press(key, string)
with keyboard.Listener(on_press=on_press_func) as listener:
    listener.join()

You might also write the lambda function this way, to bind the value of string immediately instead of closing over it:

on_press_func = lambda key, string=string: on_press(key, string)

3 Comments

Named lambdas are bad practice. Use a def instead.
@wjandrea I would normally put the lambda directly in as an argument, I just wanted to keep the lines shorter for Stack Overflow. The name is only temporary and is expected to be used only once in the immediately following line.
Yeah that's fair. I mostly wanted to mention it for OP's sake. I'm sure you already know it so sorry if it seemed nit-picky.
0
from pynput import keyboard

class MyRecorder:

    def __init__(self):
        self.counter = 0

    def start_recording(self):
        with keyboard.Listener(on_press=self.on_press) as listener:
            listener.join()

    def on_press(self, key):
        print(self.counter)
        self.counter += 1

        if key == keyboard.Key.esc:
            return False

if __name__ == "__main__":
    recorder = MyRecorder()
    recorder.start_recording()

With the previous solutions the value you pass in is static / only defined once. For my case I needed something that would allow for a lot more complexity and the ability to expand.

While in the example I stuck the listener inside the class, you don't have to. this would work just as fine:

if __name__ == "__main__":
    recorder = MyRecorder()
    with keyboard.Listener(on_press=recorder.on_press) as listener:
        listener.join()

A question I do ask myself is whether this is thread safe in a multi-threading environment. Given that each instance of MyRecorder runs its own listening thread, as long as one does not modify class variables (only instance variables) during run time, I think it is. Also not sure there'd be a reason to run multiple keyboard listeners concurrently, so for this case, probably doesn't even need be a question.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.