Hallo!
kann man irgendwie abfragen, ob im Terminal eine taste gedrückt wurde und die dann einlesen?
oder irgendwie auf eine Taste warten, ohne dass (wie bei input) return gedrückt werden muss?
in C gibt es ja sowas wie kbhit, aber für python hab ich da nix gefunden.
Schonmal vielen Dank für antworten:-)
jtk
Sofortige Tastenabfrage im Terminal/Eingabeauffurderung
So ohne weiteres ist das nicht möglich, da die Terminals normalerweise im Zeilenmodus agieren und Chars erst mit <return> abgesetzt werden. Für unixoide Systeme gibts das termios-Modul, in dem ICANON von Hand False setzen könntest, funktioniert aber nicht an der DOS-Konsole.
Einfacher gehts mit dem Modul curses, da übernimmt ncurses die komplette Terminalsteuerung. Für Windows gabs früher separate Module, wcurses und console. Keine Ahnung, ob die noch aktuell sind.
In der Pythondokumentation findest Du auch ein Tutorial für curses: http://docs.python.org/howto/curses.html
Edit:
Beide Module sind zumindest für Python 2.5 erhältlich.
wcurses: http://adamv.com/dev/python/curses/
console: http://effbot.org/zone/console-index.htm
Einfacher gehts mit dem Modul curses, da übernimmt ncurses die komplette Terminalsteuerung. Für Windows gabs früher separate Module, wcurses und console. Keine Ahnung, ob die noch aktuell sind.
In der Pythondokumentation findest Du auch ein Tutorial für curses: http://docs.python.org/howto/curses.html
Edit:
Beide Module sind zumindest für Python 2.5 erhältlich.
wcurses: http://adamv.com/dev/python/curses/
console: http://effbot.org/zone/console-index.htm
Ist das immer durch den Zeilenmodus de verwendeten Terminals verursacht? Genau dieses Problem hat man in C nämlich auch, und mir wurde das mit einer freiwilligen Restriktion der C Stdlib erklärt. Ein C Programm hätte derartige Beschränkungen also nicht, wenn man es von einem Terminal ohne diesen Zeilenmodus ausführen würde?So ohne weiteres ist das nicht möglich, da die Terminals normalerweise im Zeilenmodus agieren und Chars erst mit <return> abgesetzt werden.
Hm, gute Frage. Spätestens seit vt100 gibts das ICANON-Flag in der Termios-Struktur, welches dann von den Shells auf 1 gesetzt wird. Damit wird das Terminal kanonisch, sprich die Characterverwaltung (Pufferung und Rausschreiben) wird vom Terminaltreiber vorgenommen bis ein CR erscheint. Dein Programm weiß also noch garnichts von einem geschriebenen Zeichen. Ohne ICANON fällt die treiberseitige Pufferung und Ausgabe weg und jedes Zeichen wird direkt durchgereicht.Ist das immer durch den Zeilenmodus de verwendeten Terminals verursacht?
Wie das unter der DOS-Konsole läuft, weiß ich nicht.
@str1442: Das hat mit der Programmiersprache nichts zu tun, sondern eben wirklich mit den Terminals. Wenn die erst nach Betätigen der Eingabetaste den Inhalt ihres Zeilenpuffers an das Programm schicken, ist das von der Sprache, in der das Programm geschrieben ist, völlig unabhängig.
Weil Terminals recht kompliziert werden können, und sich teilweise auch sehr stark unterscheiden -- "damals" gab's deutlich mehr Hardware-Wildwuchs als die VT-100-Varianten, die heute in Software emuliert werden -- ist dafür wohl nichts in der C-Standardbibliothek gelandet. Denn bei den meisten Terminals kann man auch von Zeilenmodus in einen "Raw"-Modus umschalten. Dann muss man aber im Programm mit den ganzen Codes umgehen können, die die senden und die auch wieder von Gerät zu Gerät unterschiedlich sein können.
Um diese Komplexität ein wenig in den Griff zu bekommen, gibt's Bibliotheken wie `curses`.
Ansonsten ist das direkte Abfragen der Tastatur auch ohne Terminal von Rechnersystem und Betriebssystem abhängig und sehr unterschiedlich gelöst.
Weil Terminals recht kompliziert werden können, und sich teilweise auch sehr stark unterscheiden -- "damals" gab's deutlich mehr Hardware-Wildwuchs als die VT-100-Varianten, die heute in Software emuliert werden -- ist dafür wohl nichts in der C-Standardbibliothek gelandet. Denn bei den meisten Terminals kann man auch von Zeilenmodus in einen "Raw"-Modus umschalten. Dann muss man aber im Programm mit den ganzen Codes umgehen können, die die senden und die auch wieder von Gerät zu Gerät unterschiedlich sein können.
Um diese Komplexität ein wenig in den Griff zu bekommen, gibt's Bibliotheken wie `curses`.
Ansonsten ist das direkte Abfragen der Tastatur auch ohne Terminal von Rechnersystem und Betriebssystem abhängig und sehr unterschiedlich gelöst.
Hallo,
für Linux-Terminals hatte ich mal das hier zusammenprobiert:
HTH
Gruß
P.S.: Wenn das Programm irgendwie abstürzt oder unterbrochen wird, ist das Terminal danach falsch eingestellt und nimmt deshalb ggf. keine Tasten mehr an. Dann STRG+d oder ALT+F4 bzgl. des Terminals drücken.
für Linux-Terminals hatte ich mal das hier zusammenprobiert:
Code: Alles auswählen
#!/usr/bin/env python
import os
import sys
import tty
import termios
import fcntl
import time
class KeyHandler:
def __init__(self):
self.fd = sys.stdin.fileno()
self.oldterm = termios.tcgetattr(self.fd)
self.oldflags = fcntl.fcntl(self.fd, fcntl.F_GETFL)
tty.setcbreak(sys.stdin.fileno())
self.newattr = termios.tcgetattr(self.fd)
self.newattr[3] = self.newattr[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(self.fd, termios.TCSANOW, self.newattr)
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.oldflags | os.O_NONBLOCK)
def oldTerminalSettings(self):
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.oldterm)
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.oldflags)
def newTerminalSettings(self):
# tty.setcbreak(sys.stdin.fileno())
termios.tcsetattr(self.fd, termios.TCSANOW, self.newattr)
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.oldflags | os.O_NONBLOCK)
def getKey(self):
try:
c = sys.stdin.read(1)
return ord(c)
except IOError:
return 0
except KeyboardInterrupt:
self.quit(1)
def quit(self, retcode):
self.oldTerminalSettings()
sys.exit(retcode)
if os.name == "posix":
os.system("clear")
print "\nPress 'q' to stop.\n"
kh = KeyHandler()
keycode = None
while keycode != ord("q"):
print "Running"
keycode = kh.getKey()
if keycode != 0:
print "\t\t\t" + chr(keycode)
time.sleep(0.2)
kh.oldTerminalSettings()
Gruß
P.S.: Wenn das Programm irgendwie abstürzt oder unterbrochen wird, ist das Terminal danach falsch eingestellt und nimmt deshalb ggf. keine Tasten mehr an. Dann STRG+d oder ALT+F4 bzgl. des Terminals drücken.
@str1442
Hier eine Lösung in C unter Linux.
Achtung:
Die Beispiele auf der Seite sind z.T. fehlerhaft. Bitte das Beispiel my_getpass.c so nicht übernehmen, da die Funktion my_getpass() einen ungültigen Pointer zurückgibt.
Hier eine Lösung in C unter Linux.
Achtung:
Die Beispiele auf der Seite sind z.T. fehlerhaft. Bitte das Beispiel my_getpass.c so nicht übernehmen, da die Funktion my_getpass() einen ungültigen Pointer zurückgibt.
Zuletzt geändert von jerch am Samstag 28. März 2009, 23:11, insgesamt 1-mal geändert.
Danke für die Erläuterungen und Beispiele.
@jerch Der Link zeigt in ähnliches wie das Posting von problembär, aber die erste Funktion ist recht kaputt. Diese erzeugt zuerst ein neues char array und danach einen pointer auf dieses und nutzt dazu statisch allozierten Speicher. Dann wird am Ende der Pointer auf das Array zurückgegeben und die aufrufende Funktion bekommt einen netten ungültigen Pointer zurück, da C statisch allozierten Speicher ja funktionsweise freigibt.
@problembär: Danke & Deine Klasse sieht aus wie eine Ansammlung von Funktionen. Methoden sollten niemals krasse Seiteneffekte haben, die sich nicht auf das eigene Objekt beziehen, außer man hat einen guten Grund dazu. Für sowas sollte man Funktionen nehmen. Insbesondere sys.exit() innerhalb einer Methode (oder einer Funktion, die nicht sehr weit oben in der Aufrufhierarchie steht) aufrufen sollte man nie tun. Ansonsten dient die Klasse nur zur Statusspeicherung. Und den Code auf Modulebene am besten in eine main() Funktion packen, zusammen mit einer if __name__ == "__main__": main() Zeile am Ende des Modules, das bewirkt, das das Modul auch dann verwendet werden kann, wenn jemand es importieren will.
@jerch Der Link zeigt in ähnliches wie das Posting von problembär, aber die erste Funktion ist recht kaputt. Diese erzeugt zuerst ein neues char array und danach einen pointer auf dieses und nutzt dazu statisch allozierten Speicher. Dann wird am Ende der Pointer auf das Array zurückgegeben und die aufrufende Funktion bekommt einen netten ungültigen Pointer zurück, da C statisch allozierten Speicher ja funktionsweise freigibt.
@problembär: Danke & Deine Klasse sieht aus wie eine Ansammlung von Funktionen. Methoden sollten niemals krasse Seiteneffekte haben, die sich nicht auf das eigene Objekt beziehen, außer man hat einen guten Grund dazu. Für sowas sollte man Funktionen nehmen. Insbesondere sys.exit() innerhalb einer Methode (oder einer Funktion, die nicht sehr weit oben in der Aufrufhierarchie steht) aufrufen sollte man nie tun. Ansonsten dient die Klasse nur zur Statusspeicherung. Und den Code auf Modulebene am besten in eine main() Funktion packen, zusammen mit einer if __name__ == "__main__": main() Zeile am Ende des Modules, das bewirkt, das das Modul auch dann verwendet werden kann, wenn jemand es importieren will.
Mein Linux-getch fällt etwas übersichtlicher aus:
Anstelle von setcbreak() könnte man auch setraw() nehmen, dann schluckt das Terminal auch ein Ctrl+c ohne KeyboardInterrupt.
Unter Windows:
Code: Alles auswählen
import sys, os, tty, termios
def getch(fd=None):
if fd is None: fd = sys.stdin.fileno()
term_settings = termios.tcgetattr(fd)
try:
tty.setcbreak(fd)
char = os.read(fd, 1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, term_settings)
return char
Unter Windows:
Code: Alles auswählen
import msvcrt
msvcrt.getch()
und noch ne frage zu curses:
das programm bricht irgendwann einfach ab!?!?!?
_curses.error: addch() returned ERR
und noch was?
wieso steht am anfang schon ein "#" da?
---------
Jonny
Code: Alles auswählen
from curses import *
from random import *
from signal import *
ATTRS=[A_ALTCHARSET,A_BLINK,A_BOLD,A_DIM,A_NORMAL,A_STANDOUT,A_UNDERLINE]
def ende(*args):
global running
running=False
print("--- SIGNAL EMPFANGEN ---")
signal(SIGINT, ende)
scr=initscr()
HOEHE, BREITE = scr.getmaxyx()
curs_set(0)
running=True
for i, att in enumerate(ATTRS):
scr.addch(ord(str(i)), att)
scr.getch()
while running:
scr.addch(randrange(HOEHE), randrange(BREITE),
ord("#"), choice(ATTRS))
scr.refresh()
napms(50)
endwin()
_curses.error: addch() returned ERR
und noch was?
wieso steht am anfang schon ein "#" da?
---------
Jonny
@jtk: Du solltest Dir dringend die Sternchen-Importe und ``global`` abgewöhnen.
Namen komplett in Grossbuchstaben dsind per Konvention für Konstanten vorgesehen. Das trifft auf `HOEHE` und `BREITE` aber nicht zu.
Wenn Du den Fehler behandelst und Dir mal die Position ausgeben lässt an der die Ausgabe stattfinden sollte, wirst Du feststellen, dass es immer die Position ganz unten rechts ist, bei der der Fehler auftritt. Da kann wahrscheinlich nichts ausgegeben werden, weil das Terminal dann den ganzen Inhalt um eine Zeile nach oben scrollen würde.
Namen komplett in Grossbuchstaben dsind per Konvention für Konstanten vorgesehen. Das trifft auf `HOEHE` und `BREITE` aber nicht zu.
Wenn Du den Fehler behandelst und Dir mal die Position ausgeben lässt an der die Ausgabe stattfinden sollte, wirst Du feststellen, dass es immer die Position ganz unten rechts ist, bei der der Fehler auftritt. Da kann wahrscheinlich nichts ausgegeben werden, weil das Terminal dann den ganzen Inhalt um eine Zeile nach oben scrollen würde.
- cofi
- Python-Forum Veteran
- Beiträge: 4432
- Registriert: Sonntag 30. März 2008, 04:16
- Wohnort: RGFybXN0YWR0
Man könnte aber auch mal reflektieren, warum das in den meisten Fällen eine blöde Idee ist, statt gleich Repressionen zu fordern.
Im übrigen ist es für den interaktiven Modus ganz nett, da es einem Tipparbeit spart (vor allem da man da meist Wegwerf-Code produziert)
Im übrigen ist es für den interaktiven Modus ganz nett, da es einem Tipparbeit spart (vor allem da man da meist Wegwerf-Code produziert)