Python str a typy Unicode

101

Pracując z Pythonem 2.7, zastanawiam się, jaka jest prawdziwa zaleta w używaniu typu unicodezamiast str, ponieważ oba wydają się być w stanie przechowywać ciągi Unicode. Czy jest jakiś szczególny powód oprócz możliwości ustawienia kodów Unicode w unicodełańcuchach za pomocą znaku ucieczki \?:

Wykonanie modułu z:

# -*- coding: utf-8 -*-

a = 'á'
ua = u'á'
print a, ua

Wyniki w: á, á

EDYTOWAĆ:

Więcej testów przy użyciu powłoki Pythona:

>>> a = 'á'
>>> a
'\xc3\xa1'
>>> ua = u'á'
>>> ua
u'\xe1'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> ua
u'\xe1'

Zatem unicodeciąg wydaje się być zakodowany przy użyciu latin1zamiast, utf-8a nieprzetworzony ciąg jest kodowany przy użyciu utf-8? Jestem teraz jeszcze bardziej zdezorientowany! : S

Caumons
źródło
Nie ma kodowanie dla unicode, to tylko abstrakcją Unicode; unicodemożna przekonwertować na strz pewnym kodowaniem (np utf-8.).
Bin

Odpowiedzi:

178

unicodejest przeznaczony do obsługi tekstu . Tekst to sekwencja punktów kodowych, które mogą być większe niż jeden bajt . Tekst może być zakodowany w zadaniu kodowania do reprezentowania tekstu jako surowego bajtów (np utf-8, latin-1...).

Zauważ, że unicode nie jest zakodowane ! Wewnętrzna reprezentacja używana przez Pythona to szczegół implementacji i nie powinieneś się tym przejmować, o ile jest w stanie reprezentować żądane punkty kodowe.

Wręcz przeciwnie, strw Pythonie 2 jest to zwykła sekwencja bajtów . Nie przedstawia tekstu!

Można go traktować unicodejako ogólną reprezentację pewnego tekstu, który można zakodować na wiele różnych sposobów w sekwencję danych binarnych reprezentowanych przez str.

Uwaga: w Pythonie 3 unicodezmieniono nazwę na stri istnieje nowy bytestyp dla zwykłej sekwencji bajtów.

Niektóre różnice, które możesz zobaczyć:

>>> len(u'à')  # a single code point
1
>>> len('à')   # by default utf-8 -> takes two bytes
2
>>> len(u'à'.encode('utf-8'))
2
>>> len(u'à'.encode('latin1'))  # in latin1 it takes one byte
1
>>> print u'à'.encode('utf-8')  # terminal encoding is utf-8
à
>>> print u'à'.encode('latin1') # it cannot understand the latin1 byte

Zauważ, że używając strmasz kontrolę niższego poziomu na pojedynczych bajtach określonej reprezentacji kodowania, podczas gdy używasz unicodemożesz kontrolować tylko na poziomie punktu kodu. Na przykład możesz:

>>> 'àèìòù'
'\xc3\xa0\xc3\xa8\xc3\xac\xc3\xb2\xc3\xb9'
>>> print 'àèìòù'.replace('\xa8', '')
à�ìòù

To, co wcześniej było poprawne w UTF-8, już nie jest. Używając łańcucha Unicode, nie możesz działać w taki sposób, że wynikowy ciąg nie jest prawidłowym tekstem Unicode. Możesz usunąć punkt kodowy, zastąpić punkt kodowy innym punktem kodowym itp., Ale nie możesz zepsuć wewnętrznej reprezentacji.

Bakuriu
źródło
4
Bardzo dziękuję za odpowiedź, bardzo pomogła! Najbardziej wyjaśniającą częścią dla mnie jest: „Unicode nie jest kodowany! Wewnętrzna reprezentacja używana przez Pythona jest szczegółem implementacyjnym i nie powinieneś się tym przejmować [...]”. Tak więc, podczas serializacji unicodeobiektów encode(), wydaje mi się, że najpierw musimy jawnie je ustawić w odpowiednim formacie kodowania, ponieważ nie wiemy, który z nich jest używany wewnętrznie do reprezentowania unicodewartości.
Caumons
10
Tak. Jeśli chcesz zapisać jakiś tekst (np. Do pliku), musisz przedstawić go bajtami, czyli zakodować . Podczas pobierania zawartości należy znać użyte kodowanie, aby móc zdekodować bajty do unicodeobiektu.
Bakuriu,
Przepraszam, ale stwierdzenie, które unicodenie jest zakodowane, jest po prostu błędne. UTF-16 / UCS-2 i UTF-32 / UCS-4 również są kodowaniami ... iw przyszłości prawdopodobnie powstanie ich więcej. Chodzi o to, że tylko dlatego, że nie powinieneś przejmować się szczegółami implementacji (a nawet nie powinieneś!), Nadal nie oznacza, że unicodenie jest to kodowane. Tak, oczywiście. Czy to możliwe .decode(), to zupełnie inna historia.
0xC0000022L
1
@ 0xC0000022L Może zdanie takie, jakie jest, jest niejasne. Powinien powiedzieć: unicodewewnętrzna reprezentacja obiektu może być dowolna, w tym niestandardowa. W szczególności, w python3 + unicode nie wykorzystywać niestandardową reprezentację wewnętrzną, która zmienia się także w zależności od danych zawartych. Jako takie nie jest to standardowe kodowanie . Unicode jako standard tekstowy definiuje tylko punkty kodowe, które są abstrakcyjną reprezentacją tekstu, istnieje mnóstwo sposobów kodowania Unicode w pamięci, w tym standardowy utf-X itp. Python używa własnego sposobu na zwiększenie wydajności.
Bakuriu,
1
@ 0xC0000022L Również fakt, że UTF-16 jest kodowaniem, nie ma nic wspólnego z obiektem CPythona unicode, ponieważ nie używa on UTF-16 ani UTF-32. Używa reprezentacji ad hoc, a jeśli chcesz zakodować dane na rzeczywiste bajty, musisz użyć encode. Ponadto: język nie określa sposobu unicodeimplementacji, więc różne wersje lub implementacje Pythona mogą (i mają ) różne wewnętrzne reprezentacje.
Bakuriu,
38

Unicode i kodowanie to zupełnie inne, niepowiązane ze sobą rzeczy.

Unicode

Przypisuje numeryczny identyfikator do każdego znaku:

  • 0x41 → A
  • 0xE1 → á
  • 0x414 → Д

Tak więc Unicode przypisuje liczbę 0x41 do A, 0xE1 do á i 0x414 do Д.

Nawet mała strzałka →, której użyłem, ma swój numer Unicode, to 0x2192. Nawet emoji mają swoje numery Unicode, 😂 to 0x1F602.

Możesz sprawdzić numery Unicode wszystkich znaków w tej tabeli . W szczególności możesz znaleźć pierwsze trzy znaki powyżej tutaj , strzałkę tutaj i emoji tutaj .

Te liczby przypisane do wszystkich znaków przez Unicode nazywane są punktami kodowymi .

Wszystko to ma na celu zapewnienie możliwości jednoznacznego odniesienia się do każdej postaci. Na przykład, jeśli mówię o 😂, zamiast mówić „wiesz, ten śmiejący się emoji ze łzami” , mogę po prostu powiedzieć, punkt kodu Unicode 0x1F602 . Łatwiej, prawda?

Zwróć uwagę, że punkty kodowe Unicode są zwykle formatowane z początkiem U+, a następnie szesnastkową wartością liczbową dopełnianą do co najmniej 4 cyfr. Tak więc powyższe przykłady to U + 0041, U + 00E1, U + 0414, U + 2192, U + 1F602.

Zakres punktów kodowych Unicode wynosi od U + 0000 do U + 10FFFF. To jest 1 114 112 liczb. 2048 z tych liczb jest używanych jako surogaty , więc pozostaje 1 112 064. Oznacza to, że Unicode może przypisać unikalny identyfikator (punkt kodowy) do 1112 064 różnych znaków. Nie wszystkie z tych punktów kodowych są jeszcze przypisane do znaku, a Unicode jest stale rozszerzany (na przykład po wprowadzeniu nowych emoji).

Ważną rzeczą do zapamiętania jest to, że wszystko, co robi Unicode, to przypisanie numerycznego identyfikatora, zwanego punktem kodowym, do każdego znaku w celu łatwego i jednoznacznego odniesienia.

Kodowania

Mapuj znaki do wzorów bitowych.

Te wzorce bitowe są używane do reprezentowania znaków w pamięci komputera lub na dysku.

Istnieje wiele różnych kodowań, które obejmują różne podzbiory znaków. W świecie anglojęzycznym najczęściej używane są następujące kodowania:

ASCII

Mapy 128 znaków (punkty kodowe U + 0000 do U + 007F) do wzorów bitowych o długości 7.

Przykład:

  • a → 1100001 (0x61)

Możesz zobaczyć wszystkie mapowania w tej tabeli .

ISO 8859-1 (aka Latin-1)

Mapy 191 znaków (punkty kod U + 0020 U + 007E i U U + 00A0 + 00FF) do wzorów bitowych o długości 8.

Przykład:

  • a → 01100001 (0x61)
  • á → 11100001 (0xE1)

Możesz zobaczyć wszystkie mapowania w tej tabeli .

UTF-8

Mapy 1,112,064 znaków (wszystkie istniejące punkty kod Unicode) do wzorów bitowych z każdej długości 8, 16, 24 lub 32 bitów (to jest 1, 2, 3 lub 4 bajtów).

Przykład:

  • a → 01100001 (0x61)
  • á → 11000011 10100001 (0xC3 0xA1)
  • ≠ → 11100010 10001001 10100000 (0xE2 0x89 0xA0)
  • 😂 → 11110000 10011111 10011000 10000010 (0xF0 0x9F 0x98 0x82)

Drogą UTF-8 koduje znaki na ciągach bitów jest bardzo dobrze opisane tutaj .

Unicode i kodowanie

Patrząc na powyższe przykłady, staje się jasne, jak przydatny jest Unicode.

Na przykład, jeśli jestem Latin-1 i chcę wyjaśnić moje kodowanie á, nie muszę mówić:

„Koduję to a za pomocą aigu (lub jak to nazywasz) jako 11100001”

Ale mogę po prostu powiedzieć:

„Koduję U + 00E1 jako 11100001”

A jeśli mam UTF-8 , mogę powiedzieć:

„Ja z kolei koduję U + 00E1 jako 11000011 10100001”

I dla każdego jest jednoznacznie jasne, o jaką postać mamy na myśli.

Teraz przejdźmy do często pojawiającego się zamieszania

To prawda, że ​​czasami wzorzec bitowy kodowania, jeśli interpretujesz go jako liczbę binarną, jest taki sam, jak punkt kodowy Unicode tego znaku.

Na przykład:

  • Koduje ASCII a jak 1100001, który można interpretować jako liczbę szesnastkową 0x61 , a punkt kodowy Unicode jest U + 0061 .
  • Latin-1 koduje á jako 11100001, co można zinterpretować jako liczbę szesnastkową 0xE1 , a punkt kodu Unicode á to U + 00E1 .

Oczywiście zostało to specjalnie zaaranżowane dla wygody. Ale powinieneś spojrzeć na to jak na czysty zbieg okoliczności . Wzorzec bitowy używany do reprezentowania znaku w pamięci nie jest w żaden sposób powiązany z punktem kodowym Unicode tego znaku.

Nikt nawet nie mówi, że jako liczbę binarną trzeba interpretować ciąg bitowy, taki jak 11100001. Wystarczy spojrzeć na to jako na sekwencję bitów, której Latin-1 używa do kodowania znaku á .

Wracając do twojego pytania

Kodowanie używane przez twój interpreter Pythona to UTF-8 .

Oto, co dzieje się w twoich przykładach:

Przykład 1

Poniższy kod koduje znak á w UTF-8. W rezultacie otrzymujemy ciąg bitów 11000011 10100001, który jest zapisywany w zmiennej a.

>>> a = 'á'

Gdy spojrzysz na wartość a, jej zawartość 11000011 10100001 jest formatowana jako liczba szesnastkowa 0xC3 0xA1 i wyświetlana jako '\xc3\xa1':

>>> a
'\xc3\xa1'

Przykład 2

Poniższe zapisuje punkt kodu Unicode á, czyli U + 00E1, w zmiennej ua(nie wiemy, jakiego formatu danych Python używa wewnętrznie do reprezentowania punktu kodowego U + 00E1 w pamięci i nie jest to dla nas ważne):

>>> ua = u'á'

Kiedy patrzysz na wartość ua, Python mówi ci, że zawiera punkt kodowy U + 00E1:

>>> ua
u'\xe1'

Przykład 3

Poniższy kod koduje punkt kodowy Unicode U + 00E1 (reprezentujący znak á) za pomocą UTF-8, co skutkuje wzorem bitowym 11000011 10100001. Ponownie, dla wyjścia ten wzór bitowy jest reprezentowany jako liczba szesnastkowa 0xC3 0xA1:

>>> ua.encode('utf-8')
'\xc3\xa1'

Przykład 4

Poniższy kod koduje punkt kodowy Unicode U + 00E1 (reprezentujący znak á) za pomocą Latin-1, co daje w wyniku wzór bitowy 11100001. Na wyjściu ten wzorzec bitowy jest reprezentowany jako liczba szesnastkowa 0xE1, która przypadkowo jest taka sama jak początkowa punkt kodowy U + 00E1:

>>> ua.encode('latin1')
'\xe1'

Nie ma związku między obiektem Unicode uaa kodowaniem Latin-1. To, że punkt kodowy á to U + 00E1, a kodowanie Latin-1 á to 0xE1 (jeśli interpretujesz wzór bitowy kodowania jako liczbę binarną), jest czystym zbiegiem okoliczności.

weibeld
źródło
31

Twój terminal jest skonfigurowany do UTF-8.

Fakt, że drukarnia ato przypadek; piszesz surowe bajty UTF-8 na terminalu. ato wartość o długości dwa , zawierająca dwa bajty, wartości szesnastkowe C3 i A1, natomiast uajest wartością w formacie Unicode o długości jeden , zawierającą punkt kodowy U + 00E1.

Ta różnica w długości jest głównym powodem używania wartości Unicode; nie można łatwo zmierzyć liczby znaków tekstowych w ciągu bajtów; len()z ciągiem bajtów powie Ci ile bajtów zostały wykorzystane, a nie jak wiele znaków zostały zakodowane.

Widać różnicę podczas kodowania wartości Unicode różnych kodowań wyjściowych:

>>> a = 'á'
>>> ua = u'á'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> a
'\xc3\xa1'

Zauważ, że pierwsze 256 punktów kodowych standardu Unicode odpowiada standardowi Latin 1, więc punkt kodowy U + 00E1 jest kodowany jako Latin 1 jako bajt z wartością szesnastkową E1.

Co więcej, Python używa kodów ucieczki w reprezentacjach zarówno ciągów znaków Unicode, jak i bajtów, a niskie punkty kodowe, które nie są drukowane w ASCII, są również reprezentowane za pomocą \x..wartości ucieczki. Dlatego ciąg Unicode z punktem kodowym od 128 do 255 wygląda tak, jak kodowanie Latin 1. Jeśli masz ciąg znaków Unicode z punktami kodowymi powyżej U + 00FF, \u....zamiast tego używana jest inna sekwencja ucieczki , z czterocyfrową wartością szesnastkową.

Wygląda na to, że nie rozumiesz jeszcze w pełni, jaka jest różnica między Unicode a kodowaniem. Przed kontynuowaniem przeczytaj następujące artykuły:

Martijn Pieters
źródło
Zmieniłem moje pytanie, przeprowadzając dalsze testy. Od jakiegoś czasu czytam informacje o Unicode i różnych kodowaniach i myślę, że rozumiem teorię, ale podczas testowania kodu Pythona nie
łapię
1
Kodowanie latin-1 pasuje do pierwszych 256 punktów kodowych standardu Unicode. To dlatego U + 00E1 koduje \xe1w alfabecie łacińskim 1.
Martijn Pieters
2
To jest najważniejszy aspekt Unicode. To nie jest kodowanie . To jest tekst. Unicode to standard, który zawiera dużo, dużo więcej, na przykład informacje o tym, jakie punkty kodowe są liczbami, spacje lub inne kategorie, powinny być wyświetlane od lewej do prawej lub od prawej do lewej itd. Itd.
Martijn Pieters
1
To tak, jakby powiedzieć, że Unicode jest jak „interfejs”, a kodowanie jest jak rzeczywista „implementacja”.
Caumons
2
@Varun: musisz używać wąskiej kompilacji Pythona 2, która wewnętrznie używa UCS-2 i błędnie interpretuje wszystko, co przekracza U + FFFF, jako mające długość dwa. Python 3 i kompilacja UCS-2 (szeroka) pokażą, że długość naprawdę wynosi 1.
Martijn Pieters
2

Gdy zdefiniujesz a jako Unicode, znaki a i á są równe. W przeciwnym razie á liczy się jako dwa znaki. Spróbuj len (a) i len (au). Oprócz tego może być konieczne kodowanie podczas pracy z innymi środowiskami. Na przykład, jeśli używasz md5, otrzymujesz różne wartości a i ua

Ali Rasim Kocal
źródło