Configurando a codificação correta ao canalizar stdout em Python

343

Ao canalizar a saída de um programa Python, o interpretador Python fica confuso sobre a codificação e o define como Nenhum. Isso significa um programa como este:

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

funcionará bem quando executado normalmente, mas falhará com:

UnicodeEncodeError: o codec 'ascii' não pode codificar o caractere u '\ xa0' na posição 0: ordinal fora do intervalo (128)

quando usado em uma sequência de tubulação.

Qual é a melhor maneira de fazer isso funcionar na tubulação? Posso apenas dizer a ele para usar a codificação que seja o shell / sistema de arquivos / o que estiver usando?

As sugestões que eu vi até agora são modificar o site.py diretamente ou codificar a codificação padrão usando este hack:

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

Existe uma maneira melhor de fazer a tubulação funcionar?

Joakim Lundborg
fonte
11
Veja também stackoverflow.com/questions/4545661/…
ShreevatsaR
2
Se você tiver esse problema no Windows, também poderá executar chcp 65001antes de executar seu script. Isso pode ter problemas, mas geralmente ajuda e não requer muita digitação (menos que set PYTHONIOENCODING=utf_8).
Tomasz Gandor
O comando chcp não é o mesmo que configurar PYTHONIOENCODING. Eu acho que o chcp é apenas uma configuração para o próprio terminal e não tem nada a ver com a gravação em um arquivo (que é o que você está fazendo ao canalizar o stdout). Tente setx PYTHONENCODING utf-8torná-lo permanente, se você quiser salvar a digitação.
EJM
Eu enfrentei um problema um pouco relacionado e encontrei uma solução aqui -> stackoverflow.com/questions/48782529/…
bkrishna2006

Respostas:

162

Seu código funciona quando executado em um script porque o Python codifica a saída para qualquer codificação que seu aplicativo de terminal estiver usando. Se você estiver usando um encanamento, deverá codificá-lo.

Uma regra prática é: sempre use Unicode internamente. Decodifique o que você recebe e codifique o que você envia.

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

Outro exemplo didático é um programa Python para converter entre ISO-8859-1 e UTF-8, tornando tudo maiúsculo.

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)

Definir a codificação padrão do sistema é uma má idéia, porque alguns módulos e bibliotecas que você usa podem confiar no fato de serem ASCII. Não faça isso.

nosklo
fonte
11
O problema é que o usuário não deseja especificar a codificação explicitamente. Ele quer apenas usar o Unicode para E / S. E a codificação que ele usa deve ser especificada nas configurações de localidade, não nas configurações do aplicativo de terminal. AFAIK, Python 3 usa uma codificação de localidade neste caso. Mudar sys.stdoutparece ser uma maneira mais agradável.
Andrey Vlasovskikh 02/04/10
4
A codificação / decodificação de todas as strings é obrigada a causar bugs quando uma chamada de codificação ou decodificação está ausente ou é adicionada uma vez a mais em algum lugar. A codificação de saída pode ser definida quando a saída é um terminal, e pode ser configurada quando a saída não é um terminal. Existe até um ambiente LC_CTYPE padrão para especificá-lo. É um mas em python que não respeita isso.
Rasmus Kaj
65
Esta resposta está errada. Você não deve converter manualmente em cada entrada e saída do seu programa; isso é quebradiço e completamente insustentável.
Glenn Maynard
29
@ Glenn Maynard: então, qual é a resposta certa da IYO? É mais útil para nos dizer do que apenas dizer 'Esta resposta está errada'
SMCI
14
@smci: a resposta é não modificar seu script, definir PYTHONIOENCODINGse você está redirecionando stdout do script em Python 2.
jfs
168

Primeiro, com relação a esta solução:

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

Não é prático imprimir explicitamente com uma determinada codificação sempre. Isso seria repetitivo e propenso a erros.

Uma solução melhor é mudar sys.stdoutno início do seu programa, codificar com uma codificação selecionada. Aqui está uma solução que encontrei no Python: Como o sys.stdout.encoding é escolhido? , em particular um comentário de "toka":

