Curses-programmering med Python¶
- Författare:
A.M. Kuchling, Eric S. Raymond
- Release:
2.04
Vad är curses?¶
Curses-biblioteket tillhandahåller en terminaloberoende skärmmålnings- och tangentbordshanteringsfunktion för textbaserade terminaler; sådana terminaler inkluderar VT100, Linux-konsolen och den simulerade terminalen som tillhandahålls av olika program. Displayterminaler stöder olika kontrollkoder för att utföra vanliga åtgärder som att flytta markören, rulla skärmen och radera områden. Olika terminaler använder vitt skilda koder och har ofta sina egna små egenheter.
I en värld av grafiska skärmar kan man fråga sig ”varför bry sig”? Det är sant att terminaler med teckencellsskärm är en föråldrad teknik, men det finns nischer där det fortfarande är värdefullt att kunna göra snygga saker med dem. En nisch är små eller inbäddade Unix-datorer som inte kör en X-server. En annan är verktyg som OS-installatörer och kärnkonfiguratorer som kan behöva köras innan något grafiskt stöd finns tillgängligt.
Curses-biblioteket har ganska grundläggande funktioner och ger programmeraren en abstraktion av en bildskärm som innehåller flera icke överlappande textfönster. Innehållet i ett fönster kan ändras på olika sätt - lägga till text, radera den, ändra dess utseende - och curses-biblioteket räknar ut vilka kontrollkoder som behöver skickas till terminalen för att producera rätt utdata. curses tillhandahåller inte många användargränssnittskoncept som knappar, kryssrutor eller dialogrutor; om du behöver sådana funktioner, överväg ett användargränssnittsbibliotek som Urwid.
Curses-biblioteket skrevs ursprungligen för BSD Unix; de senare System V-versionerna av Unix från AT&T lade till många förbättringar och nya funktioner. BSD curses underhålls inte längre, utan har ersatts av ncurses, som är en implementering av AT&T-gränssnittet med öppen källkod. Om du använder ett Unix med öppen källkod, som Linux eller FreeBSD, använder ditt system nästan säkert ncurses. Eftersom de flesta aktuella kommersiella Unix-versioner är baserade på System V-kod, kommer alla funktioner som beskrivs här förmodligen att finnas tillgängliga. De äldre versionerna av curses som finns i vissa proprietära Unix-system kanske dock inte stöder allt.
Windows-versionen av Python innehåller inte modulen curses
. En portad version som heter UniCurses är tillgänglig.
Python-modulen curses¶
Python-modulen är ett ganska enkelt omslag över de C-funktioner som tillhandahålls av curses; om du redan är bekant med curses-programmering i C är det väldigt enkelt att överföra den kunskapen till Python. Den största skillnaden är att Python-gränssnittet gör saker enklare genom att slå samman olika C-funktioner som addstr()
, mvaddstr()
och mvwaddstr()
till en enda addstr()
-metod. Du kommer att se detta mer i detalj senare.
Denna HOWTO är en introduktion till att skriva textlägesprogram med curses och Python. Den försöker inte vara en komplett guide till curses API; för det, se Python-biblioteksguidens avsnitt om ncurses och C-manualsidorna för ncurses. Det kommer dock att ge dig de grundläggande idéerna.
Starta och avsluta en curses-applikation¶
Innan något kan göras måste curses initialiseras. Detta görs genom att anropa funktionen initscr()
, som bestämmer terminaltypen, skickar alla nödvändiga inställningskoder till terminalen och skapar olika interna datastrukturer. Om funktionen lyckas returnerar initscr()
ett fönsterobjekt som representerar hela skärmen; detta kallas vanligtvis stdscr
efter namnet på motsvarande C-variabel.
import curses
stdscr = curses.initscr()
Vanligtvis stänger curses-applikationer av automatisk ekoåtergivning av tangenter till skärmen, för att kunna läsa tangenter och bara visa dem under vissa omständigheter. Detta kräver att man anropar funktionen noecho()
.
curses.noecho()
Program behöver också ofta reagera på tangenter direkt, utan att Enter-tangenten behöver tryckas; detta kallas cbreak-läge, i motsats till det vanliga buffrade inmatningsläget.
curses.cbreak()
Terminaler returnerar vanligtvis specialtangenter, t.ex. markörknapparna eller navigeringstangenter som Page Up och Home, som en escape-sekvens med flera byte. Du kan skriva ditt program så att det förväntar sig sådana sekvenser och behandlar dem därefter, men curses kan göra det åt dig genom att returnera ett specialvärde som curses.KEY_LEFT
. För att få curses att göra jobbet måste du aktivera tangentbordsläget.
stdscr.keypad(True)
Att avsluta en curses-applikation är mycket enklare än att starta en. Du behöver bara anropa:
curses.nocbreak()
stdscr.keypad(False)
curses.echo()
för att återställa de curses-vänliga terminalinställningarna. Anropa sedan funktionen endwin()
för att återställa terminalen till dess ursprungliga driftläge.
curses.endwin()
Ett vanligt problem när man felsöker en curses-applikation är att terminalen blir rörig när applikationen dör utan att terminalen återställs till sitt tidigare tillstånd. I Python händer detta ofta när din kod är buggig och ger upphov till ett undantag som inte fångats upp. Tangenter ekar inte längre på skärmen när du skriver dem, till exempel, vilket gör det svårt att använda skalet.
I Python kan du undvika dessa komplikationer och göra felsökningen mycket enklare genom att importera funktionen curses.wrapper()
och använda den så här:
from curses import wrapper
def main(stdscr):
# Clear screen
stdscr.clear()
# This raises ZeroDivisionError when i == 10.
for i in range(0, 11):
v = i-10
stdscr.addstr(i, 0, '10 divided by {} is {}'.format(v, 10/v))
stdscr.refresh()
stdscr.getkey()
wrapper(main)
Funktionen wrapper()
tar ett anropbart objekt och gör de initialiseringar som beskrivs ovan, samt initialiserar färger om det finns färgstöd. wrapper()
kör sedan din tillhandahållna anropbara kod. När det anropbara objektet returneras återställer wrapper()
terminalens ursprungliga tillstånd. Den anropbara funktionen anropas inuti en try
…except
som fångar upp undantag, återställer terminalens tillstånd och sedan anropar undantaget igen. Därför kommer din terminal inte att lämnas i ett konstigt tillstånd vid undantag och du kommer att kunna läsa undantagets meddelande och traceback.
Fönster och pads¶
Fönster är den grundläggande abstraktionen i curses. Ett fönsterobjekt representerar ett rektangulärt område på skärmen och stöder metoder för att visa text, radera den, låta användaren mata in strängar och så vidare.
Objektet stdscr
som returneras av funktionen initscr()
är ett fönsterobjekt som täcker hela skärmen. Många program behöver bara detta enda fönster, men du kanske vill dela upp skärmen i mindre fönster för att kunna rita om eller rensa dem separat. Funktionen newwin()
skapar ett nytt fönster av en given storlek och returnerar det nya fönsterobjektet.
begin_x = 20; begin_y = 7
height = 5; width = 40
win = curses.newwin(height, width, begin_y, begin_x)
Observera att det koordinatsystem som används i curses är ovanligt. Koordinater skickas alltid i ordningen y,x, och det övre vänstra hörnet av ett fönster är koordinat (0,0). Detta bryter mot den normala konventionen för hantering av koordinater där x-koordinaten kommer först. Detta är en olycklig skillnad mot de flesta andra datorprogram, men det har varit en del av curses sedan det först skrevs, och det är för sent att ändra på saker nu.
Ditt program kan bestämma skärmens storlek genom att använda variablerna curses.LINES
och curses.COLS
för att få storlekarna y och x. Lagliga koordinater kommer då att sträcka sig från (0,0)
till (curses.LINES - 1, curses.COLS - 1)
.
När du anropar en metod för att visa eller radera text visas inte effekten omedelbart på skärmen. Istället måste du anropa metoden refresh()
för fönsterobjekt för att uppdatera skärmen.
Detta beror på att curses ursprungligen skrevs med tanke på långsamma 300-baud terminalanslutningar; med dessa terminaler var det mycket viktigt att minimera den tid som krävdes för att rita om skärmen. Istället ackumulerar curses ändringar på skärmen och visar dem på det mest effektiva sättet när du anropar refresh()
. Om ditt program t.ex. visar text i ett fönster och sedan rensar fönstret, behöver du inte skicka originaltexten eftersom den aldrig är synlig.
I praktiken är det inte särskilt komplicerat att programmera med curses om man uttryckligen säger till curses att rita om ett fönster. De flesta program går in i en febril aktivitet och pausar sedan i väntan på en tangenttryckning eller någon annan åtgärd från användarens sida. Allt du behöver göra är att vara säker på att skärmen har ritats om innan du pausar för att vänta på användarinmatning, genom att först anropa stdscr.refresh()
eller refresh()
-metoden för något annat relevant fönster.
En pad är ett specialfall av ett fönster; den kan vara större än den faktiska skärmen och endast en del av padden visas åt gången. För att skapa en pad krävs paddens höjd och bredd, medan uppdatering av en pad kräver att koordinaterna anges för det område på skärmen där en del av padden ska visas:
pad = curses.newpad(100, 100)
# Dessa slingor fyller paddan med bokstäver; addch() förklaras
# förklaras i nästa avsnitt
för y i intervallet(0, 99):
for x in range(0, 99):
pad.addch(y,x, ord('a') + (x*x+y*y) % 26)
# Visar ett utsnitt av paddan i mitten av skärmen.
# (0,0) : koordinat för det övre vänstra hörnet av det område som ska visas.
# (5,5) : koordinat för övre vänstra hörnet av fönsterområdet som ska fyllas
# med pad-innehåll.
# (20, 75) : koordinat för det nedre högra hörnet av fönsterytan som ska
# : fyllas med pad-innehåll.
pad.refresh( 0,0, 5,5, 20,75)
Anropet refresh()
visar en del av padden i den rektangel som sträcker sig från koordinat (5,5) till koordinat (20,75) på skärmen; det övre vänstra hörnet av den visade delen är koordinat (0,0) på padden. Utöver denna skillnad är paddar precis som vanliga fönster och stöder samma metoder.
Om du har flera fönster och pads på skärmen finns det ett mer effektivt sätt att uppdatera skärmen och förhindra irriterande skärmflimmer när varje del av skärmen uppdateras. refresh()
gör faktiskt två saker:
Anropar
noutrefresh()
-metoden för varje fönster för att uppdatera en underliggande datastruktur som representerar det önskade tillståndet på skärmen.Anropar funktionen
doupdate()
för att ändra den fysiska skärmen så att den motsvarar det önskade tillstånd som registrerats i datastrukturen.
Istället kan du anropa noutrefresh()
på ett antal fönster för att uppdatera datastrukturen, och sedan anropa doupdate()
för att uppdatera skärmen.
Visning av text¶
Från en C-programmerares synvinkel kan curses ibland se ut som en snårig labyrint av funktioner, alla subtilt olika. Till exempel visar addstr()
en sträng vid den aktuella markörpositionen i fönstret stdscr
, medan mvaddstr()
först flyttar till en given y,x-koordinat innan strängen visas. waddstr()
är precis som addstr()
, men tillåter att man anger ett fönster att använda istället för att använda stdscr
som standard. mvwaddstr()
tillåter att man anger både ett fönster och en koordinat.
Lyckligtvis döljer Python-gränssnittet alla dessa detaljer. stdscr
är ett fönsterobjekt som alla andra, och metoder som addstr()
accepterar flera argumentformer. Vanligtvis finns det fyra olika former.
Formulär |
Beskrivning |
---|---|
str eller ch |
Visa strängen str eller tecknet ch vid den aktuella positionen |
str eller ch, attr |
Visa strängen str eller tecknet ch med attributet attr på den aktuella positionen |
y, x, str eller ch |
Flytta till position y,x i fönstret och visa str eller ch |
y, x, str eller ch, attr |
Flytta till position y,x i fönstret och visa str eller ch med hjälp av attributet attr |
Attribut gör det möjligt att visa text i markerade former som fetstil, understrykning, omvänd kod eller i färg. De förklaras mer i detalj i nästa underavsnitt.
Metoden addstr()
tar en Python-sträng eller bytestring som det värde som ska visas. Innehållet i bytestrings skickas till terminalen som det är. Strängar kodas till bytes med hjälp av värdet i fönstrets attribut encoding
; detta är standardvärdet för systemets standardkodning som returneras av locale.getencoding()
.
Metoderna addch()
tar ett tecken, som kan vara antingen en sträng med längden 1, en bytestring med längden 1 eller ett heltal.
Konstanter finns för tilläggstecken; dessa konstanter är heltal större än 255. Exempelvis är ACS_PLMINUS
en +/- symbol och ACS_ULCORNER
är det övre vänstra hörnet av en ruta (praktiskt för att rita gränser). Du kan också använda lämpliga Unicode-tecken.
Windows kommer ihåg var markören befann sig efter den senaste operationen, så om du utelämnar y,x-koordinaterna kommer strängen eller tecknet att visas där den senaste operationen slutade. Du kan också flytta markören med metoden move(y,x)
. Eftersom vissa terminaler alltid visar en blinkande markör kan det vara bra att se till att markören är placerad på en plats där den inte stör; det kan vara förvirrande att ha markören blinkande på en till synes slumpmässig plats.
Om ditt program inte alls behöver en blinkande markör kan du anropa curs_set(False)
för att göra den osynlig. För kompatibilitet med äldre curses-versioner finns funktionen leaveok(bool)
som är en synonym till curs_set()
. När bool är sant kommer curses-biblioteket att försöka undertrycka den blinkande markören, och du behöver inte oroa dig för att lämna den på udda platser.
Attribut och färg¶
Tecken kan visas på olika sätt. Statusrader i en textbaserad applikation visas ofta i omvänd video, eller så kan en textvisare behöva markera vissa ord. curses stöder detta genom att låta dig ange ett attribut för varje cell på skärmen.
Ett attribut är ett heltal, där varje bit representerar ett annat attribut. Du kan försöka visa text med flera attributbitar inställda, men curses garanterar inte att alla möjliga kombinationer är tillgängliga eller att de alla är visuellt distinkta. Det beror på hur bra terminalen som används är, så det är säkrast att hålla sig till de vanligaste attributen, som listas här.
Attribut |
Beskrivning |
---|---|
|
Blinkande text |
Extra ljus eller fet text |
|
Halv ljus text |
|
Text för omvänd video |
|
Det bästa markeringsläget som finns |
|
Understruken text |
För att visa en statusrad med omvänd video på den övre raden på skärmen kan du alltså koda:
stdscr.addstr(0, 0,"Aktuellt läge: Skrivläge",
curses.A_REVERSE)
stdscr.refresh()
Curses-biblioteket stöder också färg på de terminaler som tillhandahåller det. Den vanligaste sådana terminalen är förmodligen Linux-konsolen, följt av färg xterms.
För att använda färg måste du anropa funktionen start_color()
strax efter anropet av initscr()
, för att initiera standardfärgsättningen (funktionen curses.wrapper()
gör detta automatiskt). När det är gjort returnerar funktionen has_colors()
TRUE om terminalen som används faktiskt kan visa färg. (Obs: curses använder den amerikanska stavningen ”color”, istället för den kanadensiska/brittiska stavningen ”colour”. Om du är van vid den brittiska stavningen får du finna dig i att stava det fel för dessa funktioners skull)
Curses-biblioteket har ett begränsat antal färgpar, som innehåller en förgrundsfärg (eller textfärg) och en bakgrundsfärg. Du kan få fram attributvärdet som motsvarar ett färgpar med funktionen color_pair()
; detta kan bitvis-OR:as med andra attribut som A_REVERSE
, men återigen, sådana kombinationer är inte garanterade att fungera på alla terminaler.
Ett exempel, som visar en textrad med färgparet 1:
stdscr.addstr("Fin text", curses.color_pair(1))
stdscr.refresh()
Som jag sa tidigare består ett färgpar av en förgrunds- och en bakgrundsfärg. Funktionen init_pair(n, f, b)
ändrar definitionen av färgparet n, till förgrundsfärgen f och bakgrundsfärgen b. Färgparet 0 är hårdkodat till vitt på svart och kan inte ändras.
Färgerna är numrerade och start_color()
initierar 8 grundfärger när den aktiverar färgläget. Dessa är: 0:svart, 1:röd, 2:grön, 3:gul, 4:blå, 5:magenta, 6:cyan och 7:vit. Modulen curses
definierar namngivna konstanter för var och en av dessa färger: curses.COLOR_BLACK
, curses.COLOR_RED
, och så vidare.
Låt oss sätta ihop allt detta. För att ändra färg 1 till röd text på en vit bakgrund, skulle du ringa:
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)
När du ändrar ett färgpar kommer all text som redan visas med det färgparet att ändras till de nya färgerna. Du kan också visa ny text i den här färgen med:
stdscr.addstr(0,0,"RED ALERT!", curses.color_pair(1))
Mycket avancerade terminaler kan ändra definitionerna av de faktiska färgerna till ett givet RGB-värde. Detta gör att du kan ändra färg 1, som vanligtvis är röd, till lila eller blå eller någon annan färg du vill ha. Tyvärr har Linux-konsolen inte stöd för detta, så jag kan inte prova det och kan inte ge några exempel. Du kan kontrollera om din terminal kan göra detta genom att anropa can_change_color()
, som returnerar True
om möjligheten finns. Om du har turen att ha en så begåvad terminal kan du läsa mer i systemets man-sidor.
Användarinput¶
C-biblioteket curses erbjuder endast mycket enkla inmatningsmekanismer. Pythons modul curses
lägger till en grundläggande widget för textinmatning. (Andra bibliotek som Urwid har mer omfattande samlingar av widgetar)
Det finns två metoder för att få in data från ett fönster:
getch()
uppdaterar skärmen och väntar sedan på att användaren ska trycka på en tangent, och visar tangenten omecho()
har anropats tidigare. Du kan eventuellt ange en koordinat till vilken markören ska flyttas innan den pausas.getkey()
gör samma sak men omvandlar heltalet till en sträng. Enskilda tecken returneras som strängar med 1 tecken, och specialnycklar som funktionstangenter returnerar längre strängar som innehåller ett nyckelnamn somKEY_UP
eller^G
.
Det är möjligt att inte vänta på användaren med hjälp av fönstermetoden nodelay()
. Efter nodelay(True)
blir getch()
och getkey()
för fönstret icke-blockerande. För att signalera att ingen inmatning är klar returnerar getch()
curses.ERR
(ett värde på -1) och getkey()
utlöser ett undantag. Det finns också en halfdelay()
-funktion, som kan användas för att (i praktiken) ställa in en timer på varje getch()
; om ingen inmatning blir tillgänglig inom en angiven fördröjning (mätt i tiondelar av en sekund), ger curses upphov till ett undantag.
Metoden getch()
returnerar ett heltal; om det är mellan 0 och 255 representerar det ASCII-koden för den tangent som tryckts in. Värden större än 255 är specialtangenter som Page Up, Home eller markörknapparna. Du kan jämföra det returnerade värdet med konstanter som curses.KEY_PPAGE
, curses.KEY_HOME
eller curses.KEY_LEFT
. Huvudloopen i ditt program kan se ut ungefär så här:
medan True:
c = stdscr.getch()
if c == ord('p'):
PrintDocument()
elif c == ord('q'):
break # Avsluta while-slingan
elif c == curses.KEY_HOME:
x = y = 0
Modulen curses.ascii
tillhandahåller ASCII-klasstillhörighetsfunktioner som tar antingen heltals- eller 1-teckenssträngargument; dessa kan vara användbara för att skriva mer läsbara tester för sådana loopar. Den innehåller också konverteringsfunktioner som tar antingen heltal eller 1-teckenssträngar som argument och returnerar samma typ. Till exempel returnerar curses.ascii.ctrl()
det kontrolltecken som motsvarar dess argument.
Det finns också en metod för att hämta en hel sträng, getstr()
. Den används inte så ofta eftersom dess funktionalitet är ganska begränsad; de enda redigeringstangenter som finns är backstegstangenten och Enter-tangenten, som avslutar strängen. Den kan eventuellt begränsas till ett fast antal tecken.
curses.echo() # Aktivera eko av tecken
# Hämta en sträng med 15 tecken, med markören på den översta raden
s = stdscr.getstr(0,0, 15)
Modulen curses.textpad
tillhandahåller en textruta som stöder en Emacs-liknande uppsättning tangentbindningar. Olika metoder i klassen Textbox
stöder redigering med inmatningsvalidering och insamling av redigeringsresultaten antingen med eller utan efterföljande mellanslag. Här är ett exempel:
import curses
from curses.textpad import Textbox, rectangle
def main(stdscr):
stdscr.addstr(0, 0, "Enter IM message: (hit Ctrl-G to send)")
editwin = curses.newwin(5,30, 2,1)
rectangle(stdscr, 1,0, 1+5+1, 1+30+1)
stdscr.refresh()
box = Textbox(editwin)
# Let the user edit until Ctrl-G is struck.
box.edit()
# Get resulting contents
message = box.gather()
Se biblioteksdokumentationen för curses.textpad
för mer information.
För mer information¶
Denna HOWTO täcker inte några avancerade ämnen, som att läsa innehållet på skärmen eller fånga mushändelser från en xterm-instans, men Python-bibliotekssidan för modulen curses
är nu någorlunda komplett. Du bör bläddra igenom den härnäst.
Om du är osäker på hur curses-funktionerna fungerar i detalj bör du läsa manualsidorna för din curses-implementering, oavsett om det är ncurses eller en egenutvecklad Unix-leverantörs. Manualsidorna dokumenterar alla konstigheter och innehåller fullständiga listor över alla funktioner, attribut och ACS_*-tecken som är tillgängliga för dig.
Eftersom curses API är så stort finns det funktioner som inte stöds i Python-gränssnittet. Ofta beror detta inte på att de är svåra att implementera, utan på att ingen har behövt dem ännu. Dessutom stöder Python ännu inte menybiblioteket som är associerat med ncurses. Patchar som lägger till stöd för dessa skulle vara välkomna; se the Python Developer’s Guide för att lära dig mer om att skicka in patchar till Python.
Writing Programs with NCURSES: en utförlig handledning för C-programmerare.
”Manuell sida för ncurses <https://linux.die.net/man/3/ncurses>
”Use curses… don’t swwear”: video av ett PyCon 2013-tal om att styra terminaler med hjälp av curses eller Urwid.
”Console Applications with Urwid”: video av ett föredrag på PyCon CA 2012 som visar några program som skrivits med Urwid.