Int'yi baytlara dönüştürme in Python 3


178

Python 3'te bu bayt nesnesini oluşturmaya çalışıyordum:

b'3\r\n'

bu yüzden bariz denedim (benim için) ve garip bir davranış buldum:

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

Görünüşe göre:

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

Bayt dönüşümünün belgeleri bu şekilde neden çalıştığına dair herhangi bir işaret göremedim. Ancak, formatbaytlara ekleme hakkında bu Python sayısında bazı sürpriz mesajlar buldum (ayrıca bkz. Python 3 bayt biçimlendirme ):

http://bugs.python.org/issue3982

Bu, şimdi sıfır veren bayt (int) gibi tuhaflıklarla daha da kötü etkileşime giriyor

ve:

Baytlar (int) bu int'in ASCII onaylamasını döndürürse benim için çok daha uygun olurdu; ama dürüst olmak gerekirse, bir hata bile bu davranıştan daha iyi olurdu. (Eğer hiç sahip olmadığım bu davranışı isteseydim, "bytes.zeroes (n)" gibi çağrılan bir sınıf yöntemi olmayı tercih ederim.)

Birisi bana bu davranışın nereden geldiğini açıklayabilir mi?


1
başlık ile ilgili:3 .to_bytes
jfs

2
Tamsayı değeri 3'ü veya üç sayısını temsil eden ASCII karakterinin değerini (tamsayı değeri 51) istiyorsanız sorunuz net değildir. Birincisi bayt ([3]) == b '\ x03'. İkincisi bayttır ([ord ('3')]) == b'3 '.
florisla

Yanıtlar:


177

Bu şekilde tasarlandı - ve mantıklı çünkü genellikle bytestek bir tamsayı yerine yinelenebilir bir çağrı yapmanız gerekir :

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

Docs bu devlet , hem de docstring'ini için bytes:

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

25
Yukarıdakilerin sadece python 3 ile çalıştığına dikkat edin. Python 2'de bytessadece bir takma addır str, bu da bytes([3])size verir '[3]'.
botchniaque

9
Python 3'te, bytes([n])yalnızca 0 ile 255 arası int n için çalıştığına dikkat edin ValueError.
Acumenus

8
@ABB: Bir bayt yalnızca 0 ile 255 arasındaki değerleri depolayabildiği için şaşırtıcı değil.
Tim Pietzcker

7
Yine de bytes([3])OP'nin istediklerinden farklı olduğuna dikkat edilmelidir - ASCII'de "3" basamağını kodlamak için kullanılan bayt değeri, yani. bytes([51]), Ki bu b'3'değil b'\x03'.
lenz

2
bytes(500)w / len == 500 ile bir bytestring oluşturur. 500 tamsayısını kodlayan bir bytestring oluşturmaz bytes([500]). Muhtemelen doğru cevap int.to_bytes()> = 3.1 versiyonları içindir.
weberc2

200

Python 3.2'den şunları yapabilirsiniz

