Pygments - ユニコードとエンコーディング

初回公開:2018/10/14
最終更新:未

【 目次 】

ここでユニコードの扱いについて復習しておこう。

lexer

すべてのレクサーはユニコード文字列を内部的に使用します。
そのため、間違ったエンコーディングで文字列を渡すと、時折UnicodeDecodeErrorが発生することがあります。

デフォルトでは、すべてのレクサーの入力エンコードがguess(推測)に設定されています。
つまり、次のエンコーディングが試されます。

  • UTF-8(BOM処理を含む)
  • ロケールのエンコーディング(つまり、locale.getpreferredencoding()の結果)
  • 最後の手段として、latin1

レクサーにバイト文字列オブジェクト(ユニコードではない)を渡すと、このエンコーディングを使用してデータをデコードしようとします。

encodingまたはinencodingレクサーオプションを使用して、エンコーディングをオーバーライドできます。

lexerのencodingオプションとinencodingオプション

lexerクラスのコンストラクタのソース(C:\Python27\Lib\site-packages\pygments\lexer.py)をのぞいてみるとencodingオプションとinencodingオプションに以下のような処理をおこなっている。

def __init__(self, **options):
    === 途中略 ===
    self.encoding = options.get('encoding', 'guess')
    self.encoding = options.get('inencoding') or self.encoding
    === 以降略 ===

つまりinencodingオプションとencodingオプションが指定されていない場合はinencodingオプションが優先され、指定されたいない場合はencodingが、また両方とも指定されていない場合はguess「guess」(推測)となる。

chardet

chardetライブラリがインストールされていて、エンコーディングがchardetに設定されているなら、 chardetはエンコードはテキストを分析し、それは自動的に正しい考えているエンコーディングを使用します。

from pygments.lexers import PythonLexer
lexer = PythonLexer(encoding='chardet')

Pygmentsのunicodeオブジェクトを渡すのが最善の方法です。
その場合、予期せぬ出力を得ることはありません。

formatter

フォーマッタは、出力encodingを設定しないと、Unicodeオブジェクトをストリームに送ります。
フォーマッタにencodingオプションを渡すことで、そうすることができます

from pygments.formatters import HtmlFormatter
f = HtmlFormatter(encoding='utf-8')

ソースに非ASCII文字があり、出力ストリームがUnicodeを受け取っていない場合、このオプションを設定する必要があります。
これは、すべての通常のファイルとターミナルスの場合に当てはまります。

注:ターミナルフォーマッタはスマートになります。
出力ストリームに エンコーディング属性があり、オプションを設定していない場合、このエンコーディングを持つUnicode文字列をエンコードしてから書き込みます。
例えば、sys.stdoutの場合です 。他のフォーマッターにはそのような振る舞いはありません。

別の注記:Pygmentsをコマンドライン経由で呼び出す(pygmentize)と、エンコーディングは異なった方法で処理されます。
コマンドラインのドキュメントを参照してください。

encodingオプションが指定されていればオーバーライドするoutencodingオプションも受け入れるようになりました。
これにより、レクサーとフォーマッタで単一のオプション辞書を使用することができますが、入力と出力のエンコーディングは変わりません。

formatterのencodingオプションとoutencodingオプション

formatterクラスのコンストラクタのソース(C:\Python27\Lib\site-packages\pygments\formatter.py)をのぞいてみるとencodingオプションとoutencodingオプションに以下のような処理をおこなっている。

def __init__(self, **options):
    === 途中略 ===
    self.encoding = options.get('encoding', None) or None
    if self.encoding in ('guess', 'chardet'):
        # can happen for e.g. pygmentize -O encoding=guess
        self.encoding = 'utf-8'
    self.encoding = options.get('outencoding') or self.encoding
    self.options = options

つまりoutencodingオプションがencodingオプションを上書きするようになっている。

公式ドキュメントを訳してみたけど、lexerにencodingオプションとinencodingオプションが、そしてformatterにencodingオプションとoutencodingオプションが。
なぜ二つのエンコードオプションが存在する必要があるのか、理解不足で意味不明。

ページのトップへ戻る