Varsayılan kodlama ASCII olduğunda Python neden unicode karakterleri yazdırıyor?


139

Python 2.6 kabuğundan:

>>> import sys
>>> print sys.getdefaultencoding()
ascii
>>> print u'\xe9'
é
>>> 

"É" karakteri ASCII'nin bir parçası olmadığından ve bir kodlama belirtmediğim için, baskı ifadesinden sonra bazı anlamsızlık veya Hata almayı bekledim. Sanırım ASCII'nin varsayılan kodlama ne anlama geldiğini anlamıyorum.

DÜZENLE

Düzenlemeyi Yanıtlar bölümüne taşıdım ve önerildiği gibi kabul ettim.


6
Bu düzenlemeyi bir cevaba dönüştürüp kabul ederseniz çok güzel olurdu .
mercator

2
Yazdırma '\xe9'için yapılandırılmış bir terminalde UTF-8 olacak değil yazdırmak é. \xe9Geçerli bir UTF-8 dizisi olmadığı için bir yedek karakter (genellikle bir soru işareti) yazdıracaktır ( bu önde gelen baytı izlemesi gereken iki bayt eksik). Bu kesinlikle olacak değil yerine Latin-1 olarak yorumlanabilir.
Martijn Pieters

2
@MartijnPieters \xe9Yazdırmak için çıktı aldığımda terminalin ISO-8859-1'de (latin1) kod çözme olarak ayarlandığını belirlediğim kısımdan kaymış olabileceğinizden şüpheleniyorum é.
Michael Ekoka

2
Ah evet, o kısmı özledim; terminal kabuktan farklı bir konfigürasyona sahiptir. Kontrol.
Martijn Pieters

cevap ile yağsız ama aslında, ben python 2.7 için u öneki olmadan dize var. neden hala unicode olarak ele alınıyor? (benim sys.getdefaultencoding () ascii)
dtc

Yanıtlar:


104

Çeşitli cevaplardan parçalar ve parçalar sayesinde, bir açıklama yapabiliriz.

Bir unicode dize olan u '\ xe9' yazdırmaya çalışarak, Python sys.stdout.encoding içinde saklanan kodlama şemasını kullanarak bu dizeyi örtük olarak kodlamaya çalışır. Python aslında bu ayarı başlatıldığı ortamdan alır. Ortamdan uygun bir kodlama bulamazsa, ancak o zaman varsayılan ASCII'ye geri döner.

Örneğin, varsayılan olarak UTF-8 kodlayan bir bash kabuğu kullanıyorum. Python'u ondan başlatırsam, alır ve bu ayarı kullanır:

$ python

>>> import sys
>>> print sys.stdout.encoding
UTF-8

Bir an için Python kabuğundan çıkalım ve bash ortamını bazı sahte kodlamalarla ayarlayalım:

$ export LC_CTYPE=klingon
# we should get some error message here, just ignore it.

Sonra python kabuğunu tekrar başlatın ve gerçekten varsayılan ascii kodlamasına geri döndüğünü doğrulayın.

$ python

>>> import sys
>>> print sys.stdout.encoding
ANSI_X3.4-1968

Bingo!

Şimdi ascii dışında bazı unicode karakterler çıkarmaya çalışırsanız, güzel bir hata mesajı almalısınız

>>> print u'\xe9'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' 
in position 0: ordinal not in range(128)

Python'dan çıkıp bash kabuğunu atalım.

Şimdi Python dizeleri çıkardıktan sonra ne olacağını gözlemleyeceğiz. Bunun için önce bir grafik terminali içinde bir bash kabuğu başlatacağız (Gnome Terminalini kullanıyorum) ve terminali çıktıyı ISO-8859-1 aka latin-1 ile deşifre edecek şekilde ayarlayacağız (grafik terminallerinin genellikle Karakter Ayarlama seçeneği vardır Açılır menülerinden birinde kodlama ). Bunun gerçek kabuk ortamının kodlamasını değiştirmediğini, yalnızca terminalin kendisinin verdiği çıkışı çözme şeklini değiştirdiğini, bir web tarayıcısının yaptığı gibi değiştirdiğini unutmayın. Bu nedenle terminalin kodlamasını kabuk ortamından bağımsız olarak değiştirebilirsiniz. Daha sonra Python'u kabuktan başlatalım ve sys.stdout.encoding öğesinin kabuk ortamının kodlamasına (benim için UTF-8) ayarlandığını doğrulayalım:

