Najlepszy sposób na konwersję ciągu na bajty w Pythonie 3?

858

Wydaje się, że istnieją dwa różne sposoby konwersji ciągu na bajty, jak widać w odpowiedziach na TypeError: „str” nie obsługuje interfejsu bufora

Która z tych metod byłaby lepsza, czy bardziej Pythonic? Czy to tylko kwestia osobistych preferencji?

b = bytes(mystring, 'utf-8')

b = mystring.encode('utf-8')
Mark Ransom
źródło
42
Używanie kodowania / dekodowania jest bardziej powszechne i być może jaśniejsze.
Lennart Regebro
11
@LennartRegebro Odrzucam. Nawet jeśli jest to bardziej powszechne, czytając „bytes ()” wiem, co robi, a encode () nie sprawia, że ​​czuję, że koduje do bajtów.
m3nda
2
@ erm3nda Co jest dobrym powodem, aby go używać, dopóki tak nie będzie , to jesteś o krok bliżej Unicode zen.
Lennart Regebro
3
@ LennartRegebro Czuję się wystarczająco dobrze, aby po prostu użyć bytes(item, "utf8"), ponieważ jawne jest lepsze niż niejawne, więc ... str.encode( )domyślnie cicho na bajty, co czyni cię bardziej Unicode-zen, ale mniej wyraźnym-Zen. Również „wspólne” nie jest terminem, który lubię stosować. Ponadto, bytes(item, "utf8")jest bardziej podobny do str()i b"string"notacji. Przepraszam, jeśli tak bardzo rozumiem twoje powody. Dziękuję Ci.
m3nda
4
@ erm3nda, jeśli przeczytasz zaakceptowaną odpowiedź, zobaczysz, że encode()nie dzwoni bytes(), to na odwrót. Oczywiście nie jest to od razu oczywiste i dlatego zadałem pytanie.
Mark Ransom

Odpowiedzi:

570

Jeśli spojrzysz na dokumenty bytes, wskazuje to na bytearray:

bytearray ([source [, kodowanie [, błędy]]])

Zwraca nową tablicę bajtów. Typ bytearray to zmienna sekwencja liczb całkowitych z zakresu 0 <= x <256. Posiada większość zwykłych metod sekwencji zmiennych, opisanych w Zmiennych typach sekwencji, a także większość metod typu bajtów, patrz Bajty i Metody tablicy bajtów.

Opcjonalnego parametru źródłowego można użyć do zainicjowania tablicy na kilka różnych sposobów:

Jeśli jest to ciąg, musisz również podać parametry kodowania (i opcjonalnie błędy); bytearray () następnie konwertuje ciąg na bajty za pomocą str.encode ().

Jeśli jest liczbą całkowitą, tablica będzie miała ten rozmiar i zostanie zainicjowana bajtami zerowymi.

Jeśli jest to obiekt zgodny z interfejsem bufora, do zainicjowania tablicy bajtów zostanie użyty bufor tylko do odczytu obiektu.

Jeśli jest iterowalna, musi być iterowalna z liczb całkowitych z zakresu 0 <= x <256, które są używane jako początkowa zawartość tablicy.

Bez argumentu tworzona jest tablica o rozmiarze 0.

bytesMoże więc zrobić znacznie więcej niż tylko kodowanie łańcucha. Pythonic pozwala na wywołanie konstruktora z dowolnym typem parametru źródłowego, który ma sens.

Myślę, że w przypadku kodowania ciągu some_string.encode(encoding)jest to bardziej Pythonic niż używanie konstruktora, ponieważ jest to najbardziej samok dokumentujące - „weź ten ciąg i zakoduj go za pomocą tego kodowania” jest wyraźniejsze niż bytes(some_string, encoding)- nie ma wyraźnego czasownika podczas używania konstruktor.

Edycja: sprawdziłem źródło Python. Jeśli przekażesz ciąg Unicode, aby bytesużyć CPython, wywołuje PyUnicode_AsEncodedString , który jest implementacją encode; więc po prostu omijasz poziom pośredni, jeśli nazywasz encodesiebie.

Zobacz także komentarz Serdalisa - unicode_string.encode(encoding)jest również bardziej Pythoniczny, ponieważ jego odwrotność jest byte_string.decode(encoding)i symetria jest ładna.

