Outils pour utilisateurs

Outils du site


issue165:python

Welcome back to Python in the REAL World. I’ve been really busy with a project for a friend and client. One of the things that his project requires is using GPIO pins on a single-board computer (not a Raspberry Pi) to monitor the position of a stepper motor. While doing research for this phase of the project, I needed to try to write and debug the code on my Linux desktop before testing the code on the single-board computer. Not very easy. One of the things that I need to do is handle keyboard input in an Event Oriented manner. One of the things that keeps me going to Tkinter and GUI programming is the ability to deal with events easily, without a tonne of extra programming and without a lot of overhead (outside of the GUI itself). Unfortunately, in the environment that my client/friend is using, there can be no GUI. This means everything must be run via the command-line interface since there is no display.

Ravi de vous revoir dans Python dans le monde réel.

J'ai été très occupé par un projet pour un ami et client. L'une des choses dont son projet a besoin est l'utilisation de broches GPIO sur un ordinateur monocarte (pas un Raspberry Pi) pour surveiller la position d'un moteur pas-à-pas. En faisant des recherches pour cette phase du projet, je devais essayer d'écrire et de déboguer le code sur mon ordinateur de bureau Linux avant de tester le code sur l'ordinateur monocarte. Ce n'est pas très facile. L'une des choses que je dois faire est de gérer la saisie au clavier de manière événementielle.

L'une des choses qui me pousse à aller vers Tkinter et la programmation en interface graphique, c'est la possibilité de gérer les événements facilement, sans une tonne de programmation supplémentaire et sans beaucoup de choses en plus (en dehors de l'interface graphique elle-même). Malheureusement, dans l'environnement que mon client/ami utilise, il ne peut pas y avoir d'interface graphique. Cela signifie que tout doit être exécuté via l'interface en ligne de commande puisqu'il n'y a pas d'affichage.

One thing that I needed was to capture keystrokes and deal with them without the need to press [Enter] all the time. Python doesn’t support this natively. I also needed to deal with multiple threads, and I haven’t “directly” messed with threads in a long while. Because of this, I decided to create a small demo program to get up to speed. After looking around on the Internet, I settled on a third party Python library to deal with the keyboard events. It’s called pynput. You can find the github repository at https://github.com/moses-palmer/pynput. It handles mouse as well as keyboard input and control. For the purposes of this article, we will deal with only the keyboard monitoring capabilities. Of course, since it’s a third-party package, you need to install it. You can easily do this via pip… pip install pynput or: pip3 install pynput

Une chose dont j'avais besoin était de capturer les frappes et de les gérer sans devoir appuyer sur [Entrée] tout le temps. Python ne supporte pas cela nativement. J'avais également besoin de gérer plusieurs fils de traitement, et je n'ai pas « directement » touché à des « threads » depuis longtemps. C'est pourquoi j'ai décidé de créer un petit programme de démonstration pour me mettre à niveau. Après avoir cherché sur Internet, j'ai choisi une bibliothèque Python tierce pour gérer les événements du clavier. Elle s'appelle pynput. Vous pouvez trouver le dépôt github à l'adresse https://github.com/moses-palmer/pynput. Elle gère la saisie et le contrôle de la souris ainsi que du clavier. Dans le cadre de cet article, nous ne traiterons que les capacités de contrôle du clavier.

Bien sûr, comme il s'agit d'un paquet tiers, vous devez l'installer. Vous pouvez facilement le faire via pip…

pip install pynput

ou :

pip3 install pynput

The documentation on pynput can be found at https://pynput.readthedocs.io/en/lates/, and is well worth your time to look over it. I will use some of his example code in the demo program that I will present here. I also used some code from a Real Python tutorial on threads, however I have modified it pretty deeply. Since pynput uses threading to do its magic, we should have at least a basic understanding of what threads are and how they work.

La documentation sur pynput peut être consultée à l'adresse https://pynput.readthedocs.io/en/lates/ et il vaut la peine de la parcourir. J'utiliserai certains de ses exemples de code dans le programme de démonstration que je vais présenter ici. J'ai également utilisé du code provenant d'un tutoriel de Real Python sur les threads, mais je l'ai modifié assez profondément.

Puisque pynput utilise les threads pour opérer, nous devrons avoir au moins une compréhension de base de ce que sont les threads et de leur fonctionnement.

Threads There are two websites that I have found that really do a good job of threads: what they are, and how to use them. You can find them at https://www.techbeamers.com/python-multithreading-concepts/ and https://realpython.com/intro-to-python-threading/ . I’ll try to break down the information that is provided on these two sites into simply a gross overview of threads. When you run a “normal” CLI (Command-Line Interface) program written in Python, it probably runs in only what’s called the main thread. All this means is that the execution of the program starts at the beginning and runs each statement, one at a time, until the end of the program, without trying to do anything else at the same time. A fairly good example that demonstrates the need for threads (in some things) is the time.sleep() function. When you call this function, all processing in the program stops until the specified amount of time is done. So if you call time.sleep(5), your program stops for 5 seconds. This is one of the biggest hurdles that a programmer (who is just starting to work with GUI programs) has to overcome. In GUI programming, you deal with events. You don’t want to call a sleep function, because the GUI becomes unresponsive when this blocking function is called.

Fils de traitement - threads

J'ai trouvé deux sites Web qui sont vraiment bien sur les threads : ce qu'ils sont et comment les utiliser. Vous pouvez les trouver sur https://www.techbeamers.com/python-multithreading-concepts/ et https://realpython.com/intro-to-python-threading/.

Je vais essayer de décomposer les informations fournies sur ces deux sites en un simple aperçu général des threads.

Lorsque vous exécutez un programme CLI (Command-Line Interface) « normal » écrit en Python, il ne fonctionne probablement que dans ce que l'on appelle le fil principal. Tout ce que cela signifie est que l'exécution du programme commence au début et exécute chaque instruction, une à la fois, jusqu'à la fin du programme, sans essayer de faire autre chose en même temps. Un assez bon exemple qui démontre la nécessité des threads (dans certains cas) est la fonction time.sleep(). Lorsque vous appelez cette fonction, tout le traitement du programme s'arrête jusqu'à ce que la durée spécifiée soit écoulée. Ainsi, si vous appelez time.sleep(5), votre programme s'arrête pendant 5 secondes. C'est l'un des plus grands obstacles qu'un programmeur (qui commence à peine à travailler avec des programmes d'interface graphique - GUI) doit surmonter. Dans la programmation GUI, vous traitez des événements. Vous ne voulez pas appeler une fonction de mise en veille, car l'interface graphique devient insensible lorsque cette fonction de blocage est appelée.