$ python

>>> import sys

>>> print sys.stdout.encoding
UTF-8

>>> print '\xe9' # (1)
é
>>> print u'\xe9' # (2)
é
>>> print u'\xe9'.encode('latin-1') # (3)
é
>>>

(1) python ikili dizeyi olduğu gibi çıkarır, terminal onu alır ve değerini latin-1 karakter eşlemesiyle eşleştirmeye çalışır. Latince-1'de 0xe9 veya 233, "é" karakterini verir ve bu yüzden terminalin gösterdiği şey budur.

(2) python , Unicode dizesini sys.stdout.encoding öğesinde ayarlı olan şema ile örtük olarak kodlamaya çalışır , bu örnekte "UTF-8" olur. UTF-8 kodlamasından sonra, elde edilen ikili dize '\ xc3 \ xa9' dur (daha sonra açıklamaya bakınız). Terminal akışı bu şekilde alır ve latin-1 kullanarak 0xc3a9 kodunu çözmeye çalışır, ancak latin-1 0'dan 255'e gider ve böylece bir seferde yalnızca 1 baytlık kodları çözer. 0xc3a9 2 bayt uzunluğundadır, latin-1 kod çözücü 0xc3 (195) ve 0xa9 (169) olarak yorumlar ve bu 2 karakter verir: Ã ve ©.

(3) python unicode kod noktasını u '\ xe9' (233) latin-1 şemasıyla kodlar. Latin-1 kod noktaları aralığının 0-255 olduğu ve bu aralıktaki Unicode ile aynı karakteri işaret ettiği ortaya çıktı. Bu nedenle, bu aralıktaki Unicode kod noktaları latin-1'de kodlandığında aynı değeri verir. Yani latin-1 ile kodlanmış u '\ xe9' (233) ikili '\ xe9' dizesini de verecektir. Terminal bu değeri alır ve latin-1 karakter haritasında eşleştirmeye çalışır. Tıpkı durum (1) gibi "é" verir ve görüntülenen de budur.

Şimdi, açılır menüden terminalin kodlama ayarlarını UTF-8 olarak değiştirelim (web tarayıcınızın kodlama ayarlarını değiştirir gibi). Python'u durdurmaya veya kabuğu yeniden başlatmaya gerek yok. Terminalin kodlaması artık Python'larla eşleşiyor. Tekrar yazdırmayı deneyelim:

>>> print '\xe9' # (4)

>>> print u'\xe9' # (5)
é
>>> print u'\xe9'.encode('latin-1') # (6)

>>>

(4) python olduğu gibi bir ikili dize çıkarır . Terminal UTF-8 ile bu akışı çözmeyi dener. Ancak UTF-8, 0xe9 değerini anlamıyor (daha sonra açıklamaya bakınız) ve bu nedenle onu bir unicode kod noktasına dönüştüremiyor. Kod noktası bulunamadı, hiçbir karakter yazdırılmadı.

(5) python , Unicode dizesini sys.stdout.encoding içindeki ile örtük olarak kodlamaya çalışır . Hala "UTF-8". Sonuçta elde edilen ikili dize '\ xc3 \ xa9'dur. Terminal akışı alır ve UTF-8 kullanarak 0xc3a9 kodunu çözmeye çalışır. Unicode karakter eşlemesinde "é" sembolünü gösteren 0xe9 (233) kod değerini verir. Terminal "é" görüntüler.