import sys
import codecs
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
Craig McQueen
fonte
7
infelizmente, alterar o sys.stdout para aceitar apenas unicode quebra muitas bibliotecas que esperam que ele aceite codificações de bytes codificadas.
Nosklo 04/12/2009
6
nosklo: Então, como ele pode funcionar de maneira confiável e automática quando a saída é um terminal?
Rasmus Kaj
3
@Rasmus Kaj: basta definir sua própria função de impressão unicode e usá-la sempre que quiser imprimir unicode: def myprint(unicodeobj): print unicodeobj.encode('utf-8')- você detecta automaticamente a codificação do terminal inspecionando sys.stdout.encoding, mas deve considerar o caso em que está None(por exemplo, ao redirecionar a saída para um arquivo) então você precisa de uma função separada de qualquer maneira.
Nosklo 31/05
3
@nosklo: Isso não faz com que o sys.stdout aceite apenas Unicode. Você pode passar str e unicode para um StreamWriter.
Glenn Maynard
9
Suponho que esta resposta foi planejada para python2. Tenha cuidado com isso no código que se destina a suportar python2 e python3 . Para mim, está quebrando coisas quando executado em python3.
Wim
130

Você pode tentar alterar a variável de ambiente "PYTHONIOENCODING" para "utf_8". Eu escrevi uma página no meu calvário com este problema .

Tl; dr da postagem do blog:

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))

da-te

utf_8
False
ANSI_X3.4-1968
ascii
utf_8
ö ☺ ☻
daveagp
fonte
2
Alterar sys.stdout.encoding talvez não funciona, mas mudando sys.stdout funciona: sys.stdout = codecs.getwriter(encoding)(sys.stdout). Isso pode ser feito no programa python, para que o usuário não seja forçado a definir uma variável env.
blueFast
7
@ jeckyll2hide: PYTHONIOENCODINGfunciona. Como os bytes são interpretados como um texto é definido pelo ambiente do usuário . Seu script não deve assumir e ditar ao ambiente do usuário qual codificação de caracteres usar. Se o Python não pegar as configurações automaticamente, PYTHONIOENCODINGpoderá ser definido para o seu script. Você não deve precisar disso, a menos que a saída seja redirecionada para um arquivo / canal.
JFS
8
+1. Honestamente, acho que é um bug do Python. Quando redireciono a saída, quero os mesmos bytes que estariam no terminal, mas em um arquivo. Talvez não seja para todos, mas é um bom padrão. Falhando sem explicação em uma operação trivial que geralmente "simplesmente funciona" é um padrão ruim.
Cobra
@SnakE: a única maneira de racionalizar o porquê da implementação do Python intencionalmente impor uma escolha permanente e permanente de codificação no stdout no momento da inicialização, pode ser para impedir que qualquer coisa mal codificada seja lançada mais tarde. Ou alterá-lo é apenas um recurso não implementado; nesse caso, permitir ao usuário alterá-lo mais tarde seria uma solicitação razoável de recurso do Python.
Daveagp
2
@daveagp Meu argumento é que o comportamento do meu programa não deve depender se ele é redirecionado ou não - a menos que eu realmente queira, caso em que eu mesmo o implemento. O Python se comporta de maneira contrária à minha experiência com outras ferramentas de console. Isso viola o princípio da menor surpresa. Considero isso uma falha de design, a menos que haja uma lógica muito forte.
Cobra
62
export PYTHONIOENCODING=utf-8

fazer o trabalho, mas não pode configurá-lo no próprio python ...

o que podemos fazer é verificar se não está definido e dizer ao usuário para defini-lo antes do script de chamada com:

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)

Atualize para responder ao comentário: o problema existe apenas ao passar para o stdout. Eu testei no Fedora 25 Python 2.7.13

python --version
Python 2.7.13

cat b.py

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

print sys.stdout.encoding

running ./b.py

UTF-8

executando ./b.py | Menos

None
Sérgio
fonte
2
Essa verificação não funciona no Python 2.7.13. sys.stdout.encodingé definido automaticamente com base no LC_CTYPEvalor da localidade.
Amphetamachine
11
mail.python.org/pipermail/python-list/2011-June/605938.html o exemplo ainda funciona, ou seja, quando você usa ./a.py> out.txt sys.stdout.encoding é Nenhum
Sérgio
Eu tive um problema semelhante com um script de sincronização do Backblaze B2 e a exportação PYTHONIOENCODING = utf-8 resolveu meu problema. Python 2.7 no Debian Stretch.
0x3333 28/03/19
5

