Plattformunabhängigkeit und getch(curses)

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
darktrym
User
Beiträge: 680
Registriert: Freitag 24. April 2009, 09:26

Plattformunabhängigkeit und getch(curses)

Beitragvon darktrym » Freitag 24. April 2009, 09:48

Hallo,
ich bin relativer Neuling in Sachen Python. Nachdem ich einige Tutorials/Bücher gelesen habe, möchte ich mein Wissen anwenden. Mein Zielsetzung ist/war ein Brainfuck Interpreter zu schreiben und die Leistung/Implementierung in anderen Programmiersprachen(C) und OSes(Windows/NetBSD) zu vergleichen.
Damit das Programm auch auf anderen BS läuft, benötige ich curses. Nun gibts (n)curses aus irgendwelchen Gründen nicht für Windows(für C gibt es eine Bibliothek). Im Prinzip benötige ich nur die getch Funktion zum Einlesen eines Zeichens. Ich benutze die 3.01 Version von Python.

Gibt es eine andere Möglichkeit eine getch Funktion zu realisieren?
Benutzeravatar
b.esser-wisser
User
Beiträge: 272
Registriert: Freitag 20. Februar 2009, 14:21
Wohnort: Bundeshauptstadt B.

Beitragvon b.esser-wisser » Freitag 24. April 2009, 09:59

Hat Python 3 kein "sys.stdin.read(1)"?
Wcurses scheint noch nicht auf python 3 portiert zu sein (das hattest du bestimmt schon herausgefunden :D )
hth, Jörg
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

Beitragvon HerrHagen » Freitag 24. April 2009, 10:15

Unter Windows gibt es das Modul msvcrt. Darin ist getch enthalten.

@b.esser-wisser: sys.stdin.read(1) ist nicht das gleiche wie getch, da man die Eingabe abschließen muss! Mann kann also auch nacheinander 10 Zeichen eingeben. Solange kein Enter gedrückt wurde, gibt die Funktion nichts zurück (dann entsprechend nur das 1. Zeichen der Eingabe).
Benutzeravatar
darktrym
User
Beiträge: 680
Registriert: Freitag 24. April 2009, 09:26

Beitragvon darktrym » Freitag 24. April 2009, 10:40

Gibt es eine Möglichkeit den Code so zu schreiben, das er unter Windows andere Befehle interpretiert als unter unixähnlichen Systemen?
Wenn ich z.B. das Modul curses einbinde unter Windows, bekomme ich eine Fehlermeldung unter BSD wird sicherlich der Windows Kram Schwierigkeiten bereiten! Und zwei Versionen zu pflegen, will ich auch nicht.
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Beitragvon numerix » Freitag 24. April 2009, 10:51

darktrym hat geschrieben:Gibt es eine Möglichkeit den Code so zu schreiben, das er unter Windows andere Befehle interpretiert als unter unixähnlichen Systemen?


Sicher. Schau dir z.B. mal das platform-Modul an.
Benutzeravatar
b.esser-wisser
User
Beiträge: 272
Registriert: Freitag 20. Februar 2009, 14:21
Wohnort: Bundeshauptstadt B.

Beitragvon b.esser-wisser » Freitag 24. April 2009, 10:58

HerrHagen hat geschrieben:@b.esser-wisser: sys.stdin.read(1) ist nicht das gleiche wie getch, da man die Eingabe abschließen muss! Mann kann also auch nacheinander 10 Zeichen eingeben. Solange kein Enter gedrückt wurde, gibt die Funktion nichts zurück (dann entsprechend nur das 1. Zeichen der Eingabe).

Hast natürlich recht, getch() scheint der einzige Weg um den (Zeilen-)Puffer des 'Terminals' zu sein ('os.fdopen(0,"r",1)' funktioniert auch nicht).

@darktrym:
Du kannst z.B. auch "ImportError" abfangen und was anderes importieren.

sry, Jörg
Benutzeravatar
darktrym
User
Beiträge: 680
Registriert: Freitag 24. April 2009, 09:26

Beitragvon darktrym » Freitag 24. April 2009, 13:17

Code: Alles auswählen

try:
   import curses
except ImportError:
   print("no curses")


Ich verstehe nicht, wieso das nicht funktioniert.

