Jaka jest różnica między łańcuchem a łańcuchem bajtów?

209

Pracuję z biblioteką, która zwraca ciąg bajtów i muszę go przekonwertować na ciąg.

Chociaż nie jestem pewien, jaka jest różnica - jeśli w ogóle.

Sheldon
źródło

Odpowiedzi:

260

Zakładając, że Python 3 (w Pythonie 2 ta różnica jest nieco mniej dobrze zdefiniowana) - ciąg znaków jest sekwencją znaków, tj. Punktami kodowymi Unicode ; są to abstrakcyjne pojęcia i nie mogą być bezpośrednio przechowywane na dysku. Ciąg bajtów to sekwencja bajtów - rzeczy, które można przechowywać na dysku. Mapowanie między nimi jest kodowaniem - jest ich całkiem sporo (i nieskończenie wiele jest możliwych) - i musisz wiedzieć, które mają zastosowanie w danym przypadku, aby wykonać konwersję, ponieważ inne kodowanie może odwzorować te same bajty do innego ciągu:

>>> b'\xcf\x84o\xcf\x81\xce\xbdo\xcf\x82'.decode('utf-16')
'蓏콯캁澽苏'
>>> b'\xcf\x84o\xcf\x81\xce\xbdo\xcf\x82'.decode('utf-8')
'τoρνoς'

Kiedy już wiesz, którego użyć, możesz użyć .decode()metody ciągu bajtów, aby uzyskać z niego odpowiedni ciąg znaków, jak wyżej. Dla kompletności .encode()metoda ciągu znaków przebiega w drugą stronę:

>>> 'τoρνoς'.encode('utf-8')
b'\xcf\x84o\xcf\x81\xce\xbdo\xcf\x82'
lvc
źródło
7
Dla wyjaśnienia dla użytkowników Python 2: strtyp jest taki sam jak bytestyp; ta odpowiedź w sposób równoważny porównuje unicodetyp (nie istnieje w Pythonie 3) z strtypem.
craymichael,
3
@KshitijSaraogi, co również nie jest do końca prawdą; całe zdanie zostało zredagowane i jest trochę niefortunne. Reprezentacja w pamięci obiektów Python 3 strnie jest dostępna ani istotna ze strony Pythona; struktura danych jest tylko sekwencją punktów kodowych. Zgodnie z PEP 393 , dokładne kodowanie wewnętrzne to Latin-1, UCS2 lub UCS4, a reprezentacja utf-8 może być buforowana po pierwszym żądaniu, ale nawet kod C nie jest zależny od tych wewnętrznych szczegółów.
lvc
1
Jeśli nie można ich bezpośrednio zapisać na dysku, to w jaki sposób są przechowywane w pamięci?
z33k
2
@orety muszą być jakoś wewnętrznie zakodowane z tego właśnie powodu, ale to nie jest expos3s dla ciebie z kodu Pythona, podobnie jak nie musisz dbać o to, jak przechowywane są liczby zmiennoprzecinkowe.
lvc
1
@ChrisStryczyński zobacz komentarze powyżej - pewnie są one jakoś zapisane w pamięci , ale ta forma jest wyraźnie wyabstrahowana . Rzeczywiście, w dzisiejszych czasach może się zmieniać w trakcie trwania programu i różnić się między różnymi łańcuchami lub może być nawet więcej niż jeden (niektóre kodowania są buforowane), w zależności od zawartych w nich znaków - ale to jedyny czas, o który trzeba się martwić to znaczy, jeśli włamujesz się do implementacji samego typu łańcucha.
lvc
390

Jedyne, co komputer może przechowywać, to bajty.

Aby zapisać cokolwiek w komputerze, musisz najpierw go zakodować , tzn. Przekonwertować na bajty. Na przykład:

  • Jeśli chcesz przechowywać muzykę, trzeba najpierw zakodować go za pomocą MP3, WAVitd
  • Jeśli chcesz zapisać obraz, należy najpierw zakodować go za pomocą PNG, JPEGitd
  • Jeśli chcesz przechowywać tekst, należy najpierw zakodować go za pomocą ASCII, UTF-8itd