Eu tive um problema semelhante na semana passada . Foi fácil de corrigir no meu IDE (PyCharm).

Aqui estava minha correção:

A partir da barra de menus do PyCharm: Arquivo -> Configurações ... -> Editor -> Codificações de arquivo e defina: "IDE Encoding", "Project Encoding" e "Project Encoding" e "Default codification for properties files" ALL para UTF-8 e agora ela funciona como um encanto.

Espero que isto ajude!

CLaFarge
fonte
4

Uma versão higienizada discutível da resposta de Craig McQueen.

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

Uso:

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

Eu poderia "automatizá-lo" chamando:

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

Sim, é possível obter um loop infinito aqui se esse "setenv" falhar.

não
fonte
11
interessante, mas um cachimbo não parece estar feliz com isso
n611x007
2

Eu apenas pensei em mencionar algo aqui que eu tive que passar muito tempo experimentando antes de finalmente perceber o que estava acontecendo. Isso pode ser tão óbvio para todos aqui que eles nem se deram ao trabalho de mencioná-lo. Mas teria me ajudado se eles tivessem, então, nesse princípio ...!

NB: Estou usando o Jython especificamente, v 2.7, portanto, possivelmente isso pode não se aplicar ao CPython ...

NB2: as duas primeiras linhas do meu arquivo .py aqui são:

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

O mecanismo de construção de cadeia "%" (AKA "interpolation operator") também causa problemas ADICIONAIS ... Se a codificação padrão do "ambiente" for ASCII e você tentar fazer algo como

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

Você não terá dificuldade em executar no Eclipse ... Em uma CLI do Windows (janela do DOS), você encontrará que a codificação é a página de código 850 (meu sistema operacional Windows 7) ou algo semelhante, que pode manipular pelo menos caracteres acentuados europeus, por isso vou trabalhar.

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

também irá funcionar.

Se, OTOH, você direcionar para um arquivo da CLI, a codificação stdout será None, que será padronizada como ASCII (no meu sistema operacional de qualquer maneira), que não poderá lidar com nenhuma das impressões acima ... (codificação temida erro).

Então, você pode pensar em redirecionar seu stdout usando

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

e tente executar na tubulação da CLI para um arquivo ... Muito estranhamente, a impressão A acima funcionará ... Mas a impressão B acima gerará o erro de codificação! No entanto, o seguinte funcionará bem:

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

A conclusão a que cheguei (provisoriamente) é que, se uma string especificada como Unicode usando o prefixo "u" for submetida ao mecanismo de% -handling, ela envolverá o uso da codificação de ambiente padrão, independentemente de se você configurou o stdout para redirecionar!

Como as pessoas lidam com isso é uma questão de escolha. Gostaria de receber um especialista em Unicode para dizer por que isso acontece, se de alguma forma eu entendi errado, qual a solução preferida para isso, se também se aplica ao CPython , se ocorre no Python 3, etc., etc.

microfone roedor
fonte
Isso não é estranho, porque "fréd"é uma sequência de bytes e não uma sequência Unicode, portanto o codecs.getwriterwrapper o deixará em paz. Você precisa de uma liderança u, ou from __future__ import unicode_literals.
Matthias Urlichs
@MatthiasUrlichs OK ... obrigado ... Mas acho que a codificação é um dos aspectos mais irritantes da TI. De onde você tira sua compreensão? Por exemplo, acabei de postar outra pergunta sobre codificação aqui: stackoverflow.com/questions/44483067/… : trata-se de Java, Eclipse, Cygwin & Gradle. Se sua experiência for tão longe, ajude ... acima de tudo, gostaria de saber onde aprender mais!
mike roedor
1

Encontrei esse problema em um aplicativo herdado e era difícil identificar onde estava o conteúdo impresso. Eu me ajudei com esse hack:

# 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)

No topo do meu script, test.py:

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

Observe que isso altera TODAS as chamadas para impressão para usar uma codificação; portanto, seu console imprimirá o seguinte:

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

No Windows, tive esse problema com muita frequência ao executar um código Python a partir de um editor (como Sublime Text), mas não se o executasse na linha de comando.

Nesse caso, verifique os parâmetros do seu editor. No caso do SublimeText, isso Python.sublime-buildresolveu:

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