過去にこのようなエントリを書いた。 掲載されている下記のコードがWindowsでは動かない、という内容であった。
def run_openssl(data): env = os.environ.copy() env['password'] = b'\xe24U\n\xd0Ql3S\x11' proc = subprocess.Popen( ['openssl', 'enc', '-des3', '-pass', 'env:password'], env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE) proc.stdin.write(data) proc.stdin.flush() # Ensure the child gets input return proc
発売された邦訳版には訳注があり、次のように述べている。
Windows環境のPython 3では、環境変数にバイト列は指定できないため、
TypeError
が発生する。このため、WindowsではpasswordにUnicode文字列を指定する必要がある。(P.119)
今回は、なぜWindowsだとバイト列を指定できないのかに迫る。
subprocess.Popen
の定義を見ると、実行環境がWindowsかどうかで実行するメソッドが変化するようになっている。
https://hg.python.org/cpython/file/tip/Lib/subprocess.pyのPopen
の部分に着目してほしい。
Windows向けの_execute_child
、POSIX向けの_execute_child
メソッドが書かれているのがわかるだろうか。
次に、Windows向けの_execute_child
を見ると、与えたパラメータを_winapi.CreateProcess
に渡しているのが見て取れる。
try: hp, ht, pid, tid = _winapi.CreateProcess(executable, args, # no special security None, None, int(not close_fds), creationflags, env, cwd, startupinfo)
さて、次は_winapi
を追えばよいのだが、_winapi
はC言語で書かれている。
https://github.com/python/cpython/blob/master/Modules/_winapi.c
この中に static PyObject *_winapi_CreateProcess_impl
という函数があり、
その中でenv
に対応するenv_mapping
からgetenvironment(env_mapping)
として値を吸い出そうとしている函数が存在することがわかる。
if (PyErr_Occurred()) return NULL; if (env_mapping != Py_None) { environment = getenvironment(env_mapping); if (! environment) return NULL; wenvironment = PyUnicode_AsUnicode(environment); if (wenvironment == NULL) { Py_XDECREF(environment); return NULL; } } else { environment = NULL; wenvironment = NULL; } ...
同じ_winapi.c
にあるstatic PyObject * getenvironment
を見ると、引数の型をチェックしているのがわかる。
if (! PyUnicode_Check(key) || ! PyUnicode_Check(value)) { PyErr_SetString(PyExc_TypeError, "environment can only contain strings"); goto error; }
http://docs.python.jp/3/c-api/unicode.html?highlight=pyunicode_check#c.PyUnicode_Checkを見れば、
PyUnicode_Check
はその名の通り、Unicode 文字列型か Unicode 文字列型のサブタイプかどうかをチェックしているのがわかる。
以上より、Windows環境のPython 3では、環境変数にバイト列を指定すると、"environment can only contain strings"
と返されてしまうのである。