Möchtet ihr die längliche Herleitung lesen? Hier ist sie: Bei GNU/Linux.ch gibt es Wettbewerbe, bei denen man tolle Preise gewinnen kann. In letzter Zeit waren das Schreibwettbewerbe, die die Community zum Mitmachen bewegen sollten. Wir planen jedoch wieder einen Programmierwettbewerb. Ein Solcher bedarf einer gründlichen Planung, damit die Aufgabe interessant und nicht zu schwierig ist. Damit beschäftige ich mich zurzeit.
Selbstverständlich verrate ich nicht, worum es darin gehen wird. Nur soviel, es wird ein lustiges Partyspiel sein, welches es zu programmieren gilt. Ein Teil dieses Spiels ist ein Countdown-Zähler, der mit einem Tastendruck unterbrochen werden soll. Und damit komme ich zum Inhalt dieses Artikels.
Die Frage lautet: Wie kann ich eine laufende Schleife in Python durch einen beliebigen Tastendruck unterbrechen? Das klingt simple, ist es aber nicht. Beginnen möchte ich mit einem Schleifen-Beispiel in Python:
#!/usr/bin/env python3
import time
number = 10
while True:
if number == 0:
break
else:
print(number)
number -= 1
time.sleep(1)
Was macht dieses Skript? Es importiert das Modul time, damit man die Schleife um 1 Sekunde verlangsamen kann. Es definiert die Variable number, die in der while-Schleife heruntergezählt wird. Bei jedem Durchlauf der Schleife wird der Wert von number ausgegeben. Sobald number bis auf Null heruntergezählt ist, wird die Schleife mit break abgebrochen. So weit, so einfach.
Nun kommt der schwierige Teil. Die Schleife soll durch einen beliebigen Tastendruck unterbrochen werden können. Eine Suche im Internet fördert nur umständliche Lösungen hervor, die Zusatzmodule verwenden, die nicht zu den Python-Standardmodulen gehören, z. B. das Modul keyboard oder pynput. Ich möchte hier nicht darauf eingehen, warum diese Module etwas nervig sind.
Eine bessere Lösung habe ich nicht mit der Suchmaschine, sondern mit GPT 3.5-Turbo gefunden. Am Anfang der Fragerunde präsentierte die KI dieselben Lösungsvorschläge, wie die normale Suche. Nachdem ich einige Male nachgefragt habe, rückte die KI eine bessere Lösung heraus:
import sys
import select
run_loop = True
while run_loop:
rlist, _, _ = select.select([sys.stdin], [], [], 0.1)
if rlist:
key = sys.stdin.read(1)
if key:
run_loop = False
# Your loop code here
pass
Solchen KI-Vorschlägen darf man nie blind vertrauen, insbesondere wenn es um Code geht. Daher habe ich nachgelesen, was die Methode select im Modul select macht. Alles gut; keine Halluzinationen; funktioniert.
Und so sieht das Ergebnis aus:
#!/usr/bin/env python3
import time
import sys
import select
number = 10
while True:
rlist, _, _ = select.select([sys.stdin], [], [], 0.1)
if rlist:
key = sys.stdin.read(1)
if key:
break
if number == 0:
break
else:
print(number)
number -= 1
time.sleep(1)
Das ist die einfachste Lösung, die ich finden konnte, um eine Schleife (non-blocking) durch einen Tastendruck abzubrechen. Falls ihr etwas Besseres kennt, nur her damit.
Dumme Frage aber warum “while = True“ und nicht “while number > -1“? Dann spart man sich die komplette "if number == 0"
Oder noch kürzer 'while number:'
Bei mir (Tuxedo os 2, KDE, Python 3.10.12) klappt es nur mit der entertaste, nicht mit normalen.
Kann ist bestätigen. Verstehe jedoch nicht, warum der Code nur auf ENTER reagiert.
Im Artikel geht es um "non-blocking keystroke detection", nicht um schönen Code des Drumherums. Ausserdem geht es um Verständlichkeit für Einsteieger:innen.
Ich habe Deinen Ansatz gefunden und noch zwei weitere "Signal" und "Thread". (GitHub: atupal/select_input.py)[https://gist.github.com/atupal/5865237]
Danke für die Beispiele. Aber sie sind "blocking" und reagieren auch nur auf ENTER.
Funktioniert das auch auf Windows?
Probiere es aus. Ich weiss es nicht.
Ich habe nun die perfekte Lösung gefunden, die bei jedes Zeichen auslöst, aber die Schwierigkeit ist folgende: "Es muss genau in den richtigen moment die Taste gedrückt werden. Bei 0.1 war es mir unmöglich selbst bei 2 ist es schwierig. Nur wenn ich eine Taste spamme, dann löst es aus und beendet sich."
Code mit normalen Python Bibliotheken ohne etwas per pip nach zu installieren. Disclaimer: ABER funktioniert nur unter Linux! Windows 10/11 könnte es vielleicht per WSL funktionieren, aber ist nicht getestet, da ich dies nicht einsetze.
Angepasster Code:
Wie läuft das hier noch mal mit dem Code? Mit tripple-backtick-fences?