Dosyaya yeniden yönlendirilirken UnicodeDecodeError hatası


100

Bu pasajı Ubuntu terminalinde (kodlama utf-8 olarak ayarlanmış) iki kez çalıştırıyorum, bir kez ./test.pyve sonra şununla ./test.py >out.txt:

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni

Yönlendirme olmadan çöp yazdırır. Yeniden yönlendirme ile bir UnicodeDecodeError alıyorum. Birisi neden hatayı sadece ikinci durumda aldığımı açıklayabilir mi, hatta daha iyisi her iki durumda da perdenin arkasında neler olduğuna dair ayrıntılı bir açıklama verebilir mi?


Bu cevap da yardımcı olabilir.
tzot

Bulgunuzu kopyalamaya çalıştığımda, UnicodeDecodeError değil, UnicodeEncodeError alıyorum. gist.github.com/jaraco/12abfc05872c65a4f3f6cd58b6f9be4d
Jason R. Coombs

Yanıtlar:


252

Bu tür kodlama problemlerinin bütün anahtarı, prensipte iki farklı "dizge" kavramının olduğunu anlamaktır : (1) karakter dizesi ve (2) dizge / bayt dizisi. 256 karakterden fazla olmayan kodlamaların tarihsel olarak her yerde bulunması nedeniyle (ASCII, Latin-1, Windows-1252, Mac OS Roman,…) bu ayrım çoğunlukla uzun zamandır göz ardı edilmiştir: bu kodlamalar, bir dizi ortak karakteri eşleştirir. 0 ile 255 arasındaki sayılar (yani bayt); Web'in gelişinden önce nispeten sınırlı dosya değişimi, bu uyumsuz kodlama durumunu tolere edilebilir hale getirdi, çünkü çoğu program, aynı işletim sisteminde kalan metin ürettikleri sürece birden fazla kodlama olduğu gerçeğini görmezden gelebilirdi: bu tür programlar basitçe metni bayt olarak ele alın (işletim sistemi tarafından kullanılan kodlama yoluyla). Doğru, modern görünüm, aşağıdaki iki noktaya göre bu iki dizi kavramını doğru bir şekilde ayırır:

  1. Karakterler çoğunlukla bilgisayarlarla ilgisizdir : örneğin بايثون, 中 蟒 ve 🐍 gibi bir kişi onları tebeşir tahtasına vb. Çizebilir. Makineler için "karakterler" ayrıca boşluklar, satır başı, yazma yönünü belirleme talimatları (Arapça vb. İçin), aksanlar vb. Gibi "çizim talimatlarını" da içerir . Unicode standardında çok büyük bir karakter listesi bulunur ; bilinen karakterlerin çoğunu kapsar.

  2. Öte yandan, bilgisayarların bir şekilde soyut karakterleri temsil etmesi gerekir: bunun için bayt dizilerini kullanırlar (0 ile 255 arasında sayılar dahil), çünkü bellekleri bayt yığınları halinde gelir. Karakterleri bayta dönüştüren gerekli işleme kodlama denir . Bu nedenle, bir bilgisayar , karakterleri temsil etmek için bir kodlama gerektirir . Bilgisayarınızda bulunan herhangi bir metin, ister bir terminale (belirli bir şekilde kodlanmış karakterleri bekleyen) gönderilsin veya bir dosyaya kaydedilsin (görüntülenene kadar) kodlanır. Görüntülenmek veya düzgün bir şekilde "anlaşılmak" için (örneğin Python yorumlayıcısı tarafından), bayt akışlarının kodu karakterlere dönüştürülür. Birkaç kodlama(UTF-8, UTF-16,…) Unicode tarafından karakter listesi için tanımlanır (böylece Unicode hem karakterlerin bir listesini hem de bu karakterler için kodlamaları tanımlar — "Unicode kodlaması" ifadesinin bir her yerde bulunan UTF-8'e başvurmanın yolu, ancak Unicode birden çok kodlama sağladığından bu yanlış terminolojidir ).

Özetle, bilgisayarların karakterleri baytlarla dahili olarak temsil etmesi gerekir ve bunu iki işlemle yaparlar:

Kodlama : karakterler → bayt

Kod çözme : bayt → karakterler

Bazı kodlamalar tüm karakterleri (örneğin, ASCII) kodlayamazken (bazıları) Unicode kodlamaları tüm Unicode karakterlerini kodlamanıza izin verir. Kodlamanın da benzersiz olması gerekmez , çünkü bazı karakterler ya doğrudan ya da bir kombinasyon (örneğin bir temel karakter ve aksan) olarak temsil edilebilir.

Satırsonu kavramının bir karmaşıklık katmanı eklediğine dikkat edin , çünkü işletim sistemine bağlı olan farklı (kontrol) karakterlerle temsil edilebilir (bu, Python'un evrensel satırsonu dosyası okuma modunun nedenidir ).

Şimdi, yukarıda "karakter" olarak adlandırdığım şey, Unicode'un " kullanıcı tarafından algılanan karakter " olarak adlandırdığı şeydir . Kullanıcı tarafından algılanan tek bir karakter, bazen Unicode listesindeki farklı dizinlerde bulunan ve " kod noktaları " olarak adlandırılan karakter parçalarını (temel karakter, aksanlar, ...) birleştirerek Unicode'da temsil edilebilir - bu kod noktaları, oluşturmak için bir araya getirilebilir bir "grafem kümesi". Böylelikle Unicode, bayt ve karakter dizileri arasında yer alan ve sonuncusuna daha yakın olan bir Unicode kod noktası dizisinden oluşan üçüncü bir dizi kavramına yol açar. Bunlara " Unicode dizeleri " adını vereceğim (Python 2'deki gibi).

Python, (kullanıcı tarafından algılanan) karakter dizilerini yazdırabilirken , Python bayt olmayan dizeler aslında kullanıcı tarafından algılanan karakterlerin değil, Unicode kod noktalarının dizileridir . Kod noktası değerleri Python'un \uve \UUnicode dizgi sözdiziminde kullanılan değerlerdir . Bir karakterin kodlamasıyla karıştırılmamalıdırlar (ve onunla herhangi bir ilişkisi olması gerekmez: Unicode kod noktaları çeşitli şekillerde kodlanabilir).

Bunun önemli bir sonucu vardır: Bir Python (Unicode) dizesinin uzunluğu kod noktalarının sayısıdır ve bu her zaman kullanıcı tarafından algılanan karakter sayısı değildir : bu nedenle s = "\u1100\u1161\u11a8"; print(s, "len", len(s))(Python 3), tek bir kullanıcı tarafından algılanmasına 각 len 3rağmen s(Korece) verir karakter (çünkü 3 kod noktasıyla temsil edilir - gösterilmesi gerekmese bile print("\uac01")). Bununla birlikte, birçok pratik durumda, bir dizenin uzunluğu, kullanıcı tarafından algılanan karakterlerin sayısıdır, çünkü birçok karakter tipik olarak Python tarafından tek bir Unicode kod noktası olarak saklanır.

In Python 2 Unicode dizeleri "Unicode dizeleri" (... denir unicodetipi, edebi biçim u"…"bayt dizileri "dizeleri" (iken) strbayt dizisi örneği için dize hazır ile inşa edilebilir tip "…"). In Python 3 Unicode dizeleri basitçe "dizeleri" (denir strtipi, edebi biçim "…"bayt dizileri "bayt" (iken) bytestipi, edebi biçim b"…"). Sonuç "🐍"[0]olarak, Python 2 ( '\xf0'bayt) ve Python 3 ( "🐍"ilk ve tek karakter) gibi bir şey farklı bir sonuç verir .

Bu birkaç anahtar nokta ile kodlamayla ilgili çoğu soruyu anlayabilirsiniz!


Normalde, bir terminale yazdırdığınızda , çöp almamalısınız: Python, terminalinizin kodlamasını bilir. Aslında, terminalin hangi kodlamayı beklediğini kontrol edebilirsiniz:u"…"

% python
Python 2.7.6 (default, Nov 15 2013, 15:20:37) 
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print sys.stdout.encoding
UTF-8

Giriş karakterleriniz terminalin kodlamasıyla kodlanabiliyorsa, Python bunu yapacak ve karşılık gelen baytları şikayet etmeden terminalinize gönderecektir. Uçbirim, girdi baytlarının kodunu çözdükten sonra karakterleri görüntülemek için elinden gelenin en iyisini yapacaktır (en kötü durumda uçbirim yazı tipi bazı karakterlere sahip değildir ve bunun yerine bir tür boş yazacaktır).

Giriş karakterleriniz terminalin kodlamasıyla kodlanamıyorsa, bu, terminalin bu karakterleri görüntülemek için yapılandırılmadığı anlamına gelir. Python şikayet edecek (Python'da a ile UnicodeEncodeErrorkarakter dizisi terminalinize uygun bir şekilde kodlanamaz). Olası tek çözüm, karakterleri görüntüleyebilen bir uçbirim kullanmaktır (uçbirimi, karakterlerinizi temsil edebilecek bir kodlamayı kabul edecek şekilde yapılandırarak veya farklı bir uçbirim programı kullanarak). Farklı ortamlarda kullanılabilen programları dağıtırken bu önemlidir: yazdırdığınız mesajlar kullanıcının terminalinde gösterilebilir olmalıdır. Bu nedenle bazen, yalnızca ASCII karakterleri içeren dizelere bağlı kalmak en iyisidir.

Ancak, programınızın çıktısını yeniden yönlendirdiğinizde veya yönlendirdiğinizde , alıcı programın giriş kodlamasının ne olduğunu bilmek genellikle mümkün değildir ve yukarıdaki kod bazı varsayılan kodlamaları döndürür: Yok (Python 2.7) veya UTF-8 ( Python 3):

% python2.7 -c "import sys; print sys.stdout.encoding" | cat
None
% python3.4 -c "import sys; print(sys.stdout.encoding)" | cat
UTF-8

Bununla birlikte, stdin, stdout ve stderr kodlaması , gerekirse ortam değişkeni aracılığıyla ayarlanabilirPYTHONIOENCODING :

% PYTHONIOENCODING=UTF-8 python2.7 -c "import sys; print sys.stdout.encoding" | cat
UTF-8

Bir terminale yazdırma beklediğinizi üretmiyorsa, manuel olarak yerleştirdiğiniz UTF-8 kodlamasının doğru olup olmadığını kontrol edebilirsiniz; örneğin, yanılmıyorsam ilk karakteriniz ( \u001A) yazdırılamaz .

At http://wiki.python.org/moin/PrintFails , Python 2.x için aşağıdaki gibi bir çözüm bulabiliriz:

import codecs
import locale
import sys

# Wrap sys.stdout into a StreamWriter to allow writing unicode.
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout) 

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni

Python 3 için, daha önce StackOverflow'da sorulan sorulardan birini kontrol edebilirsiniz .


2
@ singularity: Teşekkürler! Python 3 için bazı bilgiler ekledim.
Eric O Lebigot

2
Teşekkür ederim dostum! Bu açıklamaya çok uzun zamandır ihtiyacım vardı ... Size sadece bir olumlu oy verebildiğim için yazık.
mik01aj

3
Yardımcı olduğum için mutluyum @ m01! Bu cevabı yazmanın motivasyonlarından biri, web'de Unicode ve Python hakkında birçok sayfa olmasıydı, ancak ilginç olmalarına rağmen, somut kodlama problemlerini çözmeme asla tam olarak izin vermediklerini keşfettim ... Gerçekten inanıyorum ki, Bu cevapta bulunan ilkeler ve somut kodlama problemlerini çözerken bunları kullanmaya zaman ayırmanın çok faydası var.
Eric O Lebigot

3
Bu, şimdiye kadarki en iyi unicode ve python açıklamasıdır. Python Unicode NASIL belgesi bununla değiştirilmelidir.
stantonk

1
Burada, bu kara tahtaya "sağdan sola geçersiz kılma" karakterini
çizmeme

20

Python bir terminale, dosyaya, boruya vb. Yazarken her zaman Unicode dizelerini kodlar. Bir terminale yazarken Python genellikle terminalin kodlamasını belirleyebilir ve doğru şekilde kullanabilir. Bir dosyaya veya boruya yazarken, açıkça aksi belirtilmedikçe Python varsayılan olarak 'ascii' kodlamasını kullanır. Python'a, çıktı PYTHONIOENCODINGortam değişkeni aracılığıyla borulandığında ne yapılması gerektiği söylenebilir . Bir kabuk, Python çıktısını bir dosyaya veya boruya yönlendirmeden önce bu değişkeni ayarlayarak doğru kodlamanın bilinmesini sağlayabilir.

Sizin durumunuzda, terminalinizin yazı tipinde desteklemediği 4 alışılmadık karakter yazdırdınız. Burada, aslında benim uçbirimim tarafından desteklenen karakterlerle (UTF-8 değil cp437 kullanan) davranışı açıklamaya yardımcı olacak bazı örnekler var.

örnek 1

#codingYorumun, kaynak dosyanın kaydedildiği kodlamayı gösterdiğini unutmayın . Terminalimin yapamadığı kaynaktaki karakterleri destekleyebilmek için utf8'i seçtim. Kodlama, bir dosyaya yeniden yönlendirildiğinde görülebilmesi için stderr'e yeniden yönlendirilir.

#coding: utf8
import sys
uni = u'αßΓπΣσµτΦΘΩδ∞φ'
print >>sys.stderr,sys.stdout.encoding
print uni

Çıktı (doğrudan terminalden çalıştırın)

cp437
αßΓπΣσµτΦΘΩδ∞φ

Python, terminalin kodlamasını doğru bir şekilde belirledi.

Çıktı (dosyaya yeniden yönlendirilir)

None
Traceback (most recent call last):
  File "C:\ex.py", line 5, in <module>
    print uni
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-13: ordinal not in range(128)

Python, kodlamayı belirleyemedi (Yok), bu nedenle 'ascii' varsayılanı kullanıldı. ASCII, yalnızca Unicode'un ilk 128 karakterini dönüştürmeyi destekler.

Çıktı (dosyaya yeniden yönlendirilir, PYTHONIOENCODING = cp437)

cp437

ve çıktı dosyam doğruydu:

C:\>type out.txt
αßΓπΣσµτΦΘΩδ∞φ

Örnek 2

Şimdi kaynağa terminalim tarafından desteklenmeyen bir karakter atacağım:

#coding: utf8
import sys
uni = u'αßΓπΣσµτΦΘΩδ∞φ马' # added Chinese character at end.
print >>sys.stderr,sys.stdout.encoding
print uni

Çıktı (doğrudan terminalden çalıştırın)

cp437
Traceback (most recent call last):
  File "C:\ex.py", line 5, in <module>
    print uni
  File "C:\Python26\lib\encodings\cp437.py", line 12, in encode
    return codecs.charmap_encode(input,errors,encoding_map)
UnicodeEncodeError: 'charmap' codec can't encode character u'\u9a6c' in position 14: character maps to <undefined>

Terminalim son Çince karakteri anlamadı.

Çıkış (doğrudan çalıştır, PYTHONIOENCODING = 437: değiştir)

cp437
αßΓπΣσµτΦΘΩδ∞φ?

Hata işleyicileri kodlama ile belirtilebilir. Bu durumda bilinmeyen karakterler ile değiştirildi ?. ignoreve xmlcharrefreplacediğer bazı seçenekler. UTF8 (tüm Unicode karakterlerini kodlamayı destekler) kullanırken asla değiştirilmeyecektir, ancak karakterleri görüntülemek için kullanılan yazı tipinin bunları desteklemesi gerekir.


"Bir dosyaya veya boruya yazarken Python, açıkça aksi belirtilmedikçe 'ascii' kodlamasına varsayılan olur." Tam olarak doğru değildir. Aslında Python 3, Mac OS X / Fink'te UTF-8 kullanır.
Eric O Lebigot

2
Evet, Python 3 varsayılan olarak 'utf8' olur, ancak OP'nin örneğine göre, varsayılan olarak 'ascii' olan Python 2.X'i kullanıyor.
Mark Tolonen

Manipüle ederek doğru çıktı alamadım PYTHONIOENCODING. print string.encode("UTF-8")@ İsmail tarafından önerildiği gibi yapmak benim için çalıştı.
üçlü

chcpkod sayfası desteklemese bile yazı tipiniz destekliyorsa Çince karakterleri görebilirsiniz . Bunu önlemek UnicodeEncodeError: 'charmap'için win-unicode-consolepaketi kurabilirsiniz .
jfs

Benim sorunum python-gitlab CLI'nin Çince karakterleri cmd'de iyi yazdırmasıdır, ancak karakterler dosyalara yeniden yönlendirildikten sonra anlamsızdır. PYTHONIOENCODING=utf-8sorunu çözer.
ElpieKay

12

Yazdırırken kodlayın

uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni.encode("utf-8")

Bunun nedeni, komut dosyasını manuel olarak çalıştırdığınızda, python, terminale çıktılamadan önce kodlamasıdır, boruladığınızda python kendisini kodlamaz, bu nedenle G / Ç yaparken manuel olarak kodlamanız gerekir.


4
Hala WTH'nin burada devam ettiği sorusuna cevap vermiyor. Neden, birdenbire, yalnızca yeniden yönlendirildiğinde kodlamaya karar verir, bunun sürece tamamen şeffaf olması gerekir.
Maxim Sloyko

Python yeniden yönlendirme yaparken onu neden kodlamıyor? Python, sadece zor olmak için işleri farklı şekilde yapacağını açıkça kontrol ediyor ve karar veriyor mu?
Arafangion

1
Python'un iki durumu ayırt etmenin bir yolu var mı? Bilmesinin bir yolu olmadığını (şimdiye kadar ...) düşünüyorum.
zedoo

4
Python, çıktının bir uçbirim olup olmadığını kontrol edebilir, bir boruya çıktı veriyorsa, uçbirim türü "aptal" olacaktır. Sanırım "aptal" size Python'un bu durumda neden otomatik bir şey yapmaya çalışmadığını söylemeli, başarısız olabilir.
ismail

1
ortam utf-8 ile uyumsuz bir karakter kodlaması kullanıyorsa (örneğin, Windows'ta yaygındır) mojibake üretir. Ortamınızın karakter kodlamasını komut dosyanızın içine kodlamayın. Yerel ayarınızı veya PYTHONIOENCODING'i yapılandırın veya kurun win-unicode-console(Windows) veya bir komut satırı parametresini kabul edin (gerekirse).
jfs
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.