(6) python unicode dizgiyi latin-1 ile kodlar, aynı değer '\ xe9' ile bir ikili dizge verir. Yine, terminal için bu durum (4) ile hemen hemen aynıdır.

Sonuçlar: - Python, varsayılan kodlamasını dikkate almadan unicode olmayan dizeleri ham veri olarak çıkarır. Terminal, geçerli kodlaması verilerle eşleşirse bunları görüntüler. - Python, Unicode dizelerini sys.stdout.encoding öğesinde belirtilen şemayı kullanarak kodladıktan sonra çıkarır. - Python bu ayarı kabuğun ortamından alır. - terminal çıkışı kendi kodlama ayarlarına göre görüntüler. - terminalin kodlaması kabuktan bağımsızdır.


Unicode, UTF-8 ve latin-1 hakkında daha fazla bilgi:

Unicode temel olarak bazı simgelere işaret etmek için bazı tuşların (kod noktaları) geleneksel olarak atandığı bir karakter tablosudur. örneğin, kural olarak 0xe9 (233) anahtarının 'é' sembolüne işaret eden değer olduğuna karar verilmiştir. ASCII ve Unicode, 0 ile 127 arasında aynı kod noktalarını, latin-1 ve Unicode ile 0 ile 255 arasında aynı kodu kullanır. Yani, ASCII, latin-1 ve Unicode'da 0x41, 'C', 0xc8'de 'Ü' latin-1 ve Unicode, 0xe9, latin-1 ve Unicode'da 'é'yi gösterir.

Elektronik cihazlarla çalışırken, Unicode kod noktalarının elektronik olarak gösterilmesi için etkili bir yol gerekir. Kodlama şemaları budur. Çeşitli Unicode kodlama şemaları vardır (utf7, UTF-8, UTF-16, UTF-32). En sezgisel ve doğrudan kodlama yaklaşımı, Unicode haritasında bir kod noktasının değerini, elektronik formu için değeri olarak kullanmak olacaktır, ancak Unicode şu anda bir milyondan fazla kod noktasına sahiptir, bu da bazılarının 3 bayt olmasını gerektirir. olarak ifade edilmiştir. Metinle verimli bir şekilde çalışmak için 1'e 1 eşleme oldukça pratik olmayacaktır, çünkü tüm kod noktalarının gerçek gereksinimlerine bakılmaksızın, karakter başına minimum 3 bayt ile tam olarak aynı miktarda alanda depolanmasını gerektirecektir.

Çoğu kodlama şemasının alan gereksinimi ile ilgili eksiklikleri vardır, en ekonomik olanları tüm unicode kod noktalarını kapsamaz, örneğin ascii sadece ilk 128'i kaplarken, latin-1 ilk 256'yı kapsar. Daha kapsamlı olmaya çalışan diğerleri de sona erer israf etmek, ortak "ucuz" karakterler için bile gerekenden daha fazla bayt gerektirdiğinden. Örneğin UTF-16, ascii aralığındaki karakterler de dahil olmak üzere karakter başına minimum 2 bayt kullanır (65 olan 'B', UTF-16'da hala 2 bayt depolama gerektirir). UTF-32, tüm karakterleri 4 baytta sakladığından daha da israflıdır.

UTF-8, ikilemi akıllıca çözdü ve şema, kod noktalarını değişken miktarda bayt boşluğuyla saklayabildi. Kodlama stratejisinin bir parçası olarak UTF-8, kod noktalarını, alan gereksinimlerini ve sınırlarını (muhtemelen kod çözücülere) gösteren bayrak bitleriyle bağlar.

ASCII aralığında (0-127) unicode kod noktalarının UTF-8 kodlaması:

0xxx xxxx  (in binary)
  • x'ler kodlama sırasında kod noktasını "saklamak" için ayrılan gerçek alanı gösterir
  • Baştaki 0, UTF-8 kod çözücüsüne bu kod noktasının yalnızca 1 bayt gerektireceğini gösteren bir işarettir.
  • kodlama üzerine UTF-8, belirli bir aralıktaki kod noktalarının değerini değiştirmez (yani UTF-8'de kodlanan 65 de 65'tir). Unicode ve ASCII'nin aynı aralıkta uyumlu olduğu düşünüldüğünde, tesadüfen UTF-8 ve ASCII'yi de bu aralıkta uyumlu hale getirir.

örneğin 'B' için Unicode kod noktası ikili dosyada '0x42' veya 0100 0010'dur (dediğimiz gibi ASCII'de aynıdır). UTF-8 kodlamasından sonra:

0xxx xxxx  <-- UTF-8 encoding for Unicode code points 0 to 127
*100 0010  <-- Unicode code point 0x42
0100 0010  <-- UTF-8 encoded (exactly the same)

127'nin üzerindeki Unicode kod noktalarının UTF-8 kodlaması (ascii olmayan):

110x xxxx 10xx xxxx            <-- (from 128 to 2047)
1110 xxxx 10xx xxxx 10xx xxxx  <-- (from 2048 to 65535)
  • baştaki '110' bitleri UTF-8 kod çözücüsüne 2 baytta kodlanmış bir kod noktasının başlangıcını belirtirken, '1110' 3 bayt belirtir, 11110 4 bayt ve benzerlerini gösterir.
  • iç '10' bayrak bitleri bir iç baytın başlangıcını belirtmek için kullanılır.
  • yine x'ler, kodlamadan sonra Unicode kod noktası değerinin depolandığı alanı işaretler.

örneğin 'é' Unicode kod noktası 0xe9'dur (233).

1110 1001    <-- 0xe9

UTF-8 bu değeri kodladığında, değerin 127'den büyük ve 2048'den küçük olduğunu belirler, bu nedenle 2 baytta kodlanmalıdır:

110x xxxx 10xx xxxx   <-- UTF-8 encoding for Unicode 128-2047
***0 0011 **10 1001   <-- 0xe9
1100 0011 1010 1001   <-- 'é' after UTF-8 encoding
C    3    A    9

UTF-8 kodlaması 0xc3a9 olduktan sonra 0xe9 Unicode kodu işaret eder. Terminal tam olarak böyle alır. Terminaliniz latin-1 (unicode olmayan eski kodlamalardan biri) kullanarak dizeleri deşifre edecek şekilde ayarlanmışsa, Ã © görürsünüz, çünkü latin-1'deki 0xc3, Ã ve 0xa9 ila © işaret eder.


6
Mükemmel açıklama. Şimdi UTF-8'i anlıyorum!
Doktor Kodlayıcı

2
Tamam, tüm yayınınızı yaklaşık 10 saniye içinde okudum. "Python kodlama söz konusu olduğunda berbat" dedi.
Andrew

Harika bir açıklama. Bu soruya cevap verebilir misiniz ?
Maggyero

26

Unicode karakterler stdout'a yazdırıldığında sys.stdout.encodingkullanılır. Unicode olmayan bir karakterin olduğu varsayılır sys.stdout.encodingve sadece terminale gönderilir. Sistemimde (Python 2):

>>> import unicodedata as ud
>>> import sys
>>> sys.stdout.encoding
'cp437'
>>> ud.name(u'\xe9') # U+00E9 Unicode codepoint
'LATIN SMALL LETTER E WITH ACUTE'
>>> ud.name('\xe9'.decode('cp437')) 
'GREEK CAPITAL LETTER THETA'
>>> '\xe9'.decode('cp437') # byte E9 decoded using code page 437 is U+0398.
u'\u0398'
>>> ud.name(u'\u0398')
'GREEK CAPITAL LETTER THETA'
>>> print u'\xe9' # Unicode is encoded to CP437 correctly
é
>>> print '\xe9'  # Byte is just sent to terminal and assumed to be CP437.
Θ

sys.getdefaultencoding() yalnızca Python'un başka bir seçeneği olmadığında kullanılır.

Python 3.6 veya üstünün Windows üzerindeki kodlamaları yok saydığını ve terminale Unicode yazmak için Unicode API'lerini kullandığını unutmayın. Herhangi bir UnicodeEncodeError uyarısı yoktur ve yazı tipi destekliyorsa doğru karakter görüntülenir. Yazı bile gelmez bunu destekleyecek karakterleri hala destekleyen yazı ile bir uygulamaya terminali gelen kesik-n-yapıştırılan olabilir ve doğru olacaktır. Yükselt!


8

Python REPL, ortamınızdan hangi kodlamanın kullanılacağını almaya çalışır. Eğer aklı başında bir şey bulursa, o zaman hepsi çalışır. Ne olup bittiğini anlayamadığı zamandır.

>>> print sys.stdout.encoding
UTF-8

3
meraktan sys.stdout.encoding dosyasını ascii olarak nasıl değiştirebilirim?
Michael Ekoka

2
@TankorSmash TypeError: readonly attribute2.7.2
Kos

4

Sen gelmiş açık bir Unicode dizesi girerek bir kodlama belirtildi. uÖneki kullanmamanın sonuçlarını karşılaştırın .

>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>> '\xe9'
'\xe9'
>>> u'\xe9'
u'\xe9'
>>> print u'\xe9'
é
>>> print '\xe9'

>>> 

O \xe9zaman Python varsayılan kodlamanızı (Ascii) varsayar, böylece boş bir şey yazdırır.


1
Eğer iyi anlıyorsam, unicode dizeleri (kod noktaları) yazdırdığımda, python bana sadece ascii'de ne olabileceğini vermek yerine utf-8'de kodlanmış bir çıktı istediğimi varsayar ?
Michael Ekoka

1
@mike: AFAIK söylediğin doğru. O Eğer yaptığımız Unicode karakterleri yazdırmak ancak ASCII olarak kodlanmış, her şey bozuk çıkacağını ve muhtemelen tüm başlayanlar, soran olurdu "Nasıl Unicode metin yazdırmak yiyemezsin?"
Mark Rushakoff

2
Teşekkür ederim. Aslında bu yeni başlayanlardan biriyim, ancak unicode hakkında biraz bilgisi olan insanların tarafından geliyor, bu yüzden bu davranış beni biraz atıyor.
Michael Ekoka

3
R., doğru değil, çünkü '\ xe9' ascii karakter kümesinde değil. Unicode olmayan dizeler sys.stdout.encoding kullanılarak yazdırılır, Unicode dizeleri yazdırılmadan önce sys.stdout.encoding olarak kodlanır.
Mark Tolonen

0

Benim için çalışıyor:

import sys
stdin, stdout = sys.stdin, sys.stdout
reload(sys)
sys.stdin, sys.stdout = stdin, stdout
sys.setdefaultencoding('utf-8')

1
Kaçınılmaz olarak başka bir şey kıracak ucuz kirli kesmek. Doğru şekilde yapmak zor değil!
Chris Johnson

0

Python varsayılan / örtülü dize kodlamaları ve dönüşümleri uyarınca :

  • Ne zaman printing unicode, bu oluyor encodeile d <file>.encoding.
    • zaman encodingayarlı değil, unicodeörtülü dönüştürülür str(bunun için codec'i olduğundan sys.getdefaultencoding(), yani ascii, herhangi bir ulusal karakterler neden olur UnicodeEncodeError)
    • standart akışlar için encodingortamdan çıkarılır. Genellikle fot ttyakışları ayarlanır (terminalin yerel ayarlarından), ancak büyük olasılıkla borular için ayarlanmaz
      • bu nedenle a print u'\xe9', bir terminale gittiğinde başarılı olur ve yönlendirilirse başarısız olur. encode()Girmeden önce istenen kodlamaya sahip dizeye bir çözüm gelir print.
  • Ne zaman printing strolduğu gibi, bayt akışından gönderilir. Terminalin gösterdiği glifler yerel ayarlarına bağlı olacaktır.
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.