Встановлення правильного кодування під час передачі протоколу stdout у Python


343

Під час передачі даних на програму Python інтерпретатор Python плутається з приводу кодування та встановлює його на None. Це означає таку програму:

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

буде працювати нормально при нормальному виконанні, але не вдасться:

UnicodeEncodeError: кодек "ascii" не може кодувати символ u '\ xa0' у позиції 0: порядковий не знаходиться в діапазоні (128)

при використанні в послідовності труб.

Який найкращий спосіб зробити цю роботу при трубопроводах? Чи можу я просто сказати йому використовувати будь-яке кодування оболонки / файлової системи / що б там не було?

Поки що я бачив пропозиції - це змінити ваш сайт.py безпосередньо або жорстке кодування дефолтного кодування за допомогою цього злому:

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

Чи є кращий спосіб змусити трубопроводи працювати?



2
Якщо у вас є проблема з Windows, ви також можете запустити chcp 65001перед виконанням сценарію. Це може мати проблеми, але це часто допомагає і не потребує великого набору тексту (менше, ніж set PYTHONIOENCODING=utf_8).
Томаш Гандор

Команда chcp не те саме, що встановити PYTHONIOENCODING. Я думаю, що chcp - це лише конфігурація для самого терміналу і не має нічого спільного з записом у файл (що ви робите під час передачі протоколу stdout). Спробуйте setx PYTHONENCODING utf-8зробити його постійним, якщо хочете зберегти набравши текст.
ейм


Я зіткнувся кілька пов'язаного з цим питанням, і знайшов рішення тут -> stackoverflow.com/questions/48782529 / ...
bkrishna2006

Відповіді:


162

Ваш код працює під час запуску в сценарії, оскільки Python кодує вихід до того, що кодує ваша термінальна програма. Якщо ви працюєте в трубопроводі, ви повинні кодувати його самостійно.

Основне правило: Завжди використовуйте Unicode всередині. Розшифруйте отримане і кодуйте те, що ви надіслали.

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

Ще один дидактичний приклад - програма Python для перетворення між ISO-8859-1 та UTF-8, що робить усе прописними літерами між ними.

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)

Встановлення системного кодування за замовчуванням - погана ідея, оскільки деякі використовувані модулі та бібліотеки можуть покладатися на те, що це ASCII. Не робіть цього.


11
Проблема полягає в тому, що користувач не хоче чітко вказувати кодування. Він хоче просто використовувати Unicode для IO. І кодування, яке він використовує, повинно бути кодуванням, вказаним у налаштуваннях локалі, а не в налаштуваннях термінальної програми. AFAIK, Python 3 використовує кодування локалів у цьому випадку. Зміна sys.stdoutздається більш приємним способом.
Андрій Власовських

4
Кодування / декодування кожного рядка експліцитно пов'язане з тим, щоб викликати помилки, коли виклик коду або декодування відсутній або доданий один раз до кудись багато. Кодування виводу може бути встановлено, коли вихід є терміналом, тому він може бути встановлений, коли вихід не є терміналом. Існує навіть стандартне середовище LC_CTYPE для його визначення. Це, але в python, що він цього не поважає.
Расмус Кай

65
Ця відповідь неправильна. Ви не повинні здійснювати перетворення вручну на кожен вхід і вихід програми; це крихке і абсолютно неможливе.
Гленн Мейнард

29
@Glenn Maynard: так що IYO правильна відповідь? Це корисніше сказати нам, ніж просто сказати: «Ця відповідь неправильна»
smci

14
@smci: відповідь не змінює ваш сценарій, встановіть, PYTHONIOENCODINGякщо ви перенаправляєте
строку

168

По-перше, щодо цього рішення:

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

Не практично кожного разу чітко друкувати із заданим кодуванням. Це буде повторюваним і схильним до помилок.

Краще рішення - змінити sys.stdoutна початку вашої програми, кодувати вибране кодування. Ось одне рішення, яке я знайшов на Python: Як вибрано sys.stdout.encoding? , зокрема коментар "toka":

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

7
на жаль, зміна sys.stdout на прийняття тільки unicode розбиває багато бібліотек, які очікують, що він прийме закодовані байствейн.
nosklo

6
nosklo: Тоді як це можна надійно та автоматично працювати, коли вихід є терміналом?
Расмус Кай