>>> (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')

Buna göre x == int_from_bytes(int_to_bytes(x)). Bu kodlamanın yalnızca işaretsiz (negatif olmayan) tamsayılar için çalıştığını unutmayın.


4
Bu cevap iyi olsa da, yalnızca işaretsiz (negatif olmayan) tamsayılar için çalışır. İmzalı tamsayılar için de işe yarayan bir cevap yazdım .
Acumenus

1
Sorudan da anlaşılacağı gibi, bu b"3"durumdan kurtulmaya yardımcı olmuyor 3. ( b"\x03"
Verecek

41

Yapının paketini kullanabilirsiniz :

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

">" Bayt sırasıdır (big-endian) ve "I" biçim karakteridir . Yani başka bir şey yapmak istiyorsanız spesifik olabilirsiniz:

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

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

Bu hem piton 2 ve üzerinde aynı şekilde çalışır piton 3 .

Not: ters işlem (bayttan int'e) ambalajından çıkartarak yapılabilir .


2
Bir yapı bakılmaksızın girdinin standart boyuta sahip olduğundan, netleştirmek için @AndyHayden, I, H, ve Bkadar iş 2**k - 1k sırasıyla 32, 16 ve 8 olduğu. Daha büyük girdiler için yükselirler struct.error.
Acumenus

Muhtemelen aşağıya oy verdi, çünkü soruya cevap vermedi: OP b'3\r\n', nasıl oluşturulacağını bilmek istiyor , yani ASCII karakterini değil "3" ASCII karakterini içeren bir bayt dizesi "\ x03"
Dave Jones

1
@DaveJones OP'nin ne istediğini size düşündüren nedir? Kabul edilen cevap geri döner \x03ve sadece isterseniz çözüm b'3'önemsizdir. ABB tarafından belirtilen neden çok daha mantıklı ... ya da en azından anlaşılabilir.
Andy Hayden

@DaveJones Ayrıca, bu yanıtı eklememin nedeni, Google'ın tam olarak bunu yapmak için sizi buraya götürmesiydi. İşte bu yüzden burada.
Andy Hayden

5
Bu sadece 2 ve 3'te aynı şekilde çalışmaz, aynı zamanda Python 3.5'teki bytes([x])ve (x).to_bytes()yöntemlerinden daha hızlıdır . Bu beklenmedikti.
Mark Ransom


11

Belgeler diyor ki:

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

Sekans:

b'3\r\n'

Karakter '3' (ondalık 51) '\ r' (13) ve '\ n' (10) karakteridir.

Bu nedenle, yol bu şekilde ele alınacaktır, örneğin:

>>> 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'

IPython 1.1.0 ve Python 3.2.3 üzerinde test edilmiştir


1
Sonunda bytes(str(n), 'ascii') + b'\r\n'veya str(n).encode('ascii') + b'\r\n'. Teşekkürler! :)
astrojuanlu

1
@ Juanlu001, ayrıca "{}\r\n".format(n).encode()varsayılan utf8 kodlaması kullanılarak yapılan herhangi bir zarar olduğunu düşünmüyorum
John La Rooy

6

3'ün ASCIIifikasyonu "\x33"değil "\x03"!

Python str(3)bunun için yapar, ancak ikili veriler dizileri olarak kabul edilmeleri ve dizeler olarak kötüye kullanılmamaları gerektiği için baytlar için tamamen yanlış olur.

İstediğinizi elde etmenin en kolay yolu bytes((3,)), bytes([3])bir listeyi başlatmak çok daha pahalı olduğu için daha iyidir , bu yüzden tuples kullanabildiğinizde asla listeleri kullanmayın. Düğmesini kullanarak daha büyük tam sayıları dönüştürebilirsiniz int.to_bytes(3, "little").

Belirli bir uzunluğa sahip baytları başlatmak mantıklıdır ve en faydalı olanıdır, çünkü bunlar genellikle verilen boyutta bir miktar belleğe ihtiyaç duyduğunuz bir tür tampon oluşturmak için kullanılır. Bunu genellikle dizileri başlatırken veya içine sıfır yazarak bazı dosyaları genişletirken kullanırım.


1
(A) kaçış notasyonu: Bu yanıt ile birkaç sorun vardır b'3'IS b'\x33'değil b'\x32'. (b) (3)bir demet değildir - virgül eklemeniz gerekir. (c) Sıfırları olan bir dizinin başlatılması senaryosu bytes, değişmez oldukları için nesneler için geçerli değildir (yine de bytearrays için anlamlıdır ).
lenz

Yorumun için teşekkürler. Bu iki açık hatayı düzelttim. Durumunda bytesve bytearray, bunu çoğunlukla tutarlılık meselesi olduğunu düşünüyorum. Ancak, bazı sıfırları bir arabellek veya dosyaya itmek istiyorsanız da yararlıdır, bu durumda yalnızca veri kaynağı olarak kullanılır.
Bachsau

5

int(Python2'ler dahil long) bytesaşağıdaki işlev kullanılarak dönüştürülebilir :

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')

Ters dönüşüm başka bir dönüşümle yapılabilir:

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)

Her iki işlev de Python2 ve Python3 üzerinde çalışır.


'hex_value ='% x '% i', Python 3.4 altında çalışmaz. Bir TypeError alırsınız, bunun yerine hex () kullanmanız gerekir.
bjmc

@bjmc str.format ile değiştirildi. Bu Python 2.6+ üzerinde çalışmalıdır.
renskiy

Teşekkürler @renskiy. 'Hex' yerine 'hex_codec' kullanmak isteyebilirsiniz çünkü 'hex' takma adı tüm Python 3 sürümlerinde mevcut görünmüyor gibi bkz. Stackoverflow.com/a/12917604/845210
bjmc

@bjmc düzeltildi. Teşekkürler
renskiy

Bu, python
3.6'daki

4

Aralıktaki tek bir int için çeşitli yöntemlerin performansını merak ettim [0, 255], bu yüzden bazı zamanlama testleri yapmaya karar verdim.

Aşağıdaki zamanlaması dayanarak ve birçok farklı değerler ve yapılandırmaları çalışırken gözlemlenen genel eğilimden, struct.packardından hızlı gibi görünüyor int.to_bytes, bytesve ile str.encode(şaşırtıcı olmayan) en yavaş olmak. Sonuçların, gösterilenden biraz daha fazla varyasyon gösterdiğini int.to_bytesve bytesbazen test sırasında anahtarlamalı hız sıralamasını gösterdiğini, ancakstruct.pack açıkça en hızlı olduğunu unutmayın.

Windows'ta CPython 3.7 sonuçları:

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

Test modülü (adlı 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)'"
        ))