In CLI programming, you can get around this issue by using a thread. Python and the computer cooperate when running on modern multi-core processors in such a way that threads can be handled in different cores almost concurrently. When pynput starts (the way we will use it), it creates a thread that continuously “listens” for any mouse or keyboard messages. Once it “hears” a keyboard message or mouse message, it responds by calling a function that we define. This function is called a callback function. In that function, we can handle whatever key was pressed or released, or, in the case of the mouse, a movement or button click. Once it has called the callback function, it goes back to listening until the next keyboard or mouse message. We really don’t have to understand the deep internals of pynput, only the callback functions and how to start the listening process.

Dans la programmation par CLI, vous pouvez contourner ce problème en utilisant un thread. Python et l'ordinateur coopèrent lorsqu'ils fonctionnent sur des processeurs modernes à plusieurs cœurs de telle sorte que les threads peuvent être traités dans différents cœurs presque simultanément.

Lorsque pynput démarre (comme nous l'utiliserons), il crée un thread qui « écoute » en permanence les messages de la souris ou du clavier. Une fois qu'il « entend » un message du clavier ou de la souris, il répond en appelant une fonction que nous définissons. Cette fonction s'appelle une fonction de rappel (callback). Dans cette fonction, nous pouvons gérer n'importe quelle touche qui a été enfoncée ou relâchée ou, dans le cas de la souris, un mouvement ou un clic de bouton. Une fois qu'elle a appelé la fonction de rappel, elle retourne à l'écoute jusqu'au prochain message du clavier ou de la souris. Nous n'avons pas vraiment besoin de comprendre les mécanismes internes de pynput, mais seulement les fonctions de rappel et la façon de lancer le processus d'écoute.

To create the callback, we’ll first deal with the keypress callback. We’ll use his tutorial code (top right) for this. When a key is pressed on the keyboard, this function will be called. The key object is provided as a parameter. If we press the “a” key on the keyboard, the callback will print in the terminal… alphanumeric key a pressed If, however, we press a “special key” like [Shift] or [Ctrl], the callback will print… special key Key.shift pressed Or special key Key.ctrl pressed

Pour créer le rappel, nous allons d'abord nous occuper du rappel par appui sur une touche. Nous utiliserons pour cela le code du tutoriel (en haut à droite).

Lorsqu'on appuie sur une touche du clavier, cette fonction est appelée. L'objet de la touche est fourni en paramètre. Si nous appuyons sur la touche « a » du clavier, le rappel s'imprimera dans le terminal :

alphanumeric key a pressed # appuie sur touche alphanumérique a

Cependant, si nous appuyons sur une « touche spéciale » comme [Shift] ou [Ctrl], le rappel imprimera :

special key Key.shift pressed #appuie sur touche spéciale Touche.shift

ou

special key Key.ctrl pressed #appuie sur touche spéciale Key.ctrl

This way, we can monitor for any type of keypress. We can also monitor for key-release as well. Sometimes, monitoring for the release of a key is a better option, since sometimes we can get multiple events when a key is pressed because the keyboard is internally dirty. Here is the author’s key-release callback function (bottom right). In this function, the callback is looking for just one thing. The [Esc] key, which will stop the listener process. To start the pynput listener in the main loop, you simply instantiate the listener thread with the callback functions so that the keyboard events are captured. The listener.join() allows the main thread (program) to be notified of the events and any errors or exceptions. with keyboard.Listener(on_press=on_press, on_release=on_release) as listener: listener.join() Now, we’ll create our demo program that will contain the main thread, as well as two secondary threads and the listener thread.

De cette façon, nous pouvons surveiller tout type d'appui sur les touches. Nous pouvons également surveiller le relâchement des touches. Dans certains cas, la surveillance du relâchement d'une touche est une meilleure option, car nous pouvons parfois obtenir plusieurs événements lors de l'appui sur une touche parce que le clavier est sale à l'intérieur. Voici, en bas à droite, la fonction de rappel du relâchement d'une touche de l'auteur.

Dans cette fonction, le rappel ne cherche qu'une seule chose : la touche [Esc], qui arrête le processus d'écoute.

Pour démarrer listener (écouteur) de pynput dans la boucle principale, il suffit d'instancier le fil de listener avec les fonctions de rappel de façon à ce que les événements du clavier soient capturés. Listener.join() permet au fil principal (programme) d'être notifié des événements et de toute erreur ou exception.

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

Maintenant, nous allons créer notre programme de démonstration qui contiendra le fil principal, ainsi que deux fils secondaires et le fil listener.

The Code The first thing we need to do, as always, is to import our libraries… import logging import threading import time import datetime from pynput import keyboard Next, we need to define our callback functions (next page, top right) for the listener. We’ve just discussed what they do.

Le code

La première chose à faire, comme toujours, est d'importer nos bibliothèques :

import logging import threading import time import datetime from pynput import keyboard

Ensuite, nous devons définir nos fonctions de rappel (page suivante, en haut à droite) pour listener. Nous venons de discuter de ce qu'elles font.

There will be two threads that each run in a continual loop, getting the current time, printing it, sleeping for a specified amount of time (each thread will have a different sleep time), and then doing it all over again. Since I’m using a simple while loop, the loop(s) run until the test condition is false. We set the condition to True before we start the loop. For example, in the first thread, we use a global variable called doloop1, which we set to True before the loop. The loop will continue to run until, and if, doloop1 becomes False. When it becomes False, the loop is exited, and the thread will end. In order to do this, I created a function called stop_threads() which sets the two global functions to False. This will be called after the [Esc] key is pressed to end the program. def stop_threads(): global doloop1, doloop2 doloop1 = False doloop2 = False

Il y aura deux threads qui tourneront chacun en boucle continue, pour obtenir l'heure actuelle, l'imprimer, dormir pendant une durée déterminée (chaque fil aura un temps de sommeil différent), puis tout recommencer. Comme j'utilise une simple boucle while, la ou les boucles tournent jusqu'à ce que la condition de test soit False (fausse). Nous réglons la condition sur « True » (vraie) avant de commencer la boucle. Par exemple, dans le premier fil, nous utilisons une variable globale appelée doloop1, que nous mettons à True avant la boucle. La boucle continuera à tourner jusqu'à ce que, et si, doloop1 devient False. Lorsqu'elle devient False, on quitte la boucle et le fil se termine. Pour ce faire, j'ai créé une fonction appelée stop_threads() qui met les deux fonctions globales à False. Cette fonction sera appelée après un appui sur la touche [Esc] pour mettre fin au programme :

def stop_threads():

  global doloop1, doloop2
  doloop1 = False
  doloop2 = False

Now (middle right) we define the first thread function. The idea behind this, as I said before, is to loop continuously, printing the current time, and sleep for 5 seconds and then do it all over again. Thread number two (bottom right) is almost exactly the same as thread number one, with the only difference is the sleep time for thread 2 is 10 seconds. The main loop starts all of the threads and then sits idle until the listener thread notifies us that the [Esc] key has been pressed, then politely shuts down the two “worker” threads.

The next lines start the two time display threads and the keyboard listener.

  t1.start()
  t2.start()
  with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
      listener.join()**
      

Maintenant (au milieu à droite), nous définissons la fonction du premier fil. Comme je l'ai déjà dit, l'idée est de faire une boucle continue, en imprimant l'heure actuelle, et de dormir pendant 5 secondes, puis de tout recommencer.

Le fil numéro 2 (en bas à droite) est presque exactement le même que le fil numéro 1, à la seule différence que le temps de sommeil du fil 2 est de 10 secondes.

La boucle principale démarre tous les fils et puis reste inactive jusqu'à ce que le fil listener nous informe que la touche [Esc] a été enfoncée, puis arrête proprement les deux fils “travailleurs”.

Les lignes suivantes démarrent les deux fils d'affichage du temps et le listener du clavier :

  t1.start()
  t2.start()
  with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
      listener.join()   

At this point, when the listener.join() call is made, the program just waits. In a real program, you would normally have other things to do, but this is just a simple demo. Once the [Esc] key is released (remember that we monitor for that in the on_release() callback function), the rest of the code is run. logging.info(“Main : wait for the thread to finish”) stop_threads() logging.info(“Main : all done”) logging.info(“Ending Program!”) Lastly, we use the following code as the entry-point into our program, calling the mainloop() function. if name == “main”: mainloop() When the program is run, we’ll get the following output in the terminal.

À ce stade, lorsque l'appel de listener.join() est effectué, le programme attend simplement. Dans un vrai programme, vous auriez normalement d'autres choses à faire, mais ceci n'est qu'une simple démo. Une fois que la touche [Esc] est relâchée (rappelez-vous que nous surveillons cela dans la fonction de rappel on_release()), le reste du code est exécuté :

  logging.info("Main    : wait for the thread to finish")
 

stop_threads()

  logging.info("Main    : all done")
  logging.info("Ending Program!")

Enfin, nous utilisons le code suivant comme point d'entrée dans notre programme, en appelant la fonction mainloop() :

if name == “main” :

mainloop()

Lorsque le programme est exécuté, nous obtenons la sortie suivante dans le terminal :

07:24:18: Main : before creating thread 07:24:18: Main : before running thread Press the <Esc> key to exit… 07:24:18: Thread 1: starting This thread shows the time every 5 seconds… Thread 1 Time: 07:24:18 07:24:18: Thread 2: Starting This thread shows the time every 10 seconds… Thread 2 Time: 07:24:18 Thread 1 Time: 07:24:23 Thread 1 Time: 07:24:28 Thread 2 Time: 07:24:28 Thread 1 Time: 07:24:33 Thread 1 Time: 07:24:38 Thread 2 Time: 07:24:38 … Thread 1 Time: 07:24:43 Thread 1 Time: 07:24:48 Thread 2 Time: 07:24:48 Thread 1 Time: 07:24:53 Thread 1 Time: 07:24:58 Thread 2 Time: 07:24:58 special key Key.esc pressed 07:24:59: Main : wait for the thread to finish 07:24:59: Main : all done 07:24:59: Ending Program!

07:24:18: Main : before creating thread 07:24:18: Main : before running thread Press the <Esc> key to exit… 07:24:18: Thread 1: starting This thread shows the time every 5 seconds… Thread 1 Time: 07:24:18 07:24:18: Thread 2: Starting This thread shows the time every 10 seconds… Thread 2 Time: 07:24:18 Thread 1 Time: 07:24:23 Thread 1 Time: 07:24:28 Thread 2 Time: 07:24:28 Thread 1 Time: 07:24:33 Thread 1 Time: 07:24:38 Thread 2 Time: 07:24:38 … Thread 1 Time: 07:24:43 Thread 1 Time: 07:24:48 Thread 2 Time: 07:24:48 Thread 1 Time: 07:24:53 Thread 1 Time: 07:24:58 Thread 2 Time: 07:24:58 special key Key.esc pressed 07:24:59: Main : wait for the thread to finish 07:24:59: Main : all done 07:24:59: Ending Program!

That’s it. Now you have a light idea of how threads can be used, created, stopped, and how to capture keystrokes using pynput. I’ve put this month’s code up on my github repository at https://github.com/gregwa1953/FCM-165 As always, until next time; stay safe, healthy, positive and creative!

C'est tout. Maintenant, vous avez une petite idée de la façon dont les fils peuvent être utilisés, créés, arrêtés, et comment capturer les frappes de clavier en utilisant pynput.

J'ai mis le code de ce mois-ci sur mon dépôt github à l'adresse https://github.com/gregwa1953/FCM-165

Comme toujours, jusqu'à la prochaine fois ; restez en sécurité, en bonne santé, positif et créatif !

issue165/python.txt · Dernière modification : 2021/02/02 11:41 de auntiee