コンソールでのエンコーディングの問題

もう一度、WindowsでSamIzdatの独自のスクリプトインフォーマーを実行し、コンソールで「神秘的なシンボル」を見たとき私は自分自身に言いました。



このことと、Win32のDjango-vskyのようなログの出力をHabra-katの下で色付けする方法を説明します以下で説明するものはすべてPython 2.xブランチに適用されます



最初のタスク。 コンソールへの正しいテキスト出力


症状


初期化されたI / Oシステムに「修正」を行い、Unicode行でprintステートメントのみを使用するまで、OSに関係なく、すべてがほぼ正常に動作します。



「奇跡」はさらに始まります-エンコーディングを変更するか(もう少し詳しく見てください)、またはロギングモジュールを使用して画面に表示します。 Linuxで期待される動作を設定しているようで、Windowsではutf-8で「ガベージ」が発生します。 あなたはWinの下で編集を開始します-1251はコンソールでクロールします...



理論的な遠足


いくつかのパラメーターは、文字を変換してコンソールに表示するためのパラメーターを担当します。



私たちは解決策を探しています


明らかに、これらすべての問題を取り除くには、何らかの形でそれらを均一にする必要があります。

そして、ここから楽しみが始まります。

 # -*- coding: utf-8 -*- >>> import sys >>> import locale >>> print sys.getdefaultencoding() ascii >>> print locale.getpreferredencoding() # linux UTF-8 >>> print locale.getpreferredencoding() # win32/rus cp1251 #   : >>> print sys.stdout.encoding # linux UTF-8 >>> print sys.stdout.encoding # win32 cp866
      
      





うん! 「システム」は一般にASCIIで生きていることがわかります。 その結果、単純に入力/出力を処理しようとすると、「お気に入り」の例外UnicodeEncodeError/UnicodeDecodeError



終了します。



さらに、例からわかるように、Linuxのどこにでもutf-8がある場合、Windowsには2つの異なるエンコーディングがあります。いわゆるANSI、cp1251で、グラフィックパーツとOEMに使用され、cp866にテキストを出力します。コンソール OEMエンコードはDOSからやって来ており、理論的には特別なチームによって再構成することもできますが、実際には長い間誰もこれを行っていません。



最近まで、私はこの迷惑を修正するために一般的な方法を使用しました:

 #!/usr/bin/env python # -*- coding: utf-8 -*- # ============== # Main script file # ============== import sys reload(sys) sys.setdefaultencoding('utf-8') #  import locale sys.setdefaultencoding(locale.getpreferredencoding()) # ...
      
      





そして、それは一般に、うまくいきました。 print



を使用していればうまくいきました。 logging



を通じて画面出力に行ったときlogging



すべてが壊れました。

ええ、「it」はデフォルトのエンコーディングを使用するので、コンソールと同じエンコーディングを設定します。

 sys.setdefaultencoding(sys.stdout.encoding or sys.stderr.encoding)
      
      





もう少し良くなりましたが:



最初の例をよく見てみると、対応するストリームの属性をチェックすることによってのみ望ましいcp866エンコーディングが得られることがわかります。 そして、常に利用できるとは限りません。

タスクの2番目の部分は、システムエンコーディングをutf-8のままにして、コンソールへの出力を正しく構成することです。

出力をカスタマイズするには、次のように出力ストリームの処理を再定義する必要があります。

 import sys import codecs sys.stdout = codecs.getwriter('cp866')(sys.stdout,'replace')
      
      





このコードを使用すると、1石で2羽の鳥を殺すことができます。255文字のcp866で欠落しているすべての種類のウムラウトやその他のタイポグラフィを印刷するときに、目的のエンコードを設定し、例外から保護します。

このコードを普遍的にすることは残っています-任意の球状のコンピューターでOEMエンコードをどのように知ることができますか? pythonでANSI / OEMエンコーディングの既製のサポートをグーグルで検索することは合理的ではなかったため、WinAPIを少し思い出す必要がありました

 UINT GetOEMCP(void); //   OEM     UINT GetANSICP(void); //    ANSI  
      
      





...そしてそれをすべてまとめる:

 # -*- coding: utf-8 -*- import sys import codecs def setup_console(sys_enc="utf-8"): reload(sys) try: #  win32     if sys.platform.startswith("win"): import ctypes enc = "cp%d" % ctypes.windll.kernel32.GetOEMCP() #TODO:   win64/python64 else: #  Linux , ,    enc = (sys.stdout.encoding if sys.stdout.isatty() else sys.stderr.encoding if sys.stderr.isatty() else sys.getfilesystemencoding() or sys_enc) #   sys sys.setdefaultencoding(sys_enc) #    ,     if sys.stdout.isatty() and sys.stdout.encoding != enc: sys.stdout = codecs.getwriter(enc)(sys.stdout, 'replace') if sys.stderr.isatty() and sys.stderr.encoding != enc: sys.stderr = codecs.getwriter(enc)(sys.stderr, 'replace') except: pass # ?    -  -...
      
      





