Python'da stdout oluştururken doğru kodlamayı ayarlama


343

Bir Python programının çıktısını oluştururken, Python yorumlayıcısı kodlama konusunda kafanız karışır ve Yok olarak ayarlar. Bu, şöyle bir program anlamına gelir:

# -*- coding: utf-8 -*-
print u"åäö"

normal çalıştığında iyi çalışır, ancak aşağıdakilerle başarısız olur:

UnicodeEncodeError: 'ascii' codec bileşeni 0 konumunda u '\ xa0' karakterini kodlayamıyor: sıra değeri aralıkta değil (128)

bir boru dizisinde kullanıldığında.

Boru döşerken bunu yapmanın en iyi yolu nedir? Sadece kabuğu / dosya sistemini / ne kullanıyorsa onu kodlamayı kullanabilir miyim?

Şimdiye kadar gördüğüm öneriler site.py'nizi doğrudan değiştirmek veya bu hack'i kullanarak varsayılan kodlamayı sabit olarak kodlamaktır:

# -*- coding: utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
print u"åäö"

Boruların çalışmasını sağlamanın daha iyi bir yolu var mı?



2
Windows'ta bu sorunla karşılaşırsanız, chcp 65001komut dosyanızı çalıştırmadan önce de çalıştırabilirsiniz . Bunun sorunları olabilir, ancak genellikle yardımcı olur ve çok fazla yazmayı gerektirmez (daha az set PYTHONIOENCODING=utf_8).
Tomasz Gandor

chcp komutu PYTHONIOENCODING ayarıyla aynı değildir. Ben chcp sadece terminal kendisi için yapılandırma ve bir dosyaya yazma ile ilgisi olmadığını düşünüyorum (bu stdout boru yaparken ne yapıyor). setx PYTHONENCODING utf-8Yazmayı kaydetmek istiyorsanız kalıcı hale getirmeye çalışın .
ejm


Biraz ilgili bir sorunla karşılaştım ve burada bir çözüm buldum -> stackoverflow.com/questions/48782529/…
bkrishna2006

Yanıtlar:


162

Kodunuz bir komut dosyasında çalıştırıldığında çalışır, çünkü Python çıkışı terminal uygulamanızın kullandığı kodlamaya kodlar. Boru kullanıyorsanız, kendiniz kodlamanız gerekir.

Temel kural: Her zaman dahili olarak Unicode kullanın. Aldığınız kodları çözün ve gönderdiğiniz şeyleri kodlayın.

# -*- coding: utf-8 -*-
print u"åäö".encode('utf-8')

Başka bir didaktik örnek, ISO-8859-1 ve UTF-8 arasında dönüştürme yapan ve aradaki her şeyi büyük yapan bir Python programıdır.

import sys
for line in sys.stdin:
    # Decode what you receive:
    line = line.decode('iso8859-1')

    # Work with Unicode internally:
    line = line.upper()

    # Encode what you send:
    line = line.encode('utf-8')
    sys.stdout.write(line)

Sistem varsayılan kodlamasını ayarlamak kötü bir fikirdir, çünkü kullandığınız bazı modüller ve kütüphaneler ASCII olduğuna güvenebilir. Yapma.


11
Sorun, kullanıcının kodlamayı açıkça belirtmek istememesi. Sadece IO için Unicode kullanmak istiyor. Kullandığı kodlama, terminal uygulama ayarlarında değil, yerel ayarlarda belirtilen bir kodlama olmalıdır. AFAIK, Python 3 bu durumda bir yerel ayar kodlaması kullanır . Değişmek sys.stdoutdaha hoş bir yol gibi görünüyor.
Andrey Vlasovskikh

4
Kodlama / kod çözme çağrısı eksik olduğunda veya bir yere çok kez eklendiğinde, her dizeyi açıkça kodlama / kod çözme hatalara neden olur. Çıkış kodlaması, çıkış bir terminal olduğunda ayarlanabilir, bu nedenle çıkış bir terminal olmadığında ayarlanabilir. Bunu belirtmek için standart bir LC_CTYPE ortamı bile vardır. Python'da buna saygı duymaz.
Rasmus Kaj

65
Bu cevap yanlış. Sen gerektiğini değil elle her bir giriş ve programın çıkışına dönüştürerek olmak; bu kırılgan ve tamamen sürdürülemez.
Glenn Maynard

29
@Glenn Maynard: Peki IYO doğru cevap nedir? Bize sadece 'Bu cevap yanlış'
demekten daha faydalıdır

14
@smci: cevap betiğinizi değiştirmez, betiğin PYTHONIOENCODINGPython 2'deki stdout'unu yeniden yönlendiriyorsanız ayarlayın .
jfs