1
@ABB İlk cümlemde belirtildiği gibi, bunu sadece aralıktaki tek bir int için ölçüyorum [0, 255]. Sanırım "yanlış gösterge" ile ölçümlerimin çoğu duruma uyacak kadar genel olmadığı anlamına mı geliyor? Yoksa ölçüm metodolojim zayıf mıydı? İkincisi, söyleyeceklerinizi duymak isterim, ancak birincisi, ölçümlerimin tüm kullanım durumları için genel olduğunu iddia etmedim. (Belki de niş) durumum için, sadece aralıktaki ints ile uğraşıyorum [0, 255]ve bu cevapla ele almayı amaçladığım izleyici. Cevabım belirsiz miydi? Açıklık için düzenleyebilirim ...
Graham

1
Aralık için önceden hesaplanmış bir kodlamayı endeksleme tekniği ne olacak? Ön hesaplama zamanlamaya tabi olmaz, sadece endeksleme yapılır.
Acumenus

@ABB Bu iyi bir fikir. Bu her şeyden daha hızlı olacak gibi görünüyor. Biraz zamanım olacak ve biraz zamanım olduğunda bu cevaba ekleyeceğim.
Graham

3
Gerçekten zaman bayt-den-iterable şey isterseniz, kullanmalısınız bytes((i,))yerine bytes([i]), liste daha karmaşık olduğu için daha fazla bellek kullanmak ve başlatmak için uzun zaman alır. Bu durumda, hiçbir şey için.
Bachsau

4

Brunsgaard'ın önceki cevabı etkili bir kodlama olsa da, sadece işaretsiz tamsayılar için çalışır. Bu, hem imzalı hem de imzasız tamsayılar için çalışacak şekilde oluşturulur.

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)

Kodlayıcı için, ikincisi -128, -32768 vb. Verimsiz bir kodlamaya yol açtığı için (i + ((i * signed) < 0)).bit_length()kullanılır i.bit_length().


Kredi: Küçük bir verimsizliği gidermek için CervEd.


int_to_bytes(-128, signed=True) == (-128).to_bytes(1, byteorder="big", signed=True)olduğuFalse
CervEd

2 uzunluğunu kullanmıyorsunuz, işaretli bir tamsayı ise 7 ve sonra 1 ekleyerek işaretli tamsayının bit uzunluğunu hesaplıyorsunuz. Sonunda bunu bayt cinsinden uzunluğa dönüştürürsünüz. Bu -128, -32768vb. İçin beklenmedik sonuçlar verir .
CervEd


Bunu nasıl düzeltirsiniz(i+(signed*i<0)).bit_length()
CervEd

3

Davranış, Python'da sürüm 3'ten bytesönce sadece bir takma ad olduğu gerçeğinden kaynaklanmaktadır str. Python3.x bytesdeğişmez bir sürümüdür bytearray- tamamen yeni tip, geriye dönük uyumlu değil.


3

Gönderen docs bayt :

Buna göre, yapıcı argümanları bytearray () için yorumlanır.

Sonra bytearray dokümanlarından :

İsteğe bağlı source parametresi, diziyi birkaç farklı şekilde başlatmak için kullanılabilir:

  • Bir tamsayı ise, dizi bu boyuta sahip olur ve boş baytlarla başlatılır.

Not 2.x farklıdır davranış, (x> = 6 olduğu) olduğu bytesbasitçe str:

>>> bytes is str
True

PEP 3112 :

2.6 str, 3.0 bayt tipinden çeşitli şekillerde farklıdır; en önemlisi, kurucu tamamen farklıdır.


0

Bazı cevaplar büyük sayılarla çalışmaz.

Tamsayıyı onaltılık temsile dönüştürün, ardından bayt'a dönüştürün:

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

Sonuç:

>>> 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'

1
"Diğer tüm yöntemler büyük sayılarla çalışmaz." Bu doğru değil, int.to_bytesherhangi bir tamsayı ile çalışır.
juanpa.arrivillaga

@ juanpa.arrivillaga evet, kötüyüm. Cevabımı düzenledim.
Max Malysh

-1

Soru nasıl bir tamsayının kendisini (dize eşdeğeri değil) bayt dönüştürmek ise, ben sağlam cevap olduğunu düşünüyorum:

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

Bu yöntemlerle ilgili daha fazla bilgiyi burada bulabilirsiniz:

  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

1
Bu, 5 yıl önce yayınlanan ve şu anda en yüksek oy alan cevap olan brunsgaard'ın cevabından nasıl farklı?
Arthur Tacca
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.