2番目のタスク。 結論を色付けする


werkzeugと一緒にJangaのデバッグ出力を確認した後、私は自分に似たものが欲しかった。 グーグルは、 logging.StreamHandler



の最も単純な子孫から、インポート時に標準のStreamHandlerを自動的に置き換える特定のセットまで、さまざまな詳細度と利便性のプロジェクトを生成します。



それらのいくつかを試した後、 Stack Overflowのコメントの1つにリストされている単純なStreamHandlerの継承者を使用することになりましたが、これまでのところ非常に満足しています。

 class ColoredHandler( logging.StreamHandler ): def emit( self, record ): # Need to make a actual copy of the record # to prevent altering the message for other loggers myrecord = copy.copy( record ) levelno = myrecord.levelno if( levelno >= 50 ): # CRITICAL / FATAL color = '\x1b[31;1m' # red elif( levelno >= 40 ): # ERROR color = '\x1b[31m' # red elif( levelno >= 30 ): # WARNING color = '\x1b[33m' # yellow elif( levelno >= 20 ): # INFO color = '\x1b[32m' # green elif( levelno >= 10 ): # DEBUG color = '\x1b[35m' # pink else: # NOTSET and anything else color = '\x1b[0m' # normal myrecord.msg = (u"%s%s%s" % (color, myrecord.msg, '\x1b[0m')).encode('utf-8') # normal logging.StreamHandler.emit( self, myrecord )
      
      





ただし、Windowsでは、このすべての作業はもちろん拒否されました。 そして以前に、Windowsシステムフォルダーの腸のどこかにsymfonyプロジェクトから「マジック」ansi.dllを追加することにより、コンソールでANSIコードのサポートを「有効」にできた場合、Windows 7でこの機能は最終的にシステムから「カット」されました。 また、ユーザーに何らかの種類のdllをシステムフォルダーにコピーさせることも、「コーシャではありません」。



再びGoogleに目を向けると、いくつかの解決策があります。 いずれにせよ、すべてのオプションは、ANSIエスケープシーケンスをWinAPI呼び出しに置き換えてコンソール属性を管理することを要約しています。



しばらくリンクをさまよった後、 コロラマプロジェクトに出会いました。 どういうわけか私は彼が他の人よりも好きだった。 この特定のプロジェクトの利点には、コンソール出力全体の置換が含まれます- print u"\x1b[31;40m- \x1b[0m"



したい場合は、単純なprint u"\x1b[31;40m- \x1b[0m"



色付きテキストを印刷できます。



現在のバージョン0.1.18には、Unicode行の出力を中断する迷惑なバグが含まれていることがすぐにわかります。 しかし、問題を作成するときに、最も簡単なソリューションをそこにもたらしました。



実際には、両方の希望を組み合わせて、従来の「クランチ」の代わりに使い始めることが残っています。

 # -*- coding: utf-8 -*- import sys import codecs import copy import logging #: Is ANSI printing available ansi = not sys.platform.startswith("win") def setup_console(sys_enc='utf-8', use_colorama=True): """ Set sys.defaultencoding to `sys_enc` and update stdout/stderr writers to corresponding encoding .. note:: For Win32 the OEM console encoding will be used istead of `sys_enc` """ global ansi reload(sys) try: if sys.platform.startswith("win"): #... ,   if use_colorama and sys.platform.startswith("win"): try: #   colorama      `ansi`,    from colorama import init init() ansi = True except: pass class ColoredHandler( logging.StreamHandler ): def emit( self, record ): # Need to make a actual copy of the record # to prevent altering the message for other loggers myrecord = copy.copy( record ) levelno = myrecord.levelno if( levelno >= 50 ): # CRITICAL / FATAL color = '\x1b[31;1m' # red elif( levelno >= 40 ): # ERROR color = '\x1b[31m' # red elif( levelno >= 30 ): # WARNING color = '\x1b[33m' # yellow elif( levelno >= 20 ): # INFO color = '\x1b[32m' # green elif( levelno >= 10 ): # DEBUG color = '\x1b[35m' # pink else: # NOTSET and anything else color = '\x1b[0m' # normal myrecord.msg = (u"%s%s%s" % (color, myrecord.msg, '\x1b[0m')).encode('utf-8') # normal logging.StreamHandler.emit( self, myrecord )
      
      





さらにプロジェクトでは、実行ファイルで次を使用します。

 #!/usr/bin/env python # -*- coding: utf-8 -*- from setupcon import setup_console setup_console('utf-8', False) #... #       import setupcon setupcon.setup_console() import logging #... if setupcon.ansi: logging.getLogger().addHandler(setupcon.ColoredHandler())
      
      







それだけです 潜在的な改善のうち、同じStackOverflowでのより複雑な例のように、win64 pythonでの操作性を確認し、可能であれば、ColoredHandlerにisattyを確認させることが残っています。



結果のモジュールの最終バージョンは、 dumpz.org入手できます。



All Articles