168

İlk olarak, bu çözümle ilgili olarak:

# -*- coding: utf-8 -*-
print u"åäö".encode('utf-8')

Her seferinde belirli bir kodlamayla açıkça yazdırmak pratik değildir. Bu tekrarlayan ve hataya yatkın olurdu.

Daha iyi bir çözüm, sys.stdoutprogramın başlangıcında değiştirmek , seçilen bir kodlamayla kodlamaktır. İşte Python'da bulduğum bir çözüm : sys.stdout.encoding nasıl seçilir? , özellikle "toka" tarafından yapılan bir yorum:

import sys
import codecs
sys.stdout = codecs.getwriter('utf8')(sys.stdout)

7
ne yazık ki, sys.stdout'u yalnızca unicode kabul edecek şekilde değiştirmek, kodlanmış bayt dizilerini kabul etmesini bekleyen birçok kütüphaneyi kırar.
nosklo

6
nosklo: Peki çıktı bir terminal olduğunda nasıl güvenilir ve otomatik olarak çalışabilir?
Rasmus Kaj

3
@Rasmus Kaj: sadece kendi unicode yazdırma işlevinizi tanımlayın ve her unicode yazdırmak istediğinizde kullanın: def myprint(unicodeobj): print unicodeobj.encode('utf-8')- terminal kodlamasını inceleyerek otomatik olarak algılarsınız sys.stdout.encoding, ancak durumun nerede olduğunu düşünmelisiniz None(yani çıktıyı bir dosyaya yönlendirirken) bu yüzden zaten ayrı bir işleve ihtiyacınız var.
nosklo

3
@nosklo: Bu, sys.stdout'un yalnızca Unicode'u kabul etmesini sağlamaz. Hem str hem de unicode'u bir StreamWriter'a geçirebilirsiniz.
Glenn Maynard

9
Bu cevabın python2 için tasarlandığını varsayıyorum. Hem python2 hem de python3'ü desteklemeyi amaçlayan kodda buna dikkat edin . Benim için python3 altında koşmak bir şeyler kırıyor.
wim

130

"PYTHONIOENCODING" ortam değişkenini "utf_8" olarak değiştirmeyi deneyebilirsiniz. Bu sorunla ilgili sorunum hakkında bir sayfa yazdım .

Tl; blog yazısının dr:

import sys, locale, os
print(sys.stdout.encoding)
print(sys.stdout.isatty())
print(locale.getpreferredencoding())
print(sys.getfilesystemencoding())
print(os.environ["PYTHONIOENCODING"])
print(chr(246), chr(9786), chr(9787))

sana verir

utf_8
False
ANSI_X3.4-1968
ascii
utf_8
ö ☺ ☻

2
Belki çalışmıyor sys.stdout.encoding değiştirilmesi, ancak sys.stdout değişen çalışır: sys.stdout = codecs.getwriter(encoding)(sys.stdout). Bu, python programı içinden yapılabilir, böylece kullanıcı bir env değişkeni ayarlamak zorunda değildir.
blueFast

7
@ jeckyll2hide: PYTHONIOENCODINGçalışıyor. Baytların metin olarak nasıl yorumlandığı kullanıcı ortamı tarafından tanımlanır . Komut dosyanız, hangi karakter kodlamasının kullanılacağını kullanıcı ortamını varsaymamalı ve dikte etmemelidir. Python ayarları otomatik olarak almazsa PYTHONIOENCODING, komut dosyanız için ayarlanabilir. Çıktı bir dosyaya / borsaya yönlendirilmedikçe buna ihtiyacınız olmamalıdır.
jfs

8
+1. Dürüst olmak gerekirse bu bir Python hatası olduğunu düşünüyorum. Çıktıyı yeniden yönlendirdiğimde, terminalde, ancak bir dosyada aynı baytları istiyorum. Belki herkes için değil ama iyi bir varsayılan. Genellikle "işe yarayan" önemsiz bir işlemle ilgili hiçbir açıklama yapmadan zorlanmanın kötü bir varsayılanıdır.
Yılan

@SnakE: Python'un uygulanmasının neden başlangıçta stdout'ta demir kaplı ve kalıcı bir kodlama seçimini neden zorunlu kıldığını rasyonelleştirebilmemin tek yolu, daha sonra kötü kodlanmış herhangi bir şeyin ortaya çıkmasını önlemek olabilir. Ya da değiştirmek sadece uygulanmayan bir özelliktir, bu durumda kullanıcının daha sonra değiştirmesine izin vermek makul bir Python özellik isteği olacaktır.
daveagp

