Konvertieren von int in Bytes in Python 3

176

Ich habe versucht, dieses Byte-Objekt in Python 3 zu erstellen:

b'3\r\n'

Also versuchte ich das Offensichtliche (für mich) und fand ein seltsames Verhalten:

>>> bytes(3) + b'\r\n'
b'\x00\x00\x00\r\n'

Anscheinend:

>>> bytes(10)
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

Ich konnte keine Hinweise darauf sehen, warum die Bytekonvertierung auf diese Weise funktioniert, wenn ich die Dokumentation lese. In dieser Python-Ausgabe wurden jedoch einige Überraschungsmeldungen zum Hinzufügen formatzu Bytes gefunden (siehe auch Formatierung von Python 3-Bytes ):

http://bugs.python.org/issue3982

Dies interagiert noch schlechter mit Kuriositäten wie Bytes (int), die jetzt Nullen zurückgeben

und:

Es wäre für mich viel bequemer, wenn Bytes (int) die ASCIIfication dieses Int zurückgeben würden; Aber ehrlich gesagt wäre sogar ein Fehler besser als dieses Verhalten. (Wenn ich dieses Verhalten wollte - das ich nie habe -, wäre es lieber eine Klassenmethode, die wie "bytes.zeroes (n)" aufgerufen wird.)

Kann mir jemand erklären, woher dieses Verhalten kommt?

Astrojuanlu
quelle
1
im Zusammenhang mit dem Titel:3 .to_bytes
jfs
2
Aus Ihrer Frage geht nicht hervor, ob Sie den ganzzahligen Wert 3 oder den Wert des ASCII-Zeichens für die Nummer drei (ganzzahliger Wert 51) wünschen. Das erste ist Bytes ([3]) == b '\ x03'. Letzteres ist Bytes ([ord ('3')]) == b'3 '.
Florida

Antworten:

176

So wurde es entworfen - und es ist sinnvoll, weil Sie normalerweise byteseine iterable anstelle einer einzelnen Ganzzahl aufrufen würden :

>>> bytes([3])
b'\x03'

Die Dokumente geben dies an , ebenso wie die Dokumentzeichenfolge für bytes:

 >>> help(bytes)
 ...
 bytes(int) -> bytes object of size given by the parameter initialized with null bytes
Tim Pietzcker
quelle
25
Beachten Sie, dass das oben genannte nur mit Python 3 funktioniert. In Python 2 bytesist nur ein Alias ​​für str, was bedeutet, dass bytes([3])Sie erhalten '[3]'.
Botchniaque
8
Beachten Sie in Python 3, dass dies bytes([n])nur für int n von 0 bis 255 funktioniert. Für alles andere wird es ausgelöst ValueError.
Acumenus
8
@ABB: Nicht wirklich überraschend, da ein Byte nur Werte zwischen 0 und 255 speichern kann.
Tim Pietzcker
7
Es sollte auch beachtet werden, dass dies bytes([3])immer noch anders ist als das, was das OP wollte - nämlich der Bytewert, der zum Codieren der Ziffer "3" in ASCII verwendet wird, d. H. bytes([51]), was b'3'nicht ist b'\x03'.
Lenz
2
bytes(500)erstellt einen Bytestring mit len ​​== 500. Es wird kein Bytestring erstellt, der die Ganzzahl 500 codiert. Und ich stimme zu, dass bytes([500])dies nicht funktionieren kann, weshalb dies auch die falsche Antwort ist. Wahrscheinlich ist die richtige Antwort int.to_bytes()für Versionen> = 3.1.
weberc2
197

Ab Python 3.2 können Sie tun

>>> (1024).to_bytes(2, byteorder='big')
b'\x04\x00'

https://docs.python.org/3/library/stdtypes.html#int.to_bytes

def int_to_bytes(x: int) -> bytes:
    return x.to_bytes((x.bit_length() + 7) // 8, 'big')

def int_from_bytes(xbytes: bytes) -> int:
    return int.from_bytes(xbytes, 'big')

Dementsprechend x == int_from_bytes(int_to_bytes(x)). Beachten Sie, dass diese Codierung nur für vorzeichenlose (nicht negative) Ganzzahlen funktioniert.

brunsgaard
quelle
4
Diese Antwort ist zwar gut, funktioniert jedoch nur für vorzeichenlose (nicht negative) Ganzzahlen. Ich habe es angepasst, schreibe eine Antwort, die auch für vorzeichenbehaftete ganze Zahlen funktioniert.
Acumenus
1
Das hilft nicht , wenn man b"3"davon kommt 3, wie die Frage stellt. (Es wird geben b"\x03".)
gsnedders
40

Sie können das Paket der Struktur verwenden :

In [11]: struct.pack(">I", 1)
Out[11]: '\x00\x00\x00\x01'

Das ">" ist die Bytereihenfolge (Big-Endian) und das "I" ist das Formatzeichen . Sie können also genau sein, wenn Sie etwas anderes tun möchten:

In [12]: struct.pack("<H", 1)
Out[12]: '\x01\x00'

In [13]: struct.pack("B", 1)
Out[13]: '\x01'

Dies funktioniert sowohl für Python 2 als auch für Python 3 gleich .

Hinweis: Die inverse Operation (Bytes bis int) kann mit dem Entpacken durchgeführt werden .

Andy Hayden
quelle
2
@AndyHayden Um zu klären, da ein struct eine Standardgröße unabhängig von dem Eingang, I, H, und BArbeit , bis , 2**k - 1wobei k 32, 16, bzw. 8. Bei größeren Eingängen erhöhen sie sich struct.error.
Acumenus
Vermutlich herabgestimmt, da es die Frage nicht beantwortet: Das OP möchte wissen, wie es generiert wird b'3\r\n', dh eine Byte-Zeichenfolge, die das ASCII-Zeichen "3" enthält, nicht das ASCII-Zeichen "\ x03"
Dave Jones
1
@ DaveJones Was lässt dich denken, dass das OP das will? Die akzeptierte Antwort wird zurückgegeben \x03, und die Lösung, wenn Sie nur möchten, b'3'ist trivial. Der von ABB angeführte Grund ist viel plausibler ... oder zumindest verständlich.
Andy Hayden
@ DaveJones Der Grund, warum ich diese Antwort hinzugefügt habe, war, dass Google Sie bei der Suche hierher führt, um genau dies zu tun. Deshalb ist es hier.
Andy Hayden
4
Dies funktioniert nicht nur in 2 und 3 gleich, sondern ist auch schneller als die Methoden bytes([x])und (x).to_bytes()in Python 3.5. Das war unerwartet.
Mark Ransom
25

Python 3.5+ führt die% -Interpolation ( printfFormatierung im Stil) für Bytes ein :

>>> b'%d\r\n' % 3
b'3\r\n'

Siehe PEP 0461 - Hinzufügen der% -Formatierung zu Bytes und Bytearray .

In früheren Versionen könnten Sie verwenden strund .encode('ascii')das Ergebnis:

>>> s = '%d\r\n' % 3
>>> s.encode('ascii')
b'3\r\n'

Hinweis: Es unterscheidet sich von dem, was int.to_bytesproduziert :

>>> n = 3
>>> n.to_bytes((n.bit_length() + 7) // 8, 'big') or b'\0'
b'\x03'
>>> b'3' == b'\x33' != '\x03'
True
jfs
quelle
11

Die Dokumentation sagt:

bytes(int) -> bytes object of size given by the parameter
              initialized with null bytes

Die Sequenz:

b'3\r\n'

Es ist das Zeichen '3' (Dezimalzahl 51), das Zeichen '\ r' (13) und '\ n' (10).

Daher würde der Weg es als solches behandeln, zum Beispiel:

>>> bytes([51, 13, 10])
b'3\r\n'

>>> bytes('3', 'utf8') + b'\r\n'
b'3\r\n'

>>> n = 3
>>> bytes(str(n), 'ascii') + b'\r\n'
b'3\r\n'

Getestet unter IPython 1.1.0 und Python 3.2.3

Schcriher
quelle
1
Am Ende tat ich bytes(str(n), 'ascii') + b'\r\n'oder str(n).encode('ascii') + b'\r\n'. Vielen Dank! :)
Astrojuanlu
1
@ Juanlu001, auch "{}\r\n".format(n).encode()ich glaube nicht, dass es Schaden gibt, wenn die Standard-utf8-Codierung verwendet wird
John La Rooy
6

Die ASCIIfication von 3 ist "\x33"nicht "\x03"!

Dafür tut Python, aber für str(3)Bytes wäre es völlig falsch, da sie als Arrays von Binärdaten betrachtet und nicht als Zeichenfolgen missbraucht werden sollten.

Der einfachste Weg, um das zu erreichen, was Sie wollen bytes((3,)), ist besser, als bytes([3])weil das Initialisieren einer Liste viel teurer ist. Verwenden Sie daher niemals Listen, wenn Sie Tupel verwenden können. Sie können größere Ganzzahlen mithilfe von konvertieren int.to_bytes(3, "little").

Das Initialisieren von Bytes mit einer bestimmten Länge ist sinnvoll und am nützlichsten, da sie häufig zum Erstellen eines Puffertyps verwendet werden, für den Sie Speicher mit einer bestimmten zugewiesenen Größe benötigen. Ich benutze dies oft, wenn ich Arrays initialisiere oder eine Datei durch Schreiben von Nullen erweitere.

Bachsau
quelle
1
Bei dieser Antwort gibt es mehrere Probleme: (a) Die Escape-Notation von b'3'ist b'\x33', nicht b'\x32'. (b) (3)ist kein Tupel - Sie müssen ein Komma hinzufügen. (c) Das Szenario der Initialisierung einer Sequenz mit Nullen gilt nicht für bytesObjekte, da diese unveränderlich sind (es ist jedoch für bytearrays sinnvoll ).
Lenz
Vielen Dank für Ihren Kommentar. Ich habe diese beiden offensichtlichen Fehler behoben. Im Falle von bytesund bytearraydenke ich, dass es hauptsächlich um Konsistenz geht. Es ist aber auch nützlich, wenn Sie einige Nullen in einen Puffer oder eine Datei verschieben möchten. In diesem Fall wird sie nur als Datenquelle verwendet.
Bachsau
5

int(einschließlich Python2 long) kann bytesmit der folgenden Funktion konvertiert werden:

import codecs

def int2bytes(i):
    hex_value = '{0:x}'.format(i)
    # make length of hex_value a multiple of two
    hex_value = '0' * (len(hex_value) % 2) + hex_value
    return codecs.decode(hex_value, 'hex_codec')

Die umgekehrte Konvertierung kann von einem anderen durchgeführt werden:

import codecs
import six  # should be installed via 'pip install six'

long = six.integer_types[-1]

def bytes2int(b):
    return long(codecs.encode(b, 'hex_codec'), 16)

Beide Funktionen funktionieren sowohl auf Python2 als auch auf Python3.

renskiy
quelle
'hex_value ='% x '% i' funktioniert unter Python 3.4 nicht. Sie erhalten einen TypeError, sodass Sie stattdessen hex () verwenden müssen.
bjmc
@bjmc durch str.format ersetzt. Dies sollte unter Python 2.6+ funktionieren.
Renskiy
Danke, @renskiy. Möglicherweise möchten Sie 'hex_codec' anstelle von 'hex' verwenden, da der Alias ​​'hex' anscheinend nicht in allen Python 3-Versionen verfügbar ist. Siehe stackoverflow.com/a/12917604/845210
bjmc
@bjmc behoben. Danke
Renskiy
Dies schlägt bei negativen ganzen Zahlen auf Python 3.6
Berserker
4

Ich war neugierig auf die Leistung verschiedener Methoden für einen einzelnen Int im Bereich [0, 255] und entschied mich daher, einige Timing-Tests durchzuführen.

Basierend auf den folgenden Zeitpunkten und dem allgemeinen Trend, den ich beim Ausprobieren vieler verschiedener Werte und Konfigurationen beobachtet habe, struct.packscheint es am schnellsten zu sein, gefolgt von int.to_bytesund bytes, wobei str.encode(nicht überraschend) am langsamsten ist. Beachten Sie, dass die Ergebnisse etwas mehr Variationen zeigen als dargestellt, int.to_bytesund bytesmanchmal das Geschwindigkeitsranking während des Tests geändert haben, aberstruct.pack eindeutig die schnellsten sind.

Ergebnisse in CPython 3.7 unter Windows:

Testing with 63:
bytes_: 100000 loops, best of 5: 3.3 usec per loop
to_bytes: 100000 loops, best of 5: 2.72 usec per loop
struct_pack: 100000 loops, best of 5: 2.32 usec per loop
chr_encode: 50000 loops, best of 5: 3.66 usec per loop

Testmodul (benannt int_to_byte.py):

"""Functions for converting a single int to a bytes object with that int's value."""

import random
import shlex
import struct
import timeit

def bytes_(i):
    """From Tim Pietzcker's answer:
    https://stackoverflow.com/a/21017834/8117067
    """
    return bytes([i])

def to_bytes(i):
    """From brunsgaard's answer:
    https://stackoverflow.com/a/30375198/8117067
    """
    return i.to_bytes(1, byteorder='big')

def struct_pack(i):
    """From Andy Hayden's answer:
    https://stackoverflow.com/a/26920966/8117067
    """
    return struct.pack('B', i)

# Originally, jfs's answer was considered for testing,
# but the result is not identical to the other methods
# https://stackoverflow.com/a/31761722/8117067

def chr_encode(i):
    """Another method, from Quuxplusone's answer here:
    https://codereview.stackexchange.com/a/210789/140921

    Similar to g10guang's answer:
    https://stackoverflow.com/a/51558790/8117067
    """
    return chr(i).encode('latin1')

converters = [bytes_, to_bytes, struct_pack, chr_encode]

def one_byte_equality_test():
    """Test that results are identical for ints in the range [0, 255]."""
    for i in range(256):
        results = [c(i) for c in converters]
        # Test that all results are equal
        start = results[0]
        if any(start != b for b in results):
            raise ValueError(results)

def timing_tests(value=None):
    """Test each of the functions with a random int."""
    if value is None:
        # random.randint takes more time than int to byte conversion
        # so it can't be a part of the timeit call
        value = random.randint(0, 255)
    print(f'Testing with {value}:')
    for c in converters:
        print(f'{c.__name__}: ', end='')
        # Uses technique borrowed from https://stackoverflow.com/q/19062202/8117067
        timeit.main(args=shlex.split(
            f"-s 'from int_to_byte import {c.__name__}; value = {value}' " +
            f"'{c.__name__}(value)'"
        ))
Graham
quelle
1
@ABB Wie in meinem ersten Satz erwähnt, messe ich dies nur für ein einzelnes int im Bereich [0, 255]. Ich nehme an, mit "falschem Indikator" meinen Sie, dass meine Messungen nicht allgemein genug waren, um in die meisten Situationen zu passen? Oder war meine Messmethode schlecht? In letzterem Fall wäre ich interessiert zu hören, was Sie zu sagen haben, aber in letzterem Fall habe ich nie behauptet, dass meine Messungen für alle Anwendungsfälle generisch sind. Für meine (vielleicht Nischen-) Situation beschäftige ich mich nur mit Ints im Bereich [0, 255], und das ist das Publikum, das ich mit dieser Antwort ansprechen wollte. War meine Antwort unklar? Ich kann es aus Gründen der Klarheit bearbeiten ...
Graham
1
Was ist mit der Technik, nur eine vorberechnete Codierung für den Bereich zu indizieren? Die Vorberechnung würde nicht dem Timing unterliegen, sondern nur der Indizierung.
Acumenus
@ABB Das ist eine gute Idee. Das klingt so, als wäre es schneller als alles andere. Ich werde ein Timing machen und es zu dieser Antwort hinzufügen, wenn ich etwas Zeit habe.
Graham
3
Wenn Sie das Bytes-from-iterable-Ding wirklich zeitlich festlegen möchten, sollten Sie es bytes((i,))anstelle von verwenden, bytes([i])da die Liste komplexer ist, mehr Speicher benötigt und die Initialisierung lange dauert. In diesem Fall umsonst.
Bachsau
4

Obwohl die vorherige Antwort von brunsgaard eine effiziente Codierung ist, funktioniert sie nur für vorzeichenlose Ganzzahlen. Dieser baut darauf auf, um sowohl für vorzeichenbehaftete als auch für vorzeichenlose Ganzzahlen zu arbeiten.

def int_to_bytes(i: int, *, signed: bool = False) -> bytes:
    length = ((i + ((i * signed) < 0)).bit_length() + 7 + signed) // 8
    return i.to_bytes(length, byteorder='big', signed=signed)

def bytes_to_int(b: bytes, *, signed: bool = False) -> int:
    return int.from_bytes(b, byteorder='big', signed=signed)

# Test unsigned:
for i in range(1025):
    assert i == bytes_to_int(int_to_bytes(i))

# Test signed:
for i in range(-1024, 1025):
    assert i == bytes_to_int(int_to_bytes(i, signed=True), signed=True)

Wird für den Encoder (i + ((i * signed) < 0)).bit_length()verwendet, anstatt nur, i.bit_length()weil letzterer zu einer ineffizienten Codierung von -128, -32768 usw. führt.


Gutschrift: CervEd für die Behebung einer geringfügigen Ineffizienz.

Scharfsinn
quelle
int_to_bytes(-128, signed=True) == (-128).to_bytes(1, byteorder="big", signed=True)istFalse
CervEd
Sie verwenden nicht die Länge 2, sondern berechnen die Bitlänge der vorzeichenbehafteten Ganzzahl, addieren 7 und dann 1, wenn es sich um eine vorzeichenbehaftete Ganzzahl handelt. Schließlich konvertieren Sie das in die Länge in Bytes. Dies führt zu unerwarteten Ergebnissen für -128, -32768usw.
Cerved
Lassen Sie uns diese Diskussion im Chat fortsetzen .
CervEd
So beheben Sie das (i+(signed*i<0)).bit_length()
Problem
3

Das Verhalten beruht auf der Tatsache, dass in Python vor Version 3 bytesnur ein Alias ​​für war str. In Python3.x bytesist eine unveränderliche Version von bytearray- völlig neuer Typ, nicht abwärtskompatibel.

verrückt
quelle
3

Aus Bytes docs :

Dementsprechend werden Konstruktorargumente wie für bytearray () interpretiert.

Dann aus Bytearray-Dokumenten :

Der optionale Quellparameter kann verwendet werden, um das Array auf verschiedene Arten zu initialisieren:

  • Wenn es sich um eine Ganzzahl handelt, hat das Array diese Größe und wird mit Null-Bytes initialisiert.

Beachten Sie, dass sich das Verhalten von 2.x (wobei x> = 6) unterscheidet, wobei byteseinfach str:

>>> bytes is str
True

PEP 3112 :

Der 2,6-Str unterscheidet sich vom Byte-Typ von 3.0 in verschiedener Hinsicht. Vor allem ist der Konstruktor völlig anders.

alko
quelle
0

Einige Antworten funktionieren nicht mit großen Zahlen.

Konvertieren Sie eine Ganzzahl in die Hex-Darstellung und konvertieren Sie sie dann in Bytes:

def int_to_bytes(number):
    hrepr = hex(number).replace('0x', '')
    if len(hrepr) % 2 == 1:
        hrepr = '0' + hrepr
    return bytes.fromhex(hrepr)

Ergebnis:

>>> int_to_bytes(2**256 - 1)
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
Max Malysh
quelle
1
"Alle anderen Methoden funktionieren nicht mit großen Zahlen." Das stimmt nicht, int.to_bytesfunktioniert mit jeder ganzen Zahl.
juanpa.arrivillaga
@ juanpa.arrivillaga ja, mein schlechtes. Ich habe meine Antwort bearbeitet.
Max Malysh
-1

Wenn die Frage ist, wie eine Ganzzahl selbst (nicht ihr String-Äquivalent) in Bytes konvertiert werden kann, lautet die robuste Antwort meiner Meinung nach:

>>> i = 5
>>> i.to_bytes(2, 'big')
b'\x00\x05'
>>> int.from_bytes(i.to_bytes(2, 'big'), byteorder='big')
5

Weitere Informationen zu diesen Methoden finden Sie hier:

  1. https://docs.python.org/3.8/library/stdtypes.html#int.to_bytes
  2. https://docs.python.org/3.8/library/stdtypes.html#int.from_bytes
Nilashish C.
quelle
1
Wie unterscheidet sich dies von der Antwort von brunsgaard, die vor 5 Jahren veröffentlicht wurde und derzeit die am höchsten bewertete Antwort ist?
Arthur Tacca