Por que NÃO devemos usar sys.setdefaultencoding (“utf-8”) em um script py?

166

Eu vi alguns scripts py que usam isso na parte superior do script. Em que casos se deve usá-lo?

import sys
reload(sys)
sys.setdefaultencoding("utf-8")
mlzboy
fonte
2
há um problema com a utilização deste em ipython, tempo% pára de funcionar github.com/ipython/ipython/issues/8071
seanv507
3
@ seanv507, leia as respostas - usá-lo é sério desanimado
Alastair McCormack
2
Como isso não é uma duplicata exata dos perigos de sys.setdefaultencoding ('utf-8') ? Embora essa pergunta (2010) anteceda essa (2015)? Mas essa pergunta também tem boas respostas. O que fazer? Além disso, para ficar claro, essa pergunta só faz sentido no Python 2 e não 3, mas isso não é marcado ou mencionado em nenhum lugar.
SMCI
vale a pena ler antes de mergulhar respostas SO: pythonhosted.org/kitchen/unicode-frustrations.html
ccpizza

Respostas:

141

Conforme a documentação: Isso permite alternar do ASCII padrão para outras codificações, como UTF-8, que o tempo de execução do Python usará sempre que for necessário decodificar um buffer de cadeia para unicode.

Esta função está disponível apenas no momento da inicialização do Python, quando o Python verifica o ambiente. Ele deve ser chamado em um módulo de todo o sistema sitecustomize.py. Após a avaliação deste módulo, a setdefaultencoding()função é removida do sysmódulo.

A única maneira de realmente usá-lo é com um hack de recarga que traz o atributo de volta.

Além disso, o uso de sys.setdefaultencoding()sempre foi desencorajado e tornou-se um não operacional no py3k. A codificação do py3k é conectada por fio a "utf-8" e sua alteração gera um erro.

Sugiro algumas dicas para leitura:

pyfunc
fonte
6
Ótimas coisas, embora haja um pouco de morte por muita informação aqui. Eu aprendi a mais apenas incidindo sobre este artigo: blog.notdot.net/2010/07/Getting-unicode-right-in-Python
mbb
3
Gostaria de acrescentar que a codificação padrão também é usada para codificação (ao escrever para sys.stdoutquando tiver uma Nonecodificação, como ao redirecionar a saída de um programa Python).
Eric O Lebigot
14
+1 para "o uso de sys.setdefaultencoding()sempre foi desencorajado"
JFS
7
'hard-wired to utf-8' não é verdade, não é hardwired e nem sempre é UTF-8. LC_ALL=en_US.UTF-8 python3 -c 'import sys; print(sys.stdout.encoding)'UTF-8, mas LC_ALL=C python3 -c 'import sys; print(sys.stdout.encoding)'ANSI_X3.4-1968(ou talvez algo mais)
Tino
7
@Tino, a codificação do console é separada da codificação padrão.
Alastair McCormack
59

tl; dr

A resposta é NUNCA ! (a menos que você realmente saiba o que está fazendo)

9/10 vezes a solução pode ser resolvida com um entendimento adequado de codificação / decodificação.

1/10 pessoas têm um local ou ambiente definido incorretamente e precisam definir:

PYTHONIOENCODING="UTF-8"  

em seu ambiente para corrigir problemas de impressão do console.

O que isso faz?

sys.setdefaultencoding("utf-8")(marcado para evitar reutilização) altera a codificação / decodificação padrão usada sempre que o Python 2.x precisar converter um Unicode () em um str () (e vice-versa) e a codificação não for fornecida. Ou seja:

str(u"\u20AC")
unicode("€")
"{}".format(u"\u20AC") 

No Python 2.x, a codificação padrão é definida como ASCII e os exemplos acima falharão com:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128)

(Meu console está configurado como UTF-8, portanto "€" = '\xe2\x82\xac', exceção \xe2)

ou

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

sys.setdefaultencoding("utf-8")permitirá que eles funcionem para mim , mas não necessariamente funcionará para pessoas que não usam UTF-8. O padrão do ASCII garante que as suposições de codificação não sejam inseridas no código

Console

sys.setdefaultencoding("utf-8")também tem o efeito colateral de parecer consertar sys.stdout.encoding, usado ao imprimir caracteres no console. O Python usa a localidade do usuário (Linux / OS X / Un * x) ou a página de código (Windows) para definir isso. Ocasionalmente, a localidade do usuário é interrompida e requer apenas PYTHONIOENCODINGa correção da codificação do console .

Exemplo:

$ export LANG=en_GB.gibberish
$ python
>>> import sys
>>> sys.stdout.encoding
'ANSI_X3.4-1968'
>>> print u"\u20AC"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u20ac' in position 0: ordinal not in range(128)
>>> exit()

$ PYTHONIOENCODING=UTF-8 python
>>> import sys
>>> sys.stdout.encoding
'UTF-8'
>>> print u"\u20AC"
€

O que há de tão ruim com sys.setdefaultencoding ("utf-8") ?

As pessoas desenvolvem o Python 2.x há 16 anos, entendendo que a codificação padrão é ASCII. UnicodeErrorOs métodos de tratamento de exceção foram gravados para manipular conversões de seqüência de caracteres em Unicode em seqüências que contêm não ASCII.

De https://anonbadger.wordpress.com/2015/06/16/why-sys-setdefaultencoding-will-break-code/

def welcome_message(byte_string):
    try:
        return u"%s runs your business" % byte_string
    except UnicodeError:
        return u"%s runs your business" % unicode(byte_string,
            encoding=detect_encoding(byte_string))

print(welcome_message(u"Angstrom (Å®)".encode("latin-1"))

Antes de definir a codificação padrão, esse código não seria capaz de decodificar o "Å" na codificação ascii e, em seguida, entraria no manipulador de exceções para adivinhar a codificação e transformá-la adequadamente em unicode. Impressão: Angstrom (Å®) administra sua empresa. Depois de definir a codificação padrão como utf-8, o código descobrirá que o byte_string pode ser interpretado como utf-8 e, portanto, manipulará os dados e retornará isso: Angstrom (Ů) administra seus negócios.

Alterar o que deveria ser uma constante terá efeitos dramáticos nos módulos dos quais você depende. É melhor corrigir os dados que entram e saem do seu código.

Problema de exemplo

Embora a configuração da codificação padrão para UTF-8 não seja a causa raiz no exemplo a seguir, mostra como os problemas são mascarados e como, quando a codificação de entrada é alterada, o código é quebrado de maneira não óbvia: UnicodeDecodeError: o codec 'utf8' pode decodifique o byte 0x80 na posição 3131: byte inicial inválido

Alastair McCormack
fonte
2
Embora haja surpresas sys.setdefaultencoding("utf-8"), é bom fazer o código se comportar mais como o Python 3. É 2017 agora. Mesmo quando você escreveu a resposta em 2015, acho que já era melhor olhar para a frente do que para trás. Na verdade, era a solução mais simples para mim, quando descobri que meu código se comporta de maneira diferente no Python 2, dependendo se a saída é redirecionada (problema muito desagradável para o Python 2). Escusado será dizer que já o tenho # coding: utf-8e não preciso de soluções alternativas para o Python 3 (na verdade, tenho que mascarar a setdefaultencodingverificação de versão usando).
Yongwei Wu
Isso é ótimo e funciona para você, mas sys.setdefaultencoding("utf-8")não torna seu código Py 2.x compatível com o Python 3. Nem corrige módulos externos que assumem que a codificação padrão é ASCII. Tornar seu código compatível com Python 3 é muito simples e não requer esse truque desagradável. Por exemplo, por que isso causa problemas muito reais, veja minha experiência com a Amazon mexendo com essa suposição: stackoverflow.com/questions/39465220/…
Alastair McCormack:
1
@AlastairMcCormack you rock, Meu site tem sido há meses e não conseguia descobrir o que fazer. Por fim, PYTHONIOENCODING="UTF-8"ajudei meu ambiente Python2.7 Django-1.11. Obrigado.
sam
Eu sei que você copiou o exemplo, mas posso encontrar o pacote detect_encoding.
dlamblin
@ dlamblin O exemplo de código é para provar a citação e não deve ser usado em seu código. Imagine que esse detect_encodingé um método que pode detectar a codificação de uma string com base em dicas de idioma.
Alastair McCormack
18
#!/usr/bin/env python
#-*- coding: utf-8 -*-
u = u'moçambique'
print u.encode("utf-8")
print u

chmod +x test.py
./test.py
moçambique
moçambique

./test.py > output.txt
Traceback (most recent call last):
  File "./test.py", line 5, in <module>
    print u
UnicodeEncodeError: 'ascii' codec can't encode character 
u'\xe7' in position 2: ordinal not in range(128)

no shell funciona, enviando para sdtout não, então essa é uma solução alternativa, para gravar em stdout.

Fiz outra abordagem, que não será executada se sys.stdout.encoding não estiver definida ou, em outras palavras, precisar exportar PYTHONIOENCODING = UTF-8 primeiro para gravar no stdout.

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


então, usando o mesmo exemplo:

export PYTHONIOENCODING=UTF-8
./test.py > output.txt

vai funcionar

Sérgio
fonte
3
Isso não responde à pergunta conforme solicitado. Antes, alguns pensamentos tangenciais sobre o assunto.
precisa saber é o seguinte
3
  • O primeiro perigo está reload(sys).

    Quando você recarrega um módulo, na verdade você obtém duas cópias do módulo em seu tempo de execução. O módulo antigo é um objeto Python como todo o resto e permanece vivo enquanto houver referências a ele. Portanto, metade dos objetos estará apontando para o módulo antigo e metade para o novo. Quando você faz alguma alteração, nunca a verá quando algum objeto aleatório não vê a alteração:

    (This is IPython shell)
    
    In [1]: import sys
    
    In [2]: sys.stdout
    Out[2]: <colorama.ansitowin32.StreamWrapper at 0x3a2aac8>
    
    In [3]: reload(sys)
    <module 'sys' (built-in)>
    
    In [4]: sys.stdout
    Out[4]: <open file '<stdout>', mode 'w' at 0x00000000022E20C0>
    
    In [11]: import IPython.terminal
    
    In [14]: IPython.terminal.interactiveshell.sys.stdout
    Out[14]: <colorama.ansitowin32.StreamWrapper at 0x3a9aac8>
  • Agora, sys.setdefaultencoding()adequado

    Tudo o que afeta é a conversão implícitastr<->unicode . Agora, utf-8é a codificação mais segura do planeta (compatível com versões anteriores com ASCII e tudo mais), a conversão agora "simplesmente funciona", o que poderia dar errado?

    Bem, qualquer coisa. E esse é o perigo.

    • Pode haver algum código que depende da UnicodeErrorativação para entrada não ASCII ou faz a transcodificação com um manipulador de erros, que agora produz um resultado inesperado. E como todo o código é testado com a configuração padrão, você está estritamente no território "não suportado" aqui e ninguém garante a você como o código deles se comportará.
    • A transcodificação pode produzir resultados inesperados ou inutilizáveis ​​se nem tudo no sistema usar UTF-8, porque o Python 2 realmente possui várias "codificações de string padrão" independentes . (Lembre-se, um programa deve funcionar para o cliente, no equipamento do cliente.)
      • Novamente, o pior é que você nunca saberá isso porque a conversão está implícita - você realmente não sabe quando e onde isso acontece. (Python Zen, koan 2 ahoy!) Você nunca saberá por que (e se) seu código funciona em um sistema e quebra em outro. (Ou melhor ainda, funciona no IDE e quebra no console.)
ivan_pozdeev
fonte