MP3, WAV, PNG, JPEG, ASCIII UTF-8przedstawiono przykłady kodowania . Kodowanie to format reprezentujący audio, obrazy, tekst itp. W bajtach.

W Pythonie ciąg bajtów jest po prostu: sekwencją bajtów. Nie jest czytelny dla człowieka. Pod maską wszystko musi zostać przekonwertowane na ciąg bajtów, zanim będzie można je zapisać na komputerze.

Z drugiej strony ciąg znaków, często nazywany po prostu „ciągiem”, jest ciągiem znaków. Jest czytelny dla człowieka. Ciąg znaków nie może być bezpośrednio zapisany na komputerze, musi być najpierw zakodowany (przekształcony w ciąg bajtów). Istnieje wiele kodowań, za pomocą których ciąg znaków można przekonwertować na ciąg bajtów, takich jak ASCIIi UTF-8.

'I am a string'.encode('ASCII')

Powyższy kod Python koduje ciąg 'I am a string'przy użyciu kodowania ASCII. Wynikiem powyższego kodu będzie ciąg bajtów. Jeśli go wydrukujesz, Python będzie go reprezentował jako b'I am a string'. Pamiętaj jednak, że ciągi bajtów nie są czytelne dla człowieka , po prostu Python dekoduje je ASCIIpodczas drukowania. W Pythonie ciąg bajtów jest reprezentowany przez a b, po którym następuje reprezentacja ciągu bajtów ASCII.

Łańcuch bajtów może zostać zdekodowany z powrotem na ciąg znaków, jeśli znasz kodowanie użyte do jego zakodowania.

b'I am a string'.decode('ASCII')

Powyższy kod zwróci oryginalny ciąg 'I am a string'.

Kodowanie i dekodowanie to operacje odwrotne. Wszystko musi zostać zakodowane, zanim będzie mogło zostać zapisane na dysk, i musi zostać odkodowane, zanim będzie mogło być odczytane przez człowieka.

Zenadix
źródło
59
Zenadix zasługuje na trochę uznania. Po kilku latach funkcjonowania w tym środowisku jest to pierwsze wyjaśnienie, które ze mną kliknęło. Mogę wytatuować go na drugim ramieniu (jedno ramię ma już „Absolutne minimum, każdy programista absolutnie, pozytywnie musi wiedzieć o Unicode i
zestawach
4
Absolutnie genialny. Jasne i łatwe do zrozumienia. Chciałbym jednak wspomnieć, że ten wiersz - „Jeśli go wydrukujesz, Python będzie go reprezentował jako b'I am string” „jest prawdziwy dla Python3, podobnie jak dla bajtów Python2 i str są tym samym.
SRC,
5
Przyznam wam tę nagrodę za zaoferowanie czytelnego dla człowieka wyjaśnienia, aby wyjaśnić ten temat!
fedorqui „SO przestań krzywdzić”
3
Świetna odpowiedź. Jedyną rzeczą, którą można dodać, jest wyraźniejsze wskazanie, że historycznie programiści i języki programowania zwykle wyraźnie lub pośrednio zakładali, że sekwencja bajtów i ciąg ASCII były tym samym . Python 3 postanowił jawnie złamać to założenie, poprawnie IMHO.
nekomatic
4
Link do postu Joela wspomnianego przez @ neil.millikin powyżej: joelonsoftware.com/2003/10/08/08…
Kshitij Saraogi,
14

Uwaga: Bardziej szczegółowo opracuję moją odpowiedź dla Python 3, ponieważ koniec życia Python 2 jest bardzo bliski.

W Python 3

bytesskłada się z sekwencji 8-bitowych wartości bez znaku, natomiast strskłada się z sekwencji punktów kodu Unicode reprezentujących znaki tekstowe z języków ludzkich.

>>> # bytes
>>> b = b'h\x65llo'
>>> type(b)
<class 'bytes'>
>>> list(b)
[104, 101, 108, 108, 111]
>>> print(b)
b'hello'
>>>
>>> # str
>>> s = 'nai\u0308ve'
>>> type(s)
<class 'str'>
>>> list(s)
['n', 'a', 'i', '̈', 'v', 'e']
>>> print(s)
naïve

Mimo że bytesi strwydają się działać w ten sam sposób, ich instancje nie są ze sobą kompatybilne, tj. bytesI strinstancji nie można używać razem z operatorami takimi jak >i +. Ponadto należy pamiętać, że porównywanie bytesi strprzypadki równości, tj. Używanie ==, zawsze będą oceniać, Falsenawet jeśli zawierają dokładnie te same znaki.

>>> # concatenation
>>> b'hi' + b'bye' # this is possible
b'hibye'
>>> 'hi' + 'bye' # this is also possible
'hibye'
>>> b'hi' + 'bye' # this will fail
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't concat str to bytes
>>> 'hi' + b'bye' # this will also fail
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "bytes") to str
>>>
>>> # comparison
>>> b'red' > b'blue' # this is possible
True
>>> 'red'> 'blue' # this is also possible
True
>>> b'red' > 'blue' # you can't compare bytes with str
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '>' not supported between instances of 'bytes' and 'str'
>>> 'red' > b'blue' # you can't compare str with bytes
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '>' not supported between instances of 'str' and 'bytes'
>>> b'blue' == 'red' # equality between str and bytes always evaluates to False
False
>>> b'blue' == 'blue' # equality between str and bytes always evaluates to False
False