3
@Rasmus Kaj: просто визначте свою власну функцію друку unicode та використовуйте її кожного разу, коли ви хочете надрукувати unicode: def myprint(unicodeobj): print unicodeobj.encode('utf-8')- ви автоматично виявляєте кодування терміналу, перевіряючи sys.stdout.encoding, але вам слід врахувати випадок, де він є None(тобто, перенаправляючи вихід у файл) тому вам потрібна окрема функція.
nosklo

3
@nosklo: Це не змушує sys.stdout приймати лише Unicode. Ви можете передати і str, і unicode в StreamWriter.
Гленн Мейнард

9
Я припускаю, що ця відповідь була призначена для python2. Будьте уважні з цим щодо коду, який призначений для підтримки і python2, і python3 . Для мене це ламання речей, коли бігав під python3.
Вім

130

Ви можете спробувати змінити змінну середовища "PYTHONIOENCODING" на "utf_8". Я написав сторінку про своє випробування з цією проблемою .

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

дає тобі

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

2
Зміна sys.stdout.encoding може бути , не працює, але зміна sys.stdout робить роботу: sys.stdout = codecs.getwriter(encoding)(sys.stdout). Це можна зробити з програми python, тому користувач не змушений встановлювати змінну env.
blueFast

7
@ jeckyll2hide: PYTHONIOENCODINGпрацює. Як інтерпретуються байти як текст, визначається середовище користувача . Ваш сценарій не повинен припускати та диктувати середовищу користувача, яке кодування символів використовувати. Якщо Python не підбирає налаштування автоматично, тоді PYTHONIOENCODINGйого можна встановити для вашого сценарію. Вам це не потрібно, якщо вихід не буде перенаправлений на файл / трубу.
jfs

8
+1. Чесно кажучи, я думаю, що це помилка Python. Коли я перенаправляю вихід, я хочу ті самі байти, які були б на терміналі, але у файлі. Можливо, це не для всіх, але це хороший дефолт. Тяжкий збій без пояснень щодо тривіальної операції, яка, як правило, "просто працює" - це поганий дефолт.
SnakE

@SnakE: єдиний спосіб я можу раціоналізувати, чому реалізація Python навмисно запроваджує залізо та постійний вибір кодування на stdout під час запуску, можливо, щоб запобігти появі погано закодованих речей згодом. Або його зміна - це лише безреалізована функція, і в такому випадку дозволяти користувачеві змінити її згодом було б розумним запитом функції Python.
daveagp

2
@daveagp Моя думка полягає в тому, що поведінка моєї програми не повинна залежати від того, перенаправлена ​​вона чи ні ---, якщо я дійсно цього не хочу, і в такому випадку я сам її реалізую. Python поводиться всупереч моєму досвіду будь-яких інших консольних інструментів. Це порушує принцип найменшого здивування. Я вважаю це недоліком дизайну, якщо не існує дуже сильного обгрунтування.
SnakE

62
export PYTHONIOENCODING=utf-8

виконайте роботу, але не можу встановити її на пітоні ...

що ми можемо зробити, це перевірити, якщо він не встановлений, і сказати користувачеві встановити його перед скриптом виклику за допомогою:

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)

Оновіть, щоб відповісти на коментар: проблема просто існує під час передачі на stdout. Я протестував у Fedora 25 Python 2.7.13

python --version
Python 2.7.13

кішка б.пи

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

print sys.stdout.encoding

працює ./b.py

UTF-8

працює ./b.py | менше

None

2
Ця перевірка не працює в Python 2.7.13. sys.stdout.encodingавтоматично встановлюється на основі LC_CTYPEзначення локалі.
амфетамахін

1
mail.python.org/pipermail/python-list/2011-June/605938.html приклад все ще працює, тобто коли ви використовуєте ./a.py> out.txt sys.stdout.encoding - None
Sérgio,

У мене була подібна проблема із сценарієм синхронізації з Backblaze B2 та експорт PYTHONIOENCODING = utf-8 вирішив мою проблему. Python 2.7 на Debian Stretch.
0x3333

5

У мене був подібний випуск минулого тижня . Це було легко виправити в моєму IDE (PyCharm).

Ось моя помилка:

Починаючи з панелі меню PyCharm: Файл -> Налаштування ... -> Редактор -> Кодування файлів, потім встановіть: "Кодування IDE", "Кодування проекту" та "Кодування за замовчуванням для файлів властивостей" ВСЕ до UTF-8, і вона тепер працює мов чарівність.

Сподіваюсь, це допомагає!


4

Аргументована санітована версія відповіді Крейга МакКуїна.

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

Використання:

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

2

Я міг би "автоматизувати" це за допомогою виклику:

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

Так, тут можна отримати нескінченну петлю, якщо ця "setenv" вийде з ладу.


1
Цікаво, але труба, здається, не радіє цьому
n611x007

2

Я просто думав, що тут згадаю щось, з чим мені довелося довго експериментувати, перш ніж я нарешті зрозумів, що відбувається. Це може бути настільки очевидно для всіх тут, що вони не намагалися згадувати про це. Але мені це допомогло б, якби вони мали, тож за таким принципом ...!

NB: Я використовую Jython , v 2.7, тому просто можливо це не стосується CPython ...

NB2: перші два рядки мого .py-файлу тут:

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

Механізм побудови рядків "%" (AKA "оператор інтерполяції") також спричиняє додаткові проблеми ... Якщо кодування "середовища" за замовчуванням - ASCII, і ви намагаєтесь зробити щось на кшталт

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

У вас не виникне труднощів із запуском Eclipse ... У Windows CLI (вікно DOS) ви побачите, що кодування - це сторінка коду 850 (моя ОС Windows 7) чи щось подібне, що може принаймні обробляти символи європейських наголосів, так що Буду працювати.

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

також буде працювати.

Якщо, OTOH, ви перейдете до файлу з CLI, кодування stdout буде None, що за замовчуванням буде ASCII (в будь-якій моїй ОС), який не зможе обробити жоден з вищезазначених відбитків ... (бояться кодування помилка).

Тоді ви можете подумати про перенаправлення вашого stdout за допомогою

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

і спробуйте запустити CLI в трубопровід до файлу ... Дуже дивно, надрукувати A вище буде спрацьовувати ... Але друк B вище призведе до помилки кодування! Наступне, однак, буде добре:

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

Висновок, до якого я дійшов (попередньо), полягає в тому, що якщо рядок, яка вказана як рядок Unicode з використанням префіксу "u", подається до механізму% -handling, то, схоже, це передбачає використання кодування середовища за замовчуванням, незалежно від чи встановили ви stdout для переадресації!

Як люди вирішують це - питання вибору. Я б вітав експерта Unicode, щоб сказати, чому це відбувається, чи я помилився якимось чином, яке вподобане рішення для цього, чи стосується він і CPython , чи трапляється це в Python 3 тощо, тощо.


Це не дивно, адже "fréd"це послідовність байтів, а не рядок Unicode, тому codecs.getwriterобгортка залишить її в спокої. Вам потрібен ведучий u, або from __future__ import unicode_literals.
Маттіас Урліхс

@MatthiasUrlichs Гаразд ... дякую ... Але я просто знаходжу кодування одного з найбільш гнівних аспектів ІТ. Звідки ви розумієтесь? Наприклад, я щойно розмістив тут ще одне питання щодо кодування: stackoverflow.com/questions/44483067/… : мова йде про Java, Eclipse, Cygwin & Gradle. Якщо ваша експертиза піде так далеко, будь ласка, допоможіть ... перш за все, я хотів би знати, де дізнатися більше!
мійський гризун

1

Я зіткнувся з цією проблемою у застарілому додатку, і важко було визначити, де що було надруковано. Я допомагав собі з цим хаком:

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

Поверх мого сценарію, test.py:

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

Зауважте, що це змінює ВСІ дзвінки для друку для використання кодування, тому ваша консоль буде друкувати це:

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

1

У Windows у мене виникала ця проблема дуже часто під час запуску коду Python з редактора (наприклад, Sublime Text), але не якщо він запускається з командного рядка.

У цьому випадку перевірте параметри редактора. У випадку з SublimeText це Python.sublime-buildвирішило:

{
  "cmd": ["python", "-u", "$file"],
  "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
  "selector": "source.python",
  "encoding": "utf8",
  "env": {"PYTHONIOENCODING": "utf-8", "LANG": "en_US.UTF-8"}
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.