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()
Nicolas Gervais
  • 33,817
  • 13
  • 115
  • 143

4 Answers4

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.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
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()
alex2007v
  • 1,230
  • 8
  • 12
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)
kaya3
  • 47,440
  • 4
  • 68
  • 97
  • 1
    Named lambdas are bad practice. Use a `def` instead. – wjandrea Jan 20 '20 at 01:53
  • 1
    @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. – kaya3 Jan 20 '20 at 02:25
  • 1
    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. – wjandrea Jan 20 '20 at 02:27
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.

David Mendes
  • 197
  • 2
  • 4