プログラミング

[python]Windows環境でsubprocessするときは文字コードに気をつけて

投稿日:

pythonは内部の文字コードと実行環境の文字コードが色々絡み合っていて、いろんなところで悪さをする。
特にWindows環境だと、内部はUTF-8で動いているのに実行環境はShift-JIS(正確にはcp932)という状態で、これが非常に悪さをする。
こういった悪さは日本人エンジニアは慣れっこな一方で、海外エンジニアには非常に理解されない問題。
つまり、海外製のpythonスクリプトを日本のWindows環境で動かすと、文字コード不一致が原因でバグって死ぬ

たとえば、subprocessを使って外部コマンドを実行しようとしたとき、外部コマンドの出力がUTF-8だと日本のWindows環境ではpythonが死ぬ

サンプル

外部コマンド側

UTF-8で「ハローワールド」と出力するだけのスクリプト。

import sys
sys.stdout.buffer.write(b'\xE3\x83\x8F\xE3\x83\xAD\xE3\x83\xBC\xE3\x83\xAF\xE3\x83\xBC\xE3\x83\xAB\xE3\x83\x89')

親コマンド側

さっきのコマンドを実行してprintするだけのスクリプト。

import subprocess

cmdline = ["python", "C:/utf8put.py"]
print(subprocess.check_output(cmdline, universal_newlines=True))

実行結果

こういうエラーになる。

Traceback (most recent call last):
  File "c:\utf8input.py", line 4, in <module>
    print(subprocess.check_output(cmdline, universal_newlines=True))
  File "C:\Users\givemegohan\AppData\Local\Programs\Python\Python38\lib\subprocess.py", line 411, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
  File "C:\Users\givemegohan\AppData\Local\Programs\Python\Python38\lib\subprocess.py", line 491, in run
    stdout, stderr = process.communicate(input, timeout=timeout)
  File "C:\Users\givemegohan\AppData\Local\Programs\Python\Python38\lib\subprocess.py", line 1011, in communicate
    stdout = self.stdout.read()
UnicodeDecodeError: 'cp932' codec can't decode byte 0x89 in position 20: incomplete multibyte sequence

サンプルの解説

これはsubprocess.check_outputがコマンドの出力にShift-JIS(正確にはcp932)を期待しているのに、UTF-8が飛び込んで来たので処理できなくてUnicodeDecodeErrorを吐いている。

ちなみにsubprocess.check_outputにtimeout属性を付けると、別スレッドで動くようになってさらにエラーが複雑になる。

subprocess.check_output(cmdline, universal_newlines=True, timeout=30)

↑こんな感じにtimeout=30を付けると、
↓こんな感じにスレッド2本が別々のエラーを吐いて死ぬ。

Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Users\givemegohan\AppData\Local\Programs\Python\Python38\lib\threading.py", line 932, in _bootstrap_inner
    self.run()
  File "C:\Users\givemegohan\AppData\Local\Programs\Python\Python38\lib\threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\givemegohan\AppData\Local\Programs\Python\Python38\lib\subprocess.py", line 1366, in _readerthread
    buffer.append(fh.read())
UnicodeDecodeError: 'cp932' codec can't decode byte 0x89 in position 20: incomplete multibyte sequence
Traceback (most recent call last):
  File "c:\utf8input.py", line 4, in <module>
    print(subprocess.check_output(cmdline, universal_newlines=True, timeout=30))
  File "C:\Users\givemegohan\AppData\Local\Programs\Python\Python38\lib\subprocess.py", line 411, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
  File "C:\Users\givemegohan\AppData\Local\Programs\Python\Python38\lib\subprocess.py", line 491, in run
    stdout, stderr = process.communicate(input, timeout=timeout)
  File "C:\Users\givemegohan\AppData\Local\Programs\Python\Python38\lib\subprocess.py", line 1024, in communicate
    stdout, stderr = self._communicate(input, endtime, timeout)
  File "C:\Users\givemegohan\AppData\Local\Programs\Python\Python38\lib\subprocess.py", line 1416, in _communicate
    stdout = stdout[0]
IndexError: list index out of range

で、これは実はuniversal_newlines=Trueのときの挙動。ちなみにtextとuniversal_newlinesは同じ効果なので、text=Trueでも起きる
このとき、コマンドの出力をテキストストリームとして開く。逆に言えば、textやuniversal_newlinesを付けないときはバイトストリームになる。
ここらへんの挙動は一応公式ドキュメントにあるので細かく知りたい人は読むと良い。

対策

univarsal_newlinesを外してしまえばバイトストリームとして読める。
…が、わざわざunivarsal_newlinesを付けているということは文字列として処理したいはずなので、本筋としては「このコマンドの出力はUTF-8だよ」と教えてあげるべき。

というわけで、encoding=”utf8″を付ければ良いです。

subprocess.check_output(cmdline, encoding="utf8", universal_newlines=True)

意味が分かればそれだけの話なんだけどハマると長いですよねこういうの。

-プログラミング

執筆者:


comment

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

関連記事

GeoJSONで市町村境界をマージして都道府県境界にしたい(実践編)

前々回と前回で問題を整理して、ようやく実践編です。 まず実物のリンク貼りましょう。GitHubに上げました。 今回はGo言語で書いてますが、ポイントがいくつかあります。 ちなみに言語としてGoを選択し …

【Unity】GPUを使ってパンツを隠すスクリプトができた

かわいいスカートを履きたい!でもパンチラしたくない! それは誰もが抱く夢です。もちろん、3Dモデルだってパンチラしたくないと思っているハズです。 というわけで偉大なる先人がいます。 Unityでパンツ …

no image

[C言語]スペースを大量に入れるとバグが直るコードを書いてみた

↓こういうツイートがバズっていたので、実際に組んでみた。 修士の頃、授業の課題でC言語書いてる時にどうしても謎のエラーが出て困っていた。それを見たSE経験(金融系)がある社会人大学院生の同期の女性が「 …

no image

例え話をしないC言語のポインタの説明

もくじ1 まえおき2 Hello, Worldより簡単に2.1 サンプルコード2.2 変数の置き場所2.3 変数が入っているところを見てみる3 C言語とメモリ3.1 変数cの「まわり」を見てみる3.2 …

no image

プログラミングとアルゴリズムのはなし

みんなー!小学校でプログラミングの授業がはじまるよー!! プログラミングってなんだろう? プログラミングって、コンピューターに「○○をしなさい」って命令して、なにかの問題を解いたり、ゲームをつくったり …