Published On: 2014-06-12|Last Updated: 2014-06-12|Categories: Python|Tags: |

subprocess の各関数を universal_newlines=True で呼び出すとサブプロセスの stdin/stdout/stderr がテキストモードで開かれます。(というか io.TextIOWrapper でラップされます)
locale.getpreferredencoding(False) で得られるコンソールのエンコーディングは固定で、Windows は mbcs、つまり日本語環境では cp932 となります。
ただ一部のコンソールプログラムは mbcs でなく utf-8 で出力したりします。universal_newlines=False のまま呼べばいいだろとかその通りなのですが、次のような回避策を考えてみました。

#!python3
# -*- coding: utf-8 -*-
import sys
import subprocess
from io import TextIOWrapper
import unittest, unittest.mock


def DummyTextIOWrapper(buffer, encoding=None, errors=None, newline=None,
                       line_buffering=False, write_through=False):
    return TextIOWrapper(buffer, encoding or 'utf-8', errors, newline,
                         line_buffering, write_through)


class SubprocessWithIllegalEncoding(unittest.TestCase):
    fail_string = '不明'
    cmdline = [sys.executable, '-c',
               'import sys; sys.stdout.buffer.write(%r)' % fail_string.encode()]

    @unittest.skipIf(sys.platform != 'win32', 'Windows only')
    def test_normal(self):
        self.assertRaises(UnicodeDecodeError, subprocess.check_output,
                          self.cmdline, universal_newlines=True)

    @unittest.skipIf(sys.platform != 'win32', 'Windows only')
    def test_patched(self):
        with unittest.mock.patch('io.TextIOWrapper', new=DummyTextIOWrapper):
            self.assertEqual(
                subprocess.check_output(self.cmdline, universal_newlines=True),
                self.fail_string)


if __name__ == '__main__':
    unittest.main()

unittest.mock.patch はとても便利です。ただ標準モジュールにぶち込んだのは頭を疑います。

関連