Über Byte-String iterieren und einzelne Bytes erhalten

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
Hellstorm
User
Beiträge: 231
Registriert: Samstag 22. Juni 2013, 15:01

Hallo,

wenn ich mit einer for-Schleife über ein b"Beispiel" iteriere, bekomme ich immer Integer zurück.
Beispiel:

Code: Alles auswählen

In [61]: for zeichen in b"Beispiel":                                  
   ....:     print("Typ: {}, Wert: {}".format(type(zeichen), zeichen))
   ....:                                                              
Typ: <class 'int'>, Wert: 66                                          
Typ: <class 'int'>, Wert: 101                                         
Typ: <class 'int'>, Wert: 105                                         
Typ: <class 'int'>, Wert: 115                                         
Typ: <class 'int'>, Wert: 112                                         
Typ: <class 'int'>, Wert: 105                                         
Typ: <class 'int'>, Wert: 101                                         
Typ: <class 'int'>, Wert: 108                                         
Geht das eventuell auch irgendwie, dass ich dort wieder einzelne Bytes herausbekomme? Also b"B", b"e" usw.?

Im Moment habe ich es nur geschafft, den Integer mit chr() wieder in ein Zeichen zu wandeln und dann wieder mit .encode("ascii") in ein Byte. Das ist aber viel zu umständlich und scheint mir auch vor Fehlerquellen nur zu strotzen.

Hat da jemand einen Tipp? Danke :)
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo,

"int" wandelt alles Mögliche in Integer um, "float" alles mögliche in Floats. Vielleicht wandelt "bytes" ja Integer in Byteobjekte um ;-)
Das Leben ist wie ein Tennisball.
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

EyDu hat geschrieben:Vielleicht wandelt "bytes" ja Integer in Byteobjekte um ;-)
Tut es aber nicht. Auszug aus ``help(bytes)``:
bytes(int) -> bytes object of size given by the parameter initialized with null bytes
Bei der Frage hier geht es offenbar darum, einzelne Zahlwerte in Byteobjekte umzuwandeln. Das funktioniert im Falle von `bytes()` nur, wenn mehrere Zahlen vorliegend (ein Iterable von Integern), aber halt nicht für einzelne Werte. Bei letzterem tut `bytes()` - wie zitiert - etwas völlig anderes.

Man könnte aber einen kleinen Trick anwenden, sofern man die Bytes einzeln direkt aus der Bytefolge ziehen möchte:

Code: Alles auswählen

>>> b'foo'[0:1]
b'f'
Möglicherweise geht es noch etwas sauberer und trotzdem kompakter als der bereits vorgestellte Weg über `chr()` und `.encode()`. Da muss ich jedoch passen.

Die Frage ist natürlich auch: Warum will man das? Was spricht gegen die Umwandlung in ein String-Objekt? In welchem Zusammenhang wird das benötigt?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

snafu hat geschrieben:
EyDu hat geschrieben:Vielleicht wandelt "bytes" ja Integer in Byteobjekte um ;-)
Tut es aber nicht. Auszug aus ``help(bytes)``:
bytes(int) -> bytes object of size given by the parameter initialized with null bytes
Man könnte natürlich auch einfach weiterlesen:
Construct an immutable array of bytes from:
- an iterable yielding integers in range(256)
Das Leben ist wie ein Tennisball.
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ja, aus einem *Iterable* von Integern, aber eben nicht aus einem einzelnen Integer. Ich hatte die Frage so verstanden, dass es nicht um eine Folge von Zahlen geht, sondern um die Anwendung auf eine einzelne Zahl. Daher mein Einwand.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Ich habe die Frage schon genau so verstanden wie du. Ich gehe aber davon aus, dass die Hilfe zu einer Funktion gelesen wird. Da ist der Schritt zu ``bytes()`` dann nun wirklich nicht so weit.
Das Leben ist wie ein Tennisball.
Hellstorm
User
Beiträge: 231
Registriert: Samstag 22. Juni 2013, 15:01

Ich will auf einem an einen Raspberry Pi angeschlossenem Display (HD44780) Text ausgeben.

Ich hab jetzt eine Funktion geschrieben (alles noch etwas unfertig und noch nicht in echt getestet), die folgendermaßen aussieht:

Code: Alles auswählen