agf
źródło
73
+1 za dobry argument i cytaty z dokumentów Pythona. Świetnie unicode_string.encode(encoding)pasuje również, bytearray.decode(encoding)gdy chcesz odzyskać swój ciąg.
Serdalis,
6
bytearrayjest używany, gdy potrzebujesz mutowalnego obiektu. Nie potrzebujesz go do prostych konwersji strbytes.
hamstergene,
8
@EugeneHomyakov To nie ma nic wspólnego z bytearraywyjątkiem tego, że doktorzy za bytesnie podają szczegółów, po prostu mówią „to jest niezmienna wersja bytearray”, więc muszę z tego zacytować.
agf
1
Tylko przestroga od Pythona w pigułce na temat bytes: Unikaj używania typu bajtów jako funkcji z argumentem liczby całkowitej. W wersji 2 zwraca liczbę całkowitą przekonwertowaną na łańcuch (bajtowy), ponieważ bajty to alias dla str, natomiast w wersji 3 zwraca bajtowanie zawierające podaną liczbę znaków null. Na przykład zamiast bajtów wyrażenia v3 (6) użyj równoważnego b '\ x00' * 6, który płynnie działa w ten sam sposób w każdej wersji.
holdenweb,
2
Tylko uwaga, że ​​jeśli próbujesz przekonwertować dane binarne na ciąg, najprawdopodobniej będziesz musiał użyć czegoś takiego, byte_string.decode('latin-1')ponieważ utf-8nie obejmuje całego zakresu od 0x00 do 0xFF (0-255), sprawdź dokumentację python dla więcej informacji.
iggy12345,
346

To łatwiejsze niż się wydaje:

my_str = "hello world"
my_str_as_bytes = str.encode(my_str)
type(my_str_as_bytes) # ensure it is byte representation
my_decoded_str = my_str_as_bytes.decode()
type(my_decoded_str) # ensure it is string representation
hasanatkazmi
źródło
37
Wie, jak to zrobić, po prostu pyta, która droga jest lepsza. Przeczytaj ponownie pytanie.
agf
30
FYI: str.decode (bajty) nie działało dla mnie (Python 3.3.3 powiedział, że „typ obiektu” str ”nie ma atrybutu„ dekodowania ””) Zamiast tego użyłem bytes.decode ()
Mike
6
@ Mike: użyj obj.method()składni zamiast cls.method(obj)składni, tj. Użyj bytestring = unicode_text.encode(encoding)i unicode_text = bytestring.decode(encoding).
jfs
2
... tj. niepotrzebnie robisz niezwiązaną metodę, a następnie nazywasz ją przekazując selfjako pierwszy argument
Antti Haapala
2
@KolobCanyon Pytanie już pokazuje, jak to zrobić - wywołanie encodejako metoda powiązana z łańcuchem. Ta odpowiedź sugeruje, że zamiast tego należy wywołać metodę niezwiązaną i przekazać jej ciąg. To jedyna nowa informacja w odpowiedzi i jest zła.
abarnert
144

Absolutnie najlepszym sposobem jest ani o 2, ale 3rd. Pierwszy parametr ma wartość domyślną od czasu Python 3.0. Zatem najlepszym sposobem jestencode 'utf-8'

b = mystring.encode()

Będzie to również szybsze, ponieważ domyślny argument nie powoduje ciągu "utf-8"w kodzie C, ale NULL, co jest znacznie szybsze do sprawdzenia!

Oto kilka terminów:

In [1]: %timeit -r 10 'abc'.encode('utf-8')
The slowest run took 38.07 times longer than the fastest. 
This could mean that an intermediate result is being cached.
10000000 loops, best of 10: 183 ns per loop

In [2]: %timeit -r 10 'abc'.encode()
The slowest run took 27.34 times longer than the fastest. 
This could mean that an intermediate result is being cached.
10000000 loops, best of 10: 137 ns per loop

Mimo ostrzeżenia czasy były bardzo stabilne po wielokrotnych przejazdach - odchylenie wynosiło tylko ~ 2 procent.


Używanie encode()bez argumentu nie jest zgodne z Python 2, ponieważ w Python 2 domyślnym kodowaniem znaków jest ASCII .

>>> 'äöä'.encode()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)
Antti Haapala
źródło
2
Jest tu tylko spora różnica, ponieważ (a) ciąg znaków jest czystym ASCII, co oznacza, że ​​pamięć wewnętrzna jest już wersją UTF-8, więc wyszukiwanie kodera-dekodera to prawie jedyny koszt, jaki się z tym wiąże, i (b) ciąg jest niewielki , więc nawet gdybyś musiał zakodować, nie miałoby to większego znaczenia. Spróbuj z, powiedzmy '\u00012345'*10000. Oba biorą 28,8 na moim laptopie; dodatkowe 50ns jest prawdopodobnie utracone w wyniku błędu zaokrąglania. Oczywiście jest to dość ekstremalny przykład - ale 'abc'jest równie ekstremalny w przeciwnym kierunku.
abarnert
@abarnert jest prawdą, ale nawet wtedy nie ma powodu, aby przekazać argument jako ciąg.
Antti Haapala
Zgodnie z tym domyślne argumenty są zawsze „absolutnie najlepszym sposobem” na robienie rzeczy, prawda? Tego rodzaju analiza prędkości wydawałaby się prawdopodobną przesadą, gdyby chodziło o omawianie kodu C. W tłumaczonym języku pozostawia mnie bez słowa.
Hmijail opłakuje odejście