Zuweisung in Testbedingung einer Schleife

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.
Antworten
windner
User
Beiträge: 76
Registriert: Freitag 19. Oktober 2007, 11:25

In Python ist ja das Ergebnis einer Zuweisung unbestimmt. Ich muss deshalb öfter so etwas schreiben:

Code: Alles auswählen

s = stream.read(1)
while s != "":
    # mache etwas mit s
    s = stream.read(1)
Das gefällt mir aber nicht, weil die Zuweisung zweimal auftaucht. Wie macht man das besser? Sollte ich die next-Methode des Streams überschreiben, um nicht nur über Zeilen iterieren zu können? Oder gibt es da einen Trick, mit dem man das schreiben kann wie in C:

Code: Alles auswählen

while ((c=getchar()) != EOF)
    ;
Da fällt mir gerade auf, dass es auch schön wäre, Daten auf einen (file-artigen) stream zurückzulegen, so wie ungetchar(). Oder die Daten auf dem Stream zu lesen, ohne sie zu entfernen. Geht das?
BlackJack

Die Aussage das Ergebnis einer Zuweisung sei unbestimmt klingt IMHO etwas komisch. Zuweisungen sind Anweisungen und keine Ausdrücke und Anweisungen dürfen nicht in der Bedingung bei ``if`` und ``while`` stehen. Das wäre eine Erklärung die eher mit der Terminologie aus der Python-Doku harmoniert.

Die doppelte Zuweisung kann man ganz einfach loswerden:

Code: Alles auswählen

for s in iter(lambda: stream.read(1), ''):
    # Mache etwas mit `s`.
Wenn Du auch Zeichen in den Stream zurückschieben willst, musst Du wohl eine eigene Klasse dafür schreiben. Ungetestet:

Code: Alles auswählen

class CharacterStream(object):
    def __init__(self, file_obj):
        self.file_obj = file_obj
        self.pushed = None
    
    def __iter__(self):
        return self
    
    def next(self):
        if self.pushed is None:
            result = self.file_obj.read(1)
        else:
            result = self.pushed
            self.pushed = None
        if result:
            return result
        else:
            raise StopIteration()

    def push_back(self, character):
        if self.pushed is not None:
            # TODO: Bessere Ausnahme und besserer Text.  :-)
            raise Exception("Is' zu voll hier...")
        self.pushed = character
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Auch wenn man drum rumfuhrwerken kann nervt es trotzdem tierisch. birkenfeld und ich (eher mehr birkenfeld ^^) haben da mal einen Patch für Python3 geschrieben, der "while expr as f:" erlaubt: http://dev.pocoo.org/hg/sandbox/file/ti ... locks.diff

Keine Ahnung ob der auch mit den aktuellen Python3 Sources geht.
TUFKAB – the user formerly known as blackbird
windner
User
Beiträge: 76
Registriert: Freitag 19. Oktober 2007, 11:25

Ich habe jetzt eingehend darüber nachgedacht.

Dass man mit iter() über Funktionsergebnisse iterieren kann, habe ich jetzt gelernt, und finde ich sehr elegant. CharacterStream funktioniert bestens und ist leicht verständlich. Habe davon einen WordStraem abgeleitet, analog zu getword() nach K&R:

Code: Alles auswählen

class WordStream(CharacterStream):
    def __init__(self, file_obj, letters=string.letters+string.digits):
        self.letters = letters
        CharacterStream.__init__(self, file_obj)
    
    def next(self):
        word = c = CharacterStream.next(self)
        while c in self.letters:
            c = CharacterStream.next(self)
            if c in self.letters:
                word = word + c
            else:
                CharacterStream.push_back(self, c)
                break
        return word
Danke schön!

Nebenbei bemerkt wäre while a as b viel besser als die Variante mit iter(). Wenn ich bei zwei verschiedenen möglichen Rückgabewerten abbrechen will, zB "\r" oder "\n", bräuchte ich eine iter()-Funktion, der ich keinen Terminator, sondern eine Vergleichsfunktion mitgeben kann. Oder ich mach's so:

Code: Alles auswählen

__ans=None
def call(f, *args):
    global __ans
    __ans = f(*args)
    return __ans
def ans():
    global __ans
    return __ans

# mit iter() über sys.stdin.read könnte ich hier kein "or" anbringen:
while call(sys.stdin.read, 1) != '\n' or ans() != '\r':
    # mache etwas mit ans()
BlackJack

Iiih was ist das zweite für ein Beispiel? ``global``!?

Das sieht nach einem Fall für `itertools.takewhile()` aus.

Code: Alles auswählen

characters = iter(lambda: sys.stdin.read(1), '')
for character in takewhile(lambda c: c not in '\n\r', characters):
    print character
windner
User
Beiträge: 76
Registriert: Freitag 19. Oktober 2007, 11:25

Hab' heute endlich Zeit zum Lesen gehabt -- Danke! Die itertools muss man kennen.
Frank aka Ch3ck3r
User
Beiträge: 49
Registriert: Dienstag 13. November 2007, 21:56
Wohnort: Berlin
Kontaktdaten:

Ist es nicht einfacher die Schliefe etwas zu verändern?

Code: Alles auswählen

while True:
    s = stream.read(1)
    if s == "": break
    # mache etwas mit s
kostenlose TS2-Server für jeweils 31 Tage:
http://www.ts-onlyfree.de
BlackJack

Einfacher als was? Das hier ist ja nun auch nicht gerade kompliziert:

Code: Alles auswählen

for s in iter(lambda: stream.read(1), ''):
    # Tue etwas mit `s`.
Es hat den Vorteil, dass man in der ersten Zeile schon sieht worüber iteriert wird und das der Schleifenkörper völlig unabhängig davon ist wo die Buchstaben herkommen.

Man könnte das Objekt hinter dem ``in`` durch eine Zeichenkette, ein Tupel, eine Liste oder sonst irgendwas passendes ersetzen ohne das sich die Verarbeitung der Zeichen darum zu kümmern bräuchte. Es wäre zum Beispiel möglich das in eine Funktion zu stecken, die ein "iterable" über Zeichen entgegennimmt. Das ist viel generischer als eine Funktion die ein "file like" mit einer `read()`-Methode haben möchte.
Antworten