2
@daveagp Demek istediğim, programımın davranışı, yönlendirilip yönlendirilmemesine bağlı olmamalı --- gerçekten istemediğim sürece, bu durumda kendim uygularım. Python, diğer konsol araçlarıyla olan deneyimime aykırı davranıyor. Bu en az sürpriz ilkesini ihlal eder. Çok güçlü bir gerekçe olmadıkça bunu bir tasarım hatası olarak görüyorum.
Yılan

62
export PYTHONIOENCODING=utf-8

işi yap, ama python'un üzerine ayarlayamaz ...

Yapabileceğimiz, ayarlanıp ayarlanmadığını doğrulamak ve kullanıcıya çağrı komut dosyasından önce ayarlamasını söyle:

if __name__ == '__main__':
    if (sys.stdout.encoding is None):
        print >> sys.stderr, "please set python env PYTHONIOENCODING=UTF-8, example: export PYTHONIOENCODING=UTF-8, when write to stdout."
        exit(1)

Yorumu yanıtlamak için güncelleme: sorun sadece stdout'a borulama yaparken var. Fedora 25 Python 2.7.13'te test ettim

python --version
Python 2.7.13

kedi b.py

#!/usr/bin/env python
#-*- coding: utf-8 -*-
import sys

print sys.stdout.encoding

çalışıyor ./b.py

UTF-8

çalışan ./b.py | az

None

2
Bu kontrol Python 2.7.13'te çalışmaz. sys.stdout.encoding, LC_CTYPEyerel ayar değerine göre otomatik olarak ayarlanır .
amphetamachine

1
mail.python.org/pipermail/python-list/2011-June/605938.html orada örnek hala iş, yani ./a.py> out.txt sys.stdout.encoding is Yok kullandığınızda
Sérgio

Ben Backblaze B2 senkronizasyon komut dosyası ile benzer bir sorun vardı ve ihracat PYTHONIOENCODING = utf-8 sorunumu çözdü. Debian Stretch üzerine Python 2.7.
0x3333

5

Geçen hafta benzer bir sorun yaşadım . Benim IDE (PyCharm) düzeltmek kolay oldu.

İşte benim düzeltmem:

PyCharm menü çubuğundan başlayarak: Dosya -> Ayarlar ... -> Editör -> Dosya Kodlamaları, ardından şunu ayarlayın: "IDE Kodlama", "Proje Kodlama" ve "Özellikler dosyaları için varsayılan kodlama" ALL to UTF-8 ve şimdi çalışıyor bir cazibe gibi.

Bu yardımcı olur umarım!


4

Craig McQueen'in cevabının tartışmalı temizlenmiş bir versiyonu.

import sys, codecs
class EncodedOut:
    def __init__(self, enc):
        self.enc = enc
        self.stdout = sys.stdout
    def __enter__(self):
        if sys.stdout.encoding is None:
            w = codecs.getwriter(self.enc)
            sys.stdout = w(sys.stdout)
    def __exit__(self, exc_ty, exc_val, tb):
        sys.stdout = self.stdout

Kullanımı:

with EncodedOut('utf-8'):
    print u'ÅÄÖåäö'

2

Ben bir çağrı ile "otomatik" olabilir:

def __fix_io_encoding(last_resort_default='UTF-8'):
  import sys
  if [x for x in (sys.stdin,sys.stdout,sys.stderr) if x.encoding is None] :
      import os
      defEnc = None
      if defEnc is None :
        try:
          import locale
          defEnc = locale.getpreferredencoding()
        except: pass
      if defEnc is None :
        try: defEnc = sys.getfilesystemencoding()
        except: pass
      if defEnc is None :
        try: defEnc = sys.stdin.encoding
        except: pass
      if defEnc is None :
        defEnc = last_resort_default
      os.environ['PYTHONIOENCODING'] = os.environ.get("PYTHONIOENCODING",defEnc)
      os.execvpe(sys.argv[0],sys.argv,os.environ)
__fix_io_encoding() ; del __fix_io_encoding

Evet, bu "setenv" başarısız olursa burada sonsuz bir döngü elde etmek mümkündür.


1
ilginç, ama bir boru bu konuda mutlu görünmüyor
n611x007

2

Burada olup biteni fark etmeden önce uzun zamandır denemem gereken bir şeyden bahsettiğimi düşündüm. Buradaki herkes için bu kadar açık olabilir, bundan bahsetmekten rahatsız olmamışlardır. Ama eğer olsaydı, bu prensipte bana yardımcı olurdu ...!

Not: Ben özellikle Jython , v 2.7 kullanıyorum, bu yüzden muhtemelen bu CPython için geçerli olmayabilir ...

NB2: .py dosyamın ilk iki satırı şöyledir:

# -*- coding: utf-8 -*-
from __future__ import print_function

"%" (AKA "enterpolasyon operatörü") dize oluşturma mekanizması da EK sorunlara neden oluyor ... "Ortam" ın varsayılan kodlaması ASCII ise ve bunun gibi bir şey yapmaya çalışırsanız

print( "bonjour, %s" % "fréd" )  # Call this "print A"

Eclipse'de çalışırken zorluk yaşamayacaksınız ... Windows CLI'de (DOS penceresi) kodlamanın kod sayfası 850 olduğunu göreceksiniz (Windows 7 işletim sistemim) veya benzer bir şey , bu da en azından Avrupa aksanlı karakterleri işleyebilir, bu yüzden Çalışacağım.

print( u"bonjour, %s" % "fréd" ) # Call this "print B"

de çalışacak.

OTOH, CLI'den bir dosyaya yönlendirirseniz, stdout kodlaması, varsayılan olarak ASCII'ye (işletim sistemimde), yukarıdaki baskılardan herhangi birini işleyemeyecek ... olacaktır (korkunç kodlama) hata).

Böylece stdout'unuzu kullanarak yeniden yönlendirmeyi düşünebilirsiniz.

sys.stdout = codecs.getwriter('utf8')(sys.stdout)

ve CLI boru tesisatında bir dosyaya çalışmayı deneyin ... Çok tuhaf, yukarıdaki A baskısı işe yarayacak ... Ama yukarıdaki B baskısı kodlama hatasını atacak! Ancak aşağıdakiler sorunsuz çalışır:

print( u"bonjour, " + "fréd" ) # Call this "print C"

Geldiğim sonuç (geçici olarak), "u" öneki kullanılarak bir Unicode dizesi olarak belirtilen bir dize ,% işlem mekanizmasına gönderilirse, varsayılan ortam kodlamasının kullanımını içeriyormuş gibi görünmesidir . stdout'u yeniden yönlendirecek şekilde ayarlayıp ayarlamadığınızı!

İnsanların bununla nasıl başa çıktığı bir seçim meselesidir. Unicode uzmanının neden olduğunu, bir şekilde yanlış anlasam da, bunun için tercih edilen çözümün ne olduğunu, CPython için de geçerli olup olmadığını, Python 3'te olup olmadığını söylemeyi memnuniyetle karşılarım .


Bu garip değil, çünkü "fréd"bir bayt dizisi ve bir Unicode dizesi değil, bu yüzden codecs.getwritersarıcı onu yalnız bırakacak. Bir lidere ihtiyacınız var u, ya da from __future__ import unicode_literals.
Matthias Urlichs

@MatthiasUrlichs Tamam ... teşekkürler ... Ama sadece BT'nin en sinir bozucu yönlerinden birini kodlarken buluyorum. Anlayışınızı nereden alıyorsunuz? Örneğin, burada kodlama hakkında başka bir soru daha gönderdim: stackoverflow.com/questions/44483067/… : bu Java, Eclipse, Cygwin & Gradle hakkında. Uzmanlığınız bu kadar ileri giderse, lütfen yardım edin ... her şeyden önce nerede daha fazla bilgi edinebileceğimi bilmek istiyorum!
mike kemirgen

1

Eski bir uygulamada bu sorunla karşılaştım ve neyin basıldığını belirlemek zordu. Bu hack konusunda kendime yardımcı oldum:

# encoding_utf8.py
import codecs
import builtins


def print_utf8(text, **kwargs):
    print(str(text).encode('utf-8'), **kwargs)


def print_utf8(fn):
    def print_fn(*args, **kwargs):
        return fn(str(*args).encode('utf-8'), **kwargs)
    return print_fn


builtins.print = print_utf8(print)

Senaryomun üstünde, test.py:

import encoding_utf8
string = 'Axwell Λ Ingrosso'
print(string)

Bunun TÜM çağrıları bir kodlama kullanmak üzere değiştirdiğini unutmayın, böylece konsolunuz bunu yazdıracaktır:

$ python test.py
b'Axwell \xce\x9b Ingrosso'

1

Windows'ta, bir düzenleyiciden (Yüce Metin gibi) bir Python kodu çalıştırırken bu sorunu çok sık yaşadım, ancak değil komut satırından çalışan eğer.

Bu durumda, editörünüzün parametrelerini kontrol edin. SublimeText durumunda, bu Python.sublime-buildçözüldü:

{
  "cmd": ["python", "-u", "$file"],
  "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
  "selector": "source.python",
  "encoding": "utf8",
  "env": {"PYTHONIOENCODING": "utf-8", "LANG": "en_US.UTF-8"}
}
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.