from collections import OrderedDict
def write_byte(data, data_bus):
    wiring = OrderedDict([
        (0x01, data_bus[0]),
        (0x02, data_bus[1]),
        (0x04, data_bus[2]),
        (0x08, data_bus[3]),
        (0x10, data_bus[4]),
        (0x20, data_bus[5]),
        (0x40, data_bus[6]),
        (0x80, data_bus[7])
    ])

    if isinstance(data, int) and data < 0 or data > 0xFF:
        raise ValueError("Der Wert muss zwischen 0 und 255 (FF) liegen.")

    if isinstance(data, bytes):
        if len(data) > 1:
            raise TypeError("Nur ein einzelnes Byte erlaubt")
        data = ord(data)

    for entry in wiring:
        if data & entry == entry:
            #GPIO.output(wiring[entry], True)
            print("Bit {}".format(wiring[entry]))
Ich fände es halt von der „Logik“ her besser, dem ganzen ein Byte-Objekt übergeben zu können, eben weil ja ein Byte gesendet werden soll. Mit einem Integer geht es zwar auch, aber dann müsste man z.B. erst prüfen, ob es nicht negativ ist, ob es nicht größer als 255 ist usw.

Um einen String anzuzeigen würde ich halt folgendes machen:

Code: Alles auswählen

def write_string(text, data_bus):
    bytes_string = text.encode("ascii")
    for byte in bytes_string:
        print("Zeichen {} ({}):".format(chr(byte), byte))
        write_byte(byte, data_bus)

def main():
    data_bus = (1, 2, 3, 4, 5, 6, 7, 8)
    text = "Hallo"
    write_string(text, data_bus)
D.h., der Text wird erst einmal in die entsprechende Kodierung umgewandelt (ich hab mich fürs erste mal auf ASCII beschränkt, muss da aber die anderen Zeichen noch implementieren) und dann kriegt man ja ein bytes-String heraus. Das ist ja auch genau das, was ich will. An das Display sendet man ja Bytes und keinen Text. Danach muss ich eben jedes Byte einzeln an das LCD schicken. Aber wenn man über einen bytes-String iteriert, bekommt man Integer heraus.

Naja, ich habe das jetzt so gemacht, dass write_byte() auch Integer von 0–255 akzeptiert. Das scheint mir am einfachsten zu sein, auch wenn es mich nicht ganz zufrieden stellt. Das Programm funktioniert jetzt auch erst einmal im Trockendurchlauf (d.h. er druckt immer auf die Konsole, welches Bit aktiviert wird). Ich werde das jetzt als nächstes mit 8 einzelnen LEDs ausprobieren und gucken, ob immer die entsprechenden LEDs leuchten.
BlackJack

@Hellstorm: Also ich würde da einfach Zahlen übergeben. Und die ggf. auf den Wertebereich prüfen. Oder einfach verwenden. Aus einzelnen Bytewerten jetzt `bytes`-Objekte zu machen, man beachte die Mehrzahl im Namen des Typs, halte ich für unsinnig. Ich meine Du holst Zahlen aus einem `bytes`-Objekt um dann die einzelnen Zahlen wieder in `bytes`-Objekte zu stecken und die Funktion wandelt so ein einzelnes Byte in einem `bytes`-Objekt dann wieder in eine Zahl um.

Was soll denn das `OrderedDict` bewirken? Wird da irgendwo über Schlüssel auf Werte zugegriffen, also *wirklich* und nicht nur überflüssigerweise der Reihe nach über alle Schlüssel? Denn diese Folge von Paaren kann man doch einfach berechnen statt das da so umständlich einzutippen.

Das kann letztendlich auf das hier zusammen schrumpfen:

Code: Alles auswählen

def write_byte(value, data_bus):
    for i, pin_number in enumerate(data_bus):
        bit = bool(value & (1 << i))
        GPIO.output(pin_number, bit)

def write_string(text, data_bus):
    for byte_value in text.encode('ascii'):
        write_byte(byte_value, data_bus)
Eventuell könnte man sich auch überlegen das wegen `data_bus` in einem Datentyp zu kapseln.
Hellstorm
User
Beiträge: 231
Registriert: Samstag 22. Juni 2013, 15:01

Hallo Blackjack,

danke für deine Hilfe. Das mit den Integer-Werten werde ich wohl letztens Ende so umsetzen, scheint mir doch auch einfacher und sauberer.

Das OrderedDict hbe ich vor allem genutzt, um einfach (im Sinne von gut lesbar) darauf zugreifen zu können. Ob das jetzt wirklich sortiert sein muss, wusste ich nicht. Vorher hatte ich ein normales Dict, aber dann hat er bei meinem Probedurchlauf z.B. folgendes angezeigt:
Bit 2
Bit 3
Bit 1

Das fand ich etwas unsortiert, deswegen hatte ich das in ein OrderedDict getan. Ich weiß auch nicht, ob bei dem LCD jetzt die einzelnen Kontakte in einer ansteigenden Reihenfolge angemacht werden müssen oder nicht.

Deine Lösung ist natürlich wesentlich kürzer und eleganter, aber für mich ist das noch etwas zu unverständlich. Ehrlich gesagt habe ich für diesen Teil mal gerade erst den &-Operator gelernt. Jetzt noch ein << da drin... :D Ich werde da mal die Logik versuchen zu verstehen, aber ich glaube, ich lasse es erst einmal bei meiner Lösung. Das könnte ich ja später noch ändern.
Soll ja auch meinem Lernen dienen, da kann ich nicht ganz unverständliche Sachen reinmachen. Ich merke es mir mal.


Was genau meinst du jetzt mit Datentyp? Ich verstehe ehrlich gesagt wirklich nicht, wann man jetzt eine Klasse nehmen soll oder nicht. Manchmal nutze ich eine, dann wird mir gesagt, ich soll keine nehmen, jetzt nehme ich keine, da sollte ich dann eher eine nehmen :D

Ich persönlich fände eine Klasse in dem Sinne etwas besser, da man dann folgendes machen könnte:

Code: Alles auswählen

lcd_control = LcdControl(data_bus=(1, 2, 3, 4, 5, 6, 7, 8), columns=16, rows=2, use_4_pins=False)
lcd_control.write("Hallo")
Sprich, den ganzen Konfigurationskram beim Initialisieren der Klasse zu übergeben und danach beliebig einfach Text an das LCD senden. Allerdings bin ich mir halt nicht sicher, ob eine Klasse wirklich Sinn ergibt...
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hellstorm hat geschrieben:Das OrderedDict hbe ich vor allem genutzt, um einfach (im Sinne von gut lesbar) darauf zugreifen zu können. Ob das jetzt wirklich sortiert sein muss, wusste ich nicht. Vorher hatte ich ein normales Dict, aber dann hat er bei meinem Probedurchlauf z.B. folgendes angezeigt:
Bit 2
Bit 3
Bit 1

Das fand ich etwas unsortiert, deswegen hatte ich das in ein OrderedDict getan. Ich weiß auch nicht, ob bei dem LCD jetzt die einzelnen Kontakte in einer ansteigenden Reihenfolge angemacht werden müssen oder nicht.

Deine Lösung ist natürlich wesentlich kürzer und eleganter, aber für mich ist das noch etwas zu unverständlich. Ehrlich gesagt habe ich für diesen Teil mal gerade erst den &-Operator gelernt. Jetzt noch ein << da drin... :D Ich werde da mal die Logik versuchen zu verstehen, aber ich glaube, ich lasse es erst einmal bei meiner Lösung.
Nee, dass musst du genau jetzt verstehen. Wenn du schon so dicht an der Hardware arbeitst, kommst du da so oder so nicht rum. In deinem Fall ist das Dictionary einfach nur eine Notlösung, weil du das Muster nicht erkannt hast:
0x01 -> erstes Bit gesetzt
0x02 -> zweites Bit gesetzt
0x04 -> drittes Bit gesetzt
0x08 -> viertes Bit gesetzt
0x10 -> fünftes Bit gesetzt
...
Lass dir die Werte einfach mal als binäre Darstellung ausgeben, dann erkennst du das Muster selbst. Mit dem Ausdruck ``bool(value & (1 << i))`` von BlackJack wird das einfach nur berechnet. Spiele mit den Bit-Operatoren mal rum, lasse dir die Zwischenergebniss ausgeben und, ganz wichtig, schaue dir die binären Darstellungen der Zahlen an. Dann verstehst du auch ganz schnell was da passiert.
Das Leben ist wie ein Tennisball.
Hellstorm
User
Beiträge: 231
Registriert: Samstag 22. Juni 2013, 15:01

Also das mit der binären Darstellung hatte ich schon verstanden.

Beispielsweise ist der Buchstabe „a“ in ASCII ja 0x61, was binär 0110 0001 ist. Da ich ja einen 8-Bit-Datenbus habe (das LCD kann man zwar auch mit 4 Bits ansteuern, das verschiebe ich aber mal auf später), muss ich hier die Bits 1, 6 und 7 auf High stellen.

Mit dem &-Operator bekomme ich ja dann genau das Bit zurück, welches beides mal 1 ist.

0110 0001 & 0000 0001 = 0000 0001
0110 0001 & 0010 0000 = 0010 0000

So dass ich genau in dem Schleifendurchgang das Bit auf High setzen kann (bzw. eben nicht, wenn es nicht passt). Daher bin ich das einfach der Reihenfolge nach für alle 8 Bits durchgegangen.

Mein Problem war jetzt nur dass ich das << noch nie gesehen habe. Ich wollte für mich selber eben noch eine kleine Übersicht haben, wie man das jetzt verstehen könnte. Das von Blackjack werde ich mir natürlich auch mal genauer anschauen und das auch letztendlich einbauen.

Aber naja, du hast schon recht, dann mache ich das lieber jetzt. :D Das Problem ist natürlich nur, dass es für den individiuellen Lernfortschritt auch manchmal sein muss, die eigenen Lösungen noch etwas weiterzubenutzen, selbst wenn sie nicht 100%ig perfekt sind. Wenn man einfach nur eine komplett andere Lösung gesagt bekommt, dann hat man ja keine eigene „Verbindung” zu der Lösung und versteht sie deshalb ja nicht so gut.

Edit:

Ich hab jetzt versucht, Blackjacks Lösung vom aus dem Gedächtnis her noch einmal nachzubauen (und hab daher die Lösung wohl auch verstanden, danke!), und bin da jetzt zu folgender Lösung gekommen:

Code: Alles auswählen

def write_byte(value, data_bus):
    for i, pin_number in enumerate(data_bus):
        if value & (1 << i):
            GPIO.output(pin_number, True)
Ich hab hier dieses bit=bool(...) weggelassen und das einfach nur auf Wahr oder Falsch überprüft. Geht das nicht auch?
BlackJack

@Hellstorm: Man kann ja in der Dokumentation nachlesen was der ``<<``-Operator auf ganzen Zahlen macht. Selbst wenn man das nicht macht, sondern sich einfach nur mal die Zwischenergebnisse anschaut, lässt sich das sogar erraten, und zwar IMHO deutlich einfacher als bei ``&``:

Code: Alles auswählen

In [2]: '{0:08b}'.format(1 << 0)
Out[2]: '00000001'

In [3]: '{0:08b}'.format(1 << 1)
Out[3]: '00000010'

In [4]: '{0:08b}'.format(1 << 2)
Out[4]: '00000100'

In [5]: '{0:08b}'.format(1 << 3)
Out[5]: '00001000'

In [6]: '{0:08b}'.format(1 << 4)
Out[6]: '00010000'

In [7]: '{0:08b}'.format(1 << 5)
Out[7]: '00100000'

In [8]: '{0:08b}'.format(1 << 6)
Out[8]: '01000000'

In [9]: '{0:08b}'.format(1 << 7)
Out[9]: '10000000'
Was das finden in der Dokumentation angeht, da bietet sich der Index an, der oben rechts auf fast jeder Seite der Python-Doku verlinkt ist. Da dann „Symbols” und in der Liste findet man dann die ganzen Operatoren die aus Symbolen bestehen und Links zu der Stelle wo die dokumentiert sind. Und das führt zu einer Tabelle mit den ganzen Bitoperationen und in der Zeile für ``x << n`` steht als Erklärung „x shifted left by n bits”.

Die fünf bzw. sechs Operatoren in der Tabelle sollte man IMHO alle kennen wenn man auf Bitebene Werte manipuliert. Das sind sozusagen die Grundrechenarten für Bitoperationen, so wie man +, -, *, /, und ** kennen sollte wenn man mit Zahlen rechnen möchte.
Hellstorm
User
Beiträge: 231
Registriert: Samstag 22. Juni 2013, 15:01

Ja, ich hab mir das jetzt durchgelesen. Danke :)
Antworten