何かを書き留める何か

数学や読んだ本について書く何かです。最近は社会人として生き残りの術を学ぶ日々です。

『Effective Python』Item 36 訳注の補遺(のつもり)

過去にこのようなエントリを書いた。 掲載されている下記のコードが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.pyPopenの部分に着目してほしい。 Windows向けの_execute_childPOSIX向けの_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を追えばよいのだが、_winapiC言語で書かれている。 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"と返されてしまうのである。