Edit: Fehlalarm, Verzeihung.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
[url=https://bitbucket.org/amoibos]Bitbucket[/url], [url=https://github.com/amoibos/]Github[/url]
jerch
User
Beiträge: 1622
Registriert: Mittwoch 4. März 2009, 14:19

Beitragvon jerch » Freitag 24. April 2009, 13:38

Falls jmd. interessiert, warum das stdin.read() nicht so ohne weiteres funktioniert, hier gabs schonmal einen Thread dazu.
Benutzeravatar
darktrym
User
Beiträge: 680
Registriert: Freitag 24. April 2009, 09:26

Beitragvon darktrym » Freitag 24. April 2009, 18:56

Der Code für den Windows Teil funktioniert(getestet) soweit. Es wäre nett, wenn ihr mir noch ein paar Ratschläge geben könntet, welche Sachen man mit Python Bordmitteln eleganter lösen kann. Ich komme von den Java/C Denkmuster nicht ganz los.

Code: Alles auswählen

#Copyright (c) 2009, DarkTrym aka D.Oe.
#
#Permission to use, copy, modify, and/or distribute this software for any purpose with or
#without fee is hereby granted, provided that the above copyright notice and this permission
#notice appear in all copies.
#
#THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
#REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
#AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
#INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
#LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
#OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
#PERFORMANCE OF THIS SOFTWARE.

#!/usr/bin/env python

#brainfuck interpreter
#version 0.1
import sys
import platform

#open file, if it exists
try:
   file=open(sys.argv[1],"r")
except IndexError:
   print("wrong syntax: no source file")
   exit(-1)
   
#checking  keypressing libs
try:
   import curses
   stdscr = curses.initscr()
except ImportError:
   import msvcrt
   
isWindows=False
if platform.system()=="Windows":
   isWindows=True
   
#list in which the bainfuck code is stored
code=[]

#read the input file
#filter the symbols,check syntax and store it
balance=0
while True:
   line = file.read()
   if line == "":
      break
   for char in line:
      if char == '+' or char == '-' \
      or char == '>' or char == '<' \
      or char == '.' or char == ',':
         code.append(char)
      elif char == '[':
         balance+=1
         code.append(char)
      elif char == ']':
         balance-=1
         if balance < 0:
            print("more brackets are closed than opened")
            exit(-2)
         code.append(char)
         
if balance != 0:
   print("too much opened brackets")
   exit(-3)
      
codesize=len(code)
#create interpreter memory 256*256 bytes with zeros
memory=[]
for i in range(256**2):
   memory.append(0)

#store bracket level
stack=[]
#create jump table
jmptable={}
for i in range(codesize) :
   if code[i]=='[':
      stack.append(i)
   elif code[i]==']':
      source=stack.pop()
      jmptable.setdefault(int(source), int(i))
      jmptable.setdefault(int(i), int(source))

#memory pointer
memmarker=0
#codepointer
codemarker=0

#interpret brainfuck code
while codemarker != codesize:
   if code[codemarker] == '+':
      if memory[memmarker] < 255:
         memory[memmarker]+=1
   elif code[codemarker] == '-':
      if memory[memmarker] > 0:
         memory[memmarker]-=1
   elif code[codemarker] == '>':
      if memmarker < 256**2-1:
         memmarker+=1
   elif code[codemarker] == '<':
      if memmarker > 0:
         memmarker-=1
   elif code[codemarker] == '[':
      if memory[memmarker]==0:
         codemarker=jmptable[codemarker]
   elif code[codemarker] == ']':
      if memory[memmarker]!=0:
         codemarker=jmptable[codemarker]
   elif code[codemarker] == '.':
      if isWindows:
         sys.stdout.write(chr(memory[memmarker]))
      else:   #for all systems, that use curses
         stdscr.addch(chr(memory[memmarker]))
   elif code[codemarker] == ',':
      if isWindows:
         memory[memmarker]=msvcrt.getch()
      else: #for all systems, that use curses
         memory[memmarker]=stdscr.getch()
         
   codemarker+=1
   
if not isWindows:      
   stdscr.endwin()


[code=py]

Ein kleines Testprogramm als Benchmark.

99 Bottles of Beer in Urban Mueller's BrainF*** (The actual
name is impolite)

by Ben Olmstead

ANSI C interpreter available on the internet; due to
constraints in comments the address below needs to have the
stuff in parenthesis replaced with the appropriate symbol:

http://www(dot)cats(dash)eye(dot)com/cet/soft/lang/bf/

Believe it or not this language is indeed Turing complete!
Combines the speed of BASIC with the ease of INTERCAL and
the readability of an IOCCC entry!

>+++++++++[<+++++++++++>-]<[>[-]>[-]<<[>+>+<<-]>>[<<+>>-]>>>
[-]<<<+++++++++<[>>>+<<[>+>[-]<<-]>[<+>-]>[<<++++++++++>>>+<
-]<<-<-]+++++++++>[<->-]>>+>[<[-]<<+>>>-]>[-]+<<[>+>-<<-]<<<
[>>+>+<<<-]>>>[<<<+>>>-]>[<+>-]<<-[>[-]<[-]]>>+<[>[-]<-]<+++
+++++[<++++++<++++++>>-]>>>[>+>+<<-]>>[<<+>>-]<[<<<<<.>>>>>-
]<<<<<<.>>[-]>[-]++++[<++++++++>-]<.>++++[<++++++++>-]<++.>+
++++[<+++++++++>-]<.><+++++..--------.-------.>>[>>+>+<<<-]>
>>[<<<+>>>-]<[<<<<++++++++++++++.>>>>-]<<<<[-]>++++[<+++++++
+>-]<.>+++++++++[<+++++++++>-]<--.---------.>+++++++[<------
---->-]<.>++++++[<+++++++++++>-]<.+++..+++++++++++++.>++++++
++[<---------->-]<--.>+++++++++[<+++++++++>-]<--.-.>++++++++
[<---------->-]<++.>++++++++[<++++++++++>-]<++++.-----------
-.---.>+++++++[<---------->-]<+.>++++++++[<+++++++++++>-]<-.
>++[<----------->-]<.+++++++++++..>+++++++++[<---------->-]<
-----.---.>>>[>+>+<<-]>>[<<+>>-]<[<<<<<.>>>>>-]<<<<<<.>>>+++
+[<++++++>-]<--.>++++[<++++++++>-]<++.>+++++[<+++++++++>-]<.
><+++++..--------.-------.>>[>>+>+<<<-]>>>[<<<+>>>-]<[<<<<++
++++++++++++.>>>>-]<<<<[-]>++++[<++++++++>-]<.>+++++++++[<++
+++++++>-]<--.---------.>+++++++[<---------->-]<.>++++++[<++
+++++++++>-]<.+++..+++++++++++++.>++++++++++[<---------->-]<
-.---.>+++++++[<++++++++++>-]<++++.+++++++++++++.++++++++++.
------.>+++++++[<---------->-]<+.>++++++++[<++++++++++>-]<-.
-.---------.>+++++++[<---------->-]<+.>+++++++[<++++++++++>-
]<--.+++++++++++.++++++++.---------.>++++++++[<---------->-]
<++.>+++++[<+++++++++++++>-]<.+++++++++++++.----------.>++++
+++[<---------->-]<++.>++++++++[<++++++++++>-]<.>+++[<----->
-]<.>+++[<++++++>-]<..>+++++++++[<--------->-]<--.>+++++++[<
++++++++++>-]<+++.+++++++++++.>++++++++[<----------->-]<++++
.>+++++[<+++++++++++++>-]<.>+++[<++++++>-]<-.---.++++++.----
---.----------.>++++++++[<----------->-]<+.---.[-]<<<->[-]>[
-]<<[>+>+<<-]>>[<<+>>-]>>>[-]<<<+++++++++<[>>>+<<[>+>[-]<<-]
>[<+>-]>[<<++++++++++>>>+<-]<<-<-]+++++++++>[<->-]>>+>[<[-]<
<+>>>-]>[-]+<<[>+>-<<-]<<<[>>+>+<<<-]>>>[<<<+>>>-]<>>[<+>-]<
<-[>[-]<[-]]>>+<[>[-]<-]<++++++++[<++++++<++++++>>-]>>>[>+>+
<<-]>>[<<+>>-]<[<<<<<.>>>>>-]<<<<<<.>>[-]>[-]++++[<++++++++>
-]<.>++++[<++++++++>-]<++.>+++++[<+++++++++>-]<.><+++++..---
-----.-------.>>[>>+>+<<<-]>>>[<<<+>>>-]<[<<<<++++++++++++++
.>>>>-]<<<<[-]>++++[<++++++++>-]<.>+++++++++[<+++++++++>-]<-
-.---------.>+++++++[<---------->-]<.>++++++[<+++++++++++>-]
<.+++..+++++++++++++.>++++++++[<---------->-]<--.>+++++++++[
<+++++++++>-]<--.-.>++++++++[<---------->-]<++.>++++++++[<++
++++++++>-]<++++.------------.---.>+++++++[<---------->-]<+.
>++++++++[<+++++++++++>-]<-.>++[<----------->-]<.+++++++++++
..>+++++++++[<---------->-]<-----.---.+++.---.[-]<<<]
@
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
[url=https://bitbucket.org/amoibos]Bitbucket[/url], [url=https://github.com/amoibos/]Github[/url]
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Beitragvon str1442 » Freitag 24. April 2009, 19:53

Zunächst einmal solltest du dir PEP8 ansehen (siehe im Wiki des Forums, da gibt es eine Übersetzung).

Statt einen isWindows "Control flag" zu setzen, solltest du einfach ein Einheitliches Interface anbieten. Du benutzt doch sowieso nur getch. Also kannst du auch sowas schreiben:

Code: Alles auswählen

try:
    import curses as _curses
    stdscr = _cursed.initscr()
except ImportError:
    import msvcrt as stdscr

...

stdscr.getch() # Funktioniert auf jedem System wo eine der beiden Libraries vorhanden ist


Dann hast du schonmal an der Stelle kein "Control Coupling" mehr.

Du solltest Dateien nicht "file" nennen, das ist die Builtin Klasse für Dateien (du bekommst ein Exemplar der Klasse mit open()). Du kannst in Python jede Funktion mit einem eigenen Namen überschreiben, es ist aber oft nicht anzuraten. Kann zu witzigen Fehlern führen.

file.read() liest die Datei komplett in den Speicher. Die Schleife wird an der Stelle also doch sowieso nur einmal durchlaufen, danach liefert read "" zurück, was dann zu einem break führt. Stattdessen kannst du einfach über jede Zeile der Datei mit "for line in file:" iterieren. Da du die Datei komplett einliest, ist line als Name auch falsch.

Die ganzen BrainF Symbole kannst du doch in einen Tupel packen und dann mit "char in symbols" arbeiten. Kannst du die if balance < 0 Abfrage an der Stelle des Codes entfernen und zu der Abfrage nach "balance != 0" packen? Dann kannst du

Code: Alles auswählen

brackets_balance = {"[": 1, "]": -1}
if char in symbols:
    ... # kein code.append hier
elif char in brackets_balance: # Sucht nach einem Key in brackets_balance
    balance += brackets_balance[char]
else:
    raise BrainFuckSyntaxError(...) # Subklasse von SyntaxError oder von Exception
code.append(char)


Die brackets_balance kannst du wahlweise nochmal einzeln als Tupel für "brackets" schreiben oder so belassen, aber die ganzen Symbole (auch brackets) sollten globale Konstanten sein.
Benutzeravatar
darktrym
User
Beiträge: 680
Registriert: Freitag 24. April 2009, 09:26

Beitragvon darktrym » Freitag 24. April 2009, 21:16

Dank dir. Ich habe noch ein paar Stellen verbessert, wie empfohlen.

Die jetztige Version sieht dann so aus.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
[url=https://bitbucket.org/amoibos]Bitbucket[/url], [url=https://github.com/amoibos/]Github[/url]
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

Beitragvon HerrHagen » Freitag 24. April 2009, 22:26

Tolle Sache, so ein Brainfuck-Interpreter. :-)

Hier noch ein paar kleine Hinweise:
Statt:

Code: Alles auswählen

memory=[]
for i in range(256**2):
        memory.append(0)

kannst du auch einfach folgendes schreiben:

Code: Alles auswählen

memory=[0]*(256**2)

Wenn du schon für die Eingabe ein einheitliches Inteface hast, warum dann nicht auch für die Ausgabe (Funktionen sind normale Objekte)?

Code: Alles auswählen

putchar = sys.stdout.write if isWindows else stdscr.addch

Wenn du überprüfen willst ob ein Zeichen in einer Reihe von Zeichen drin ist, brauchst du kein set/liste/... erstellen. Mit einem String ist das ebenso möglich. So wirds ein wenig übersichlicher:

Code: Alles auswählen

# brainfucksymbols={'+','-','>','<','.',',','[',']'}
brainfucksymbols="+-><.,[]"
...
if char in brainfucksymbols:
    ...

Wenn man über eine Liste iteriert und sowohl die Werte als auch den Index braucht, schreibt man gerne:
[code=]for i, char in enumerate(code) :
if char == '[':
stack.append(i)
elif char == ']':
...[/code]
Du verwendest tabs statt den standartmäßigen 4 Leerzeichen. Das solltest du unbedingt ändern um nicht Konflikte bei gepasteten Code, etc. zu geraten. Die Python-Leute sind ja generell ziemlich erpicht auf Codeformatierungen. Deswegen sei dir auch von mir nochmal PEP8 ans herz gelegt.

MFG HerrHagen
lunar

Beitragvon lunar » Freitag 24. April 2009, 23:51

HerrHagen hat geschrieben:Wenn du überprüfen willst ob ein Zeichen in einer Reihe von Zeichen drin ist, brauchst du kein set/liste/... erstellen. Mit einem String ist das ebenso möglich. So wirds ein wenig übersichlicher:

Code: Alles auswählen

# brainfucksymbols={'+','-','>','<','.',',','[',']'}
brainfucksymbols="+-><.,[]"
...
if char in brainfucksymbols:
    ...

Auf Zeichenketten hat der "in"-Operator lineare, auf Mengen dagegen lediglich konstante Laufzeit. Bei kleinen Zeichenmenge fällt das natürlich nicht ins Gewicht, im Allgemeinen aber sollte man Zeichenketten aber nicht als Mengen missbrauchen. Die Übersicht kann man trotzdem wahren, wenn man das will:

Code: Alles auswählen

brainfucksymbols=set('+-><.,[]')

Ich persönlich halte die Mengennotation von Python 3 für ausreichend übersichtlich.

Außerdem würde ich die if-elif-else-Kaskade des Interpreters durch ein Wörterbuch mit Callback-Funktionen ersetzen.

Leerzeichen nach dem Kommentarzeichen würden die Lesbarkeit der Kommentare erhöhen. Und es heißt "too many opened brackets" ...
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

Beitragvon HerrHagen » Samstag 25. April 2009, 10:27

@lunar: Da haste natürlich recht. Den Aspekt hat ich irgendwie vergessen. :oops:
Ich finde die neue set Syntax irgwendwie nicht so gelungen. Die geschweiften Klammern erinnern mich doch irgendwie zu sehr an dict's. Naja, vielleicht ist das aber nur ne Gewöhnungssache und man setzt sie aufgrund der integrierteren Syntax häufiger ein. Der Geschwindigkeitsvorteil ist auf jeden Fall da:

Code: Alles auswählen

In [20]: txt = "qwertzuiopasdfghjklyxcvbnm"
In [21]: %timeit "m" in txt
1000000 loops, best of 3: 241 ns per loop
In [22]: txt = set("qwertzuiopasdfghjklyxcvbnm")
In [23]: %timeit "m" in txt
1000000 loops, best of 3: 187 ns per loop
lunar

Beitragvon lunar » Samstag 25. April 2009, 12:02

HerrHagen hat geschrieben:Ich finde die neue set Syntax irgwendwie nicht so gelungen.

Und was wäre die Alternative gewesen? Geschweifte Klammern sind eben die mathematische Notation für Mengen, und Wörterbücher sind letztlich auch nur Mengen von Schlüssel-Wert-Paaren. Die Umsetzung ist allenfalls etwas inkonsistent, da "{}" keine leere Menge, sondern ein leeres Wörterbuch erzeugt, aber das war bei Python schon immer so, und ist daher eigentlich kein Kritikpunkt.

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder