argparseのハイフン置き換え仕様でハマった
argparseではハイフンのコマンドライン引数をアンダーバーに置き換える仕様があります。これはPythonの言語仕様の都合によるものですが、思わぬハマり方をしたので自分のメモ用に書いておきます。
目次
argparseはハイフンをアンダーバーに置き換える
これが自分の半日を溶かしたPythonの💩仕様
こんなやり方でdictからargparseに値を代入させられるのは既に記事にしています。ここからが問題で、コマンドライン引数のオプションにハイフンが含まれると、Python側で勝手にアンダーバーに置き換える仕様があります。
参考:https://qiita.com/S-T/items/49febd9488a49efd8c9c
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--batch-size", type=int, default=64)
parser.add_argument("--log-directory", type=str, default="log")
parser.add_argument("--data-directory", type=str, default="")
args = parser.parse_args()
print(args)
例えばこのようにコマンドライン引数では、ハイフンありでも、実行すれば
Namespace(batch_size=64, data_directory='', log_directory='log')
アンダーバーに置き換えられます。これはハイフンありの変数や属性をPythonが認識できないためです。
問題になるのは値の置き換え
これが問題になるのは値を置き換えるケースです。いくつかパターンがあります。
1つ目はローカルで完結するケース。argparseの値をもっと管理しやすいjsonやyamlで置き換えたい。
2つ目はAWSのSageMakerのようにクラウドとローカルで両方動かすケース。ローカルでテストして、クラウドで動かすようなケースです。SageMakerとローカルでは、データやログのディレクトリが異なり、それが環境変数に格納されている(SM_MODEL_DIR、SM_CHANNEL_TRAINING)ので、SageMaker(クラウド)特有の環境変数の面倒を見ないといけない場合です。このケースでは、何らかのローカルのデフォルト値をセットしておいて、クラウド特有の環境変数をtry~exceptでとってとれたらアップデートということをするのがまず考えられます。こっちのほうがハマったときに数倍面倒くさいです。
今回はわかりやすいように1番目のローカルで説明しましょう。
ダメなケース:dictのキーをハイフンにしてしまう
自分がハマったダメなケースです。コマンドライン引数にあわせてdictのキーをハイフンのままでアップデートしています。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--batch-size", type=int, default=64)
parser.add_argument("--log-directory", type=str, default="log")
parser.add_argument("--data-directory", type=str, default="")
args = parser.parse_args()
update_values = {
"batch-size": 128,
"log-directory": "/opt/ml/output/log",
"data-directory": "input_dir"
}
for k, v in update_values.items():
args.__setattr__(k, v)
print(args)
珍妙なクリーチャーなDictが出来上がります。なんじゃこれ
Namespace(batch_size=64, data_directory='', log_directory='log', **{'batch-size': 128, 'data-directory': 'input_dir', 'log-directory': '/opt/ml/output/log'})
要するにアップデートしたのにアップデートされていないということです。これをSageMakerのTraining Jobの中でやられると大変デバッグに苦労します。
いいケース:可能ならコマンドライン引数を最初からアンダーバーにしてしまう解決法
dictのキーをアンダーバーにすればちゃんとアップデートできます。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--batch-size", type=int, default=64)
parser.add_argument("--log-directory", type=str, default="log")
parser.add_argument("--data-directory", type=str, default="")
args = parser.parse_args()
update_values = {
"batch_size": 128,
"log_directory": "/opt/ml/output/log",
"data_directory": "input_dir"
}
for k, v in update_values.items():
args.__setattr__(k, v)
print(args)
# Namespace(batch_size=128, data_directory='input_dir', log_directory='/opt/ml/output/log')
ただこれだと紛らわしいですよね。全部自分で書いたコードで変更可能なら最初からアンダーバーに共通化してしまうのはどうでしょうか。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--batch_size", type=int, default=64)
parser.add_argument("--log_directory", type=str, default="log")
parser.add_argument("--data_directory", type=str, default="")
args = parser.parse_args()
update_values = {
"batch_size": 128,
"log_directory": "/opt/ml/output/log",
"data_directory": "input_dir"
}
for k, v in update_values.items():
args.__setattr__(k, v)
print(args)
個人的にはこれに落ち着きました。
結論
何が言いたかったかというと、argparseにこんな仕様があったのを知らずに、半日か1日溶かしたのが💩だったということです。
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー