Pythonでstdoutをパイプするときに正しいエンコーディングを設定する


343

Pythonプログラムの出力をパイプ処理すると、Pythonインタープリターはエンコードについて混乱し、それをNoneに設定します。これは、次のようなプログラムを意味します。

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

正常に実行すると正常に動作しますが、次で失敗します:

UnicodeEncodeError: 'ascii'コーデックは位置0の文字u '\ xa0'をエンコードできません:序数が範囲(128)にありません

パイプシーケンスで使用する場合。

配管時にこれを機能させる最良の方法は何ですか?シェル/ファイルシステム/使用しているエンコーディングを使用するように指示することはできますか?

これまで見てきた提案は、site.pyを直接変更するか、このハックを使用してdefaultencodingをハードコーディングすることです。

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

配管を機能させるより良い方法はありますか?



2
Windowsでこの問題が発生した場合はchcp 65001、スクリプトを実行する前に実行することもできます。これには問題がある可能性がありますが、多くの場合、効果的であり、多くの入力を必要としません(未満set PYTHONIOENCODING=utf_8)。
Tomasz Gandor 2017年

chcpコマンドは、PYTHONIOENCODINGの設定と同じではありません。chcpはターミナル自体の設定にすぎず、ファイルへの書き込みとは関係がないと思います(これはstdoutをパイプするときに行うことです)。setx PYTHONENCODING utf-8タイピングを保存したい場合は、永続的にするようにしてください。
ejm


私はやや関連の問題に直面して、ここに解決策を見つけた- > stackoverflow.com/questions/48782529/...
bkrishna2006

回答:


162

Pythonは出力をターミナルアプリケーションが使用しているエンコードにエンコードするため、コードはスクリプトで実行すると機能します。パイピングする場合は、自分でエンコードする必要があります。

原則として、常に内部的にUnicodeを使用します。受信したものをデコードし、送信したものをエンコードします。

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

もう1つの教訓的な例は、ISO-8859-1とUTF-8の間で変換し、すべてを大文字にするPythonプログラムです。

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を使用したいと考えています。また、彼が使用するエンコーディングは、ターミナルアプリケーションの設定ではなく、ロケール設定で指定されたエンコーディングである必要があります。この場合、Python 3はロケールエンコーディングを使用しています。変更sys.stdoutする方が楽しい方法のようです。
Andrey Vlasovskikh 2010

4
すべての文字列のエンコード/デコードは、エンコードまたはデコードの呼び出しがないか、どこかに一度追加された場合に、バグの原因となる可能性があります。出力エンコーディングは、出力が端末の場合に設定できるため、出力が端末でない場合に設定できます。それを指定する標準のLC_CTYPE環境もあります。これを尊重しないのはpythonですが。
Rasmus Kaj

65
この答えは間違っています。プログラムの入力と出力ごとに手動で変換するべきではありません。それは壊れやすく、完全にメンテナンスできません。
Glenn Maynard

29
@Glenn Maynard:IYOの正解は何ですか?ただ「この答えは間違っている」
smci

14
@smci:答えは設定、スクリプトを変更しないでPYTHONIOENCODING、あなたは、Python 2でスクリプトのstdoutをリダイレクトしている場合
JFS

168

まず、このソリューションについて:

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

毎回特定のエンコーディングで明示的に印刷することは現実的ではありません。これは繰り返しが多く、エラーが発生しやすくなります。

より良い解決策はsys.stdout、プログラムの開始時に変更し、選択したエンコーディングでエンコードすることです。ここに私がPythonで見つけた1つの解決策があります:sys.stdout.encodingはどのように選択されますか?、特に「toka」によるコメント:

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

7
残念ながら、sys.stdoutを変更してユニコードのみを受け入れるようにすると、エンコードされたバイト文字列を受け入れることを期待する多くのライブラリが壊れます。
nosklo 2009

6
nosklo:出力がターミナルの場合、どのようにして確実かつ自動的に機能するのでしょうか?
Rasmus Kaj

3
@Rasmus Kaj:独自のユニコード印刷関数を定義し、ユニコードを印刷するたびにそれを使用します:def myprint(unicodeobj): print unicodeobj.encode('utf-8')-検査することでターミナルエンコーディングを自動的に検出しsys.stdout.encodingますが、そうである場合None(つまり、出力をファイルにリダイレクトする場合)を考慮する必要がありますとにかく、別の関数が必要です。
nosklo 2010年

3
@nosklo:これにより、sys.stdoutがUnicodeのみを受け入れるようにはなりません。strとunicodeの両方をStreamWriterに渡すことができます。
Glenn Maynard

9
この回答はpython2を対象としたものだと思います。 python2とpython3の両方をサポートすることを目的としたコードでは、これに注意してください。私にとって、それはpython3の下で実行されたときのものを壊しています。
2016

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プログラム内から実行できるため、ユーザーが環境変数を設定する必要はありません。
blueFast 2013年

7
@ jeckyll2hide:PYTHONIOENCODING機能します。バイトがテキストとして解釈される方法は、ユーザー環境によって定義されます。スクリプトは、使用する文字エンコードを想定してユーザー環境に指示するべきではありません。Pythonが設定を自動的に取得しない場合PYTHONIOENCODINGは、スクリプトに設定できます。出力がファイル/パイプにリダイレクトされない限り、これは必要ありません。
jfs 2014年