Kolejny problem związany z obsługą bytesi strwystępujący podczas pracy z plikami zwracanymi za pomocą openwbudowanej funkcji. Z jednej strony, jeśli chcesz czytać lub zapisywać dane binarne do / z pliku, zawsze otwieraj plik w trybie binarnym, takim jak „rb” lub „wb”. Z drugiej strony, jeśli chcesz czytać lub zapisywać dane Unicode do / z pliku, pamiętaj o domyślnym kodowaniu komputera, więc w razie potrzeby przekaż encodingparametr, aby uniknąć niespodzianek.

W Python 2

strskłada się z sekwencji 8-bitowych wartości, a unicodeskłada się z sekwencji znaków Unicode. Należy pamiętać o tym stri unicodemożna go używać razem z operatorami, jeśli strskłada się tylko z 7-bitowych znaków ASCI.

Przydatne może być użycie funkcji pomocniczych do konwersji pomiędzy stri unicodew Pythonie 2 oraz pomiędzy bytesi strw Pythonie 3.

lmiguelvargasf
źródło
4

Z Co to jest Unicode :

Zasadniczo komputery zajmują się tylko liczbami. Przechowują litery i inne znaki, przypisując każdemu numer.

......

Unicode zapewnia unikalny numer dla każdego znaku, bez względu na platformę, bez względu na program, bez względu na język.

Kiedy więc komputer reprezentuje ciąg znaków, znajduje znaki zapisane na komputerze ciągu dzięki unikalnemu numerowi Unicode, a liczby te są przechowywane w pamięci. Ale nie można bezpośrednio napisać łańcucha na dysk ani przesłać go w sieci za pomocą jego unikalnego numeru Unicode, ponieważ liczby te są po prostu zwykłą liczbą dziesiętną. Należy zakodować ciąg do ciągu bajtowego, na przykład UTF-8. UTF-8to postać kodowania zdolne do kodowania wszystkich możliwych znaków i przechowuje znaki jako bajty (wygląda jak ten ). Tak więc zakodowany ciąg może być używany wszędzie, ponieważ UTF-8jest prawie wszędzie obsługiwany. Po otwarciu pliku tekstowego zakodowanego wUTF-8z innych systemów komputer go dekoduje i wyświetla w nim znaki za pomocą unikalnego numeru Unicode. Gdy przeglądarka odbiera dane łańcucha zakodowane UTF-8z sieci, dekoduje dane do łańcucha (zakładając, że przeglądarka UTF-8koduje) i wyświetla ciąg.

W python3 możesz transformować ciąg znaków i bajtów na siebie:

>>> print('中文'.encode('utf-8'))
b'\xe4\xb8\xad\xe6\x96\x87'
>>> print(b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8'))
中文 

Jednym słowem, ciąg znaków służy do wyświetlania ludziom do odczytu na komputerze, a ciąg bajtów służy do przechowywania na dysku i transmisji danych.

Sam Yang
źródło
1

Unicode jest uzgodnionym formatem binarnej reprezentacji znaków i różnego rodzaju formatowania (np. Małe / wielkie litery, nowa linia, znak powrotu karetki) i innych „rzeczy” (np. Emoji). Komputer jest nie mniej zdolny do przechowywania reprezentacji Unicode (seria bitów), czy to w pamięci, czy w pliku, niż do przechowywania reprezentacji ascii (inna seria bitów) lub jakiejkolwiek innej reprezentacji (seria bitów ).

Aby komunikacja mogła się odbyć, strony komunikacji muszą uzgodnić, jaką reprezentację należy zastosować.

Ponieważ Unicode stara się reprezentować wszystkie możliwe znaki (i inne „rzeczy”) używane w komunikacji między ludźmi i między komputerami, wymaga większej liczby bitów do przedstawienia wielu znaków (lub rzeczy) niż inne systemy reprezentacji, które starają się reprezentować bardziej ograniczony zestaw znaków / rzeczy. Aby „uprościć” i być może dostosować do użycia w przeszłości, reprezentacja Unicode jest prawie wyłącznie konwertowana na inny system reprezentacji (np. Ascii) w celu przechowywania znaków w plikach.

Nie jest tak, że Unicode nie może być używane do przechowywania znaków w plikach lub przesyłania ich jakimkolwiek kanałem komunikacyjnym, po prostu tak nie jest .

Termin „ciąg znaków” nie jest dokładnie zdefiniowany. „Ciąg znaków” w swoim powszechnym użyciu odnosi się do zestawu znaków / rzeczy. Na komputerze znaki te mogą być przechowywane w dowolnej z wielu różnych reprezentacji krok po kroku. „Łańcuch bajtów” to zestaw znaków przechowywanych przy użyciu reprezentacji, która wykorzystuje osiem bitów (osiem bitów nazywanych jest bajtem). Ponieważ w dzisiejszych czasach komputery używają systemu Unicode (znaki reprezentowane przez zmienną liczbę bajtów) do przechowywania znaków w pamięci, a ciągi bajtów (znaki reprezentowane przez pojedyncze bajty) do przechowywania znaków w plikach, należy zastosować konwersję przed znakami reprezentowanymi w pamięci zostaną przeniesione do przechowywania w plikach.

Gordon Shephard
źródło
0

Miejmy prosty jednoznakowy ciąg 'š'i zakodujemy go w sekwencji bajtów:

>>> 'š'.encode('utf-8')
b'\xc5\xa1'

Na potrzeby tego przykładu wyświetlmy sekwencję bajtów w postaci binarnej:

>>> bin(int(b'\xc5\xa1'.hex(), 16))
'0b1100010110100001'

Teraz generalnie nie jest możliwe odkodowanie informacji bez wiedzy, w jaki sposób została zakodowana. Tylko jeśli wiesz, że zastosowano utf-8kodowanie tekstu, możesz postępować zgodnie z algorytmem dekodowania utf-8 i uzyskać oryginalny ciąg znaków:

11000101 10100001
   ^^^^^   ^^^^^^
   00101   100001

Możesz wyświetlić liczbę binarną z 101100001powrotem jako ciąg:

>>> chr(int('101100001', 2))
'š'
Jeyekomon
źródło
0

Języki Python obejmują stri bytesjako standardowe „typy wbudowane”. Innymi słowy, obie są klasami. Nie sądzę, że warto próbować zracjonalizować, dlaczego Python został wdrożony w ten sposób.

Mimo, że stri bytessą bardzo podobne do siebie. Oba korzystają z większości tych samych metod. Następujące metody są unikalne dla tej strklasy:

casefold
encode
format
format_map
isdecimal
isidentifier
isnumeric
isprintable

Następujące metody są unikalne dla tej bytesklasy:

decode
fromhex
hex
pięćdziesiąt kart
źródło