PythonでWindowsのコマンドを実行したときの言語環境について

Windows環境のPythonにおいて`subprocess`を実行するとコマンドを実行できる
しかし, 次の問題が発生する

1. PCの設定によって文字エンコーディングが変わること
1. PCの言語環境によって返ってくる言葉が変わること

以上のことに対処した

# 先に結論
1. 現在の文字エンコーディングは `locale.getpreferredencoding()` でわかる
1. 言語環境は `subprocess.run(['chcp.com', '437'])` で変更できる

# 文字エンコーディングの対処

Pythonのsubprocessを使って入手できる出力はデコードする必要がある
```python
subprocess.run(
    ['ipconfig'], check=True, stdout=subprocess.PIPE,).stdout

"""
\r\nWindows IP \x8d\\\x90\xac\r\n\r\n\r\n\x83C\x81[\x83T\x83l\x83b\x83g...
"""
```

日本語の環境では cp932 (shift-JIS) でデコードすればよい
```python
subprocess.run(
    ['ipconfig'], check=True, stdout=subprocess.PIPE,)\
        .stdout.decode('cp932')

"""
\r\nWindows IP 構成\r\n\r\n\r\nイーサネット アダプター イーサネット:...
"""
```

しかし, 文字コードは言語環境が変われば変わってしまうため, ハードコーディングはしたくない

調べてるとchcpを使ってコードページを変更する例がでてくる
しかし、Pythonの`subprocess`からは文字コードを変えることはできなかった。

```python
subprocess.run(
    ['chcp.com', '65001'], check=True, stdout=subprocess.PIPE,).stdout

"""
Active code page: 65001\r\n
"""

subprocess.run(
    ['ipconfig'], check=True, stdout=subprocess.PIPE,).stdout

"""
\r\nWindows IP Configuration\r\n\r\n\r\nEthernet adapter \x83C\x81[\x83T\x83l\x83b\x83g: ...
"""
```

## 文字コードの確定
`locale` モジュールの `getpreferredencoding` 関数を用いることで環境の文字コードを取得可能である
```python
locale.getpreferredencoding()

"""cp932"""

subprocess.run(
    ['ipconfig'], check=True, stdout=subprocess.PIPE,).stdout.decode(
        locale.getpreferredencoding())

"""
\r\nWindows IP 構成\r\n\r\n\r\nイーサネット アダプター イーサネット:...
"""
```

# 言語環境の設定
言語環境によって表示されるキーワードが変わる.
これは特定の情報を入手したいときにキー名が環境で代わり不便である.
```python
p = subprocess.run(
    ['ipconfig'], check=True, stdout=subprocess.PIPE,)
data = p.stdout.decode(locale.getpreferredencoding()).split('\r\n')

for line in data:
    print(line)

"""
Japanese環境
----------
Windows IP 構成


イーサネット アダプター イーサネット:

   メディアの状態. . . . . . . . . . . .: メディアは接続されていません
   接続固有の DNS サフィックス . . . . .: 
...

US環境
----------
Windows IP Configuration


Ethernet adapter イーサネット:

   Media State . . . . . . . . . . . : Media disconnected
   Connection-specific DNS Suffix  . : 
...
"""
```

そこで `chcp` コマンドによって言語環境を変更しUS環境で情報を取得する
（ただし上のように「Ethernet adapter **イーサネット**:」のような日本語文字が残ってしまうようだ）
https://learn.microsoft.com/ja-jp/windows-server/administration/windows-commands/chcp

コンテキストマネージャを使いこのように書ける
```python

@contextmanager
def code_page(code_page: int):
    encoding = locale.getpreferredencoding()

    try:
        p = subprocess.run(
            ['chcp.com'], check=True,
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        init_cp = p.stdout.decode(encoding=encoding).replace('\r\n', '')\
            .split(' ')[-1]

        subprocess.run(
            ['chcp.com', str(code_page)], check=True,
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        yield
    finally:
        subprocess.run(
            ['chcp.com', init_cp], check=True,
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)

with code_page(437):
    d = subprocess.run(
        ['ipconfig'], check=True, stdout=subprocess.PIPE,)
data = d.stdout.decode(locale.getpreferredencoding()).split('\r\n')
```

with 文を使いコードページを臨時で変更することができる。

# おわり
コード全体
```python

from contextlib import contextmanager
import subprocess
import locale

# %%
@contextmanager
def code_page(code_page: int):
    encoding = locale.getpreferredencoding()

    try:
        p = subprocess.run(
            ['chcp.com'], check=True,
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        init_cp = p.stdout.decode(encoding=encoding).replace('\r\n', '')\
            .split(' ')[-1]

        subprocess.run(
            ['chcp.com', str(code_page)], check=True,
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        yield
    finally:
        subprocess.run(
            ['chcp.com', init_cp], check=True,
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)

# %%
def get_ipconfig():
    with code_page(437):
        d = subprocess.run(
            ['ipconfig'], check=True, stdout=subprocess.PIPE,)
    return d.stdout.decode(locale.getpreferredencoding()).split('\r\n')

# %%
data = get_ipconfig()
for line in data:
    print(line)

# %%
```

以上