8
+1。正直なところ、それはPythonのバグだと思います。出力をリダイレクトするとき、端末上にあるのと同じバイトをファイルに入れます。多分それは皆のためではないかもしれませんが、それは良いデフォルトです。通常は「正常に機能する」簡単な操作の説明がないまま激しくクラッシュすることは、不適切なデフォルトです。
SnakE

@SnakE:なぜPythonの実装が意図的に起動時にstdoutにエンコードの鉄クラッドで永続的な選択を強制するのかを合理化できる唯一の方法は、後で不適切にエンコードされたものを防ぐためかもしれません。または、変更は実装されていない機能にすぎません。その場合、ユーザーが後で変更できるようにすることは、妥当なPython機能のリクエストになります。
daveagp

2
@daveagp私の要点は、私のプログラムの動作は、それがリダイレクトされるかどうかに依存すべきではありません---私が本当にそれを望んでいない限り、私はそれを自分で実装します。Pythonは、他のコンソールツールでの私の経験に反して動作します。これは、最も驚きの少ない原則に違反しています。非常に強い根拠がない限り、私はこれを設計上の欠陥と考えます。
SnakE

62
export PYTHONIOENCODING=utf-8

仕事をしますが、Python自体には設定できません...

できることは、が設定されていないかどうかを確認し、次のコマンドを使用してスクリプトを呼び出す前に設定するようユーザーに指示することです。

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

猫b.py

#!/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ます。
amphetamachine

1
mail.python.org/pipermail/python-list/2011-June/605938.htmlあなたが./a.py> out.txtをsys.stdout.encodingを使用する場合がある例はまだ仕事、つまりはNoneです
セルジオ

Backblaze B2の同期スクリプトで同様の問題があり、エクスポートPYTHONIOENCODING = utf-8で問題が解決しました。Debianストレッチ上のPython 2.7。
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

何が起こっているのかをようやく気付く前に、ここで長い間実験に費やさなければならなかった何かをここで言及したいと思っただけです。これは、ここにいるすべての人にとって非常に明白であるため、言及することに煩わされていません。しかし、もし彼らが持っていれば、それは私を助けたでしょう、それでその原則に関して...!

注意:私は特にJython v 2.7 を使用しているので、これがCPythonに適用されない可能性があります...

NB2:.pyファイルの最初の2行は次のとおりです。

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

"%"(別名 "補間演算子")文字列構築メカニズムは、追加の問題も引き起こします... "環境"のデフォルトのエンコーディングがASCIIで、次のようなことを行おうとした場合

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

Eclipseで実行しても問題はありません... Windows CLI(DOSウィンドウ)では、コード化はコードページ850(私のWindows 7 OS)またはそれに似たものであり、少なくともヨーロッパのアクセント付き文字を処理できます。動作します。

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

も動作します。

OTOHの場合、CLIからファイルを送信すると、標準出力のエンコーディングはNoneになり、デフォルトではASCIIになり(私のOSではとにかく)、上記のいずれの印刷も処理できません...エラー)。

だからあなたはあなたのstdoutを使ってリダイレクトすることを考えるかもしれません

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

ファイルへのパイピングをCLIで実行してみてください...非常に奇妙なことに、上の印刷Aは機能します...しかし、上の印刷Bはエンコードエラーをスローします!ただし、以下は問題なく機能します。

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

私が(暫定的に)結論に達したのは、 "u"プレフィックスを使用してUnicode文字列として指定された文字列が%-handlingメカニズムに送信されると、デフォルトの環境エンコーディングの使用に関係なく、 stdoutをリダイレクトするように設定しているかどうか!

人々がこれをどのように扱うかは選択の問題です。これがなぜ起こるのか、何か間違っているのか、これに対する好ましい解決策、それがCPythonにも当てはまるのか、Python 3で起こるのかなどなど、Unicodeの専門家に歓迎します。


これは奇妙なことで"fréd"はありません。これは、バイトシーケンスであり、Unicode文字列ではないためcodecs.getwriterです。そのため、ラッパーはそれをそのままにします。あなたは、リーディングu、またはが必要from __future__ import unicode_literalsです。
Matthias Urlichs 2014年

@MatthiasUrlichs OK ...ありがとう...しかし、ITの最も苛立たしい側面の1つであるエンコーディングを見つけただけです。どこから理解を得るのですか?たとえば、ここでエンコーディングに関する別の質問を投稿しました:stackoverflow.com/questions/44483067/…:これは、Java、Eclipse、CygwinおよびGradleに関するものです。あなたの専門知識がここまで進んだら、助けてください...何よりも、どこでもっと学ぶべきか知りたいです!
マイクげっ歯類2017年

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)

これにより、エンコーディングを使用するようにprintへのすべての呼び出しが変更されるため、コンソールはこれを出力します。

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

1

Windowsでは、エディターから(Sublime Textなどの)Pythonコードを実行するときにこの問題が頻繁に発生しましたが、コマンドラインから実行する場合そうではありませんでした

この場合は、エディターのパラメーターを確認してください。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"}
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.