nykergoto’s blog

機械学習とpythonをメインに

format記法の使い道

pythonでは変数の情報を文字列にする方法がいくつかありますが、その中でも僕はformat記法をよく使っています。

特に最近「dictionaryを文字列にしたいなぁ」という場合に、とてもきれいに書くことができるということを発見(というかドキュメントを読んで判明した)ので「こういう場合に便利ですよ」という意味合いでメモしておきます。

Case: dictionaryからstringに変換したい

例えば、機械学習なんかをやろうと思うと、パラメータをディクショナリとして渡す場面がよくあると思います。仮にそのパラメータをparamsとして以下のように定義します。

params = {
    'estimator':'svm',
    'gamma':0.1,
    'eta':100,
}

このパラメータを使って学習した結果を保存しよう、となると「このパラメータ使いましたよ」という名前で保存したくなります。そういうときにdictionary→stringに変換したくなります。

単純に要素をすべて列挙してそれをつなげる、という風にすると以下のようなプログラムが考えられます。

pstr = ''
for key,val in params.items():
    if pstr != '':
        pstr += '_'
    pstr += key + '-' +str(val)
print(pstr) # <<< 'estimator-svm_eta-100_gamma-0.1'

これでいいように感じるのですが、一つ問題があります。

dictionaryは入っている要素の順番を保存していないので、実験を繰り返しているとタイトルの順番が変化してしまう場合があるのです。(実際にあってとても困りました)

'estimator-svm_eta-100_gamma-0.1' これが 'eta-100_estimator-svm_gamma-0.1' こんな風になる場合があるってこと

また先のスクリプトでは辞書の値をすべてstrを用いて文字に変換しています。これも問題があって、例えばfloatをstrを用いて文字にすると桁数の指定ができないのでstr(val)[:2]のようにして長さを揃えたりしなくてはならず、しかもこれをすると四捨五入等も行ってくれないので、まあ言ったらちょっと不便です。

そこで最近はformat記法を用いて文字列に変換するようにしています。

estimator = 'svm'
gamma = 0.166
eta = 100
pstr = 'estimator-{0}_gamma-{1:.2f}_eta-{2}'.format(estimator,gamma,eta)

print(pstr) # estimator-svm_gamma-0.17_eta-100

{}の中の番号がformatで与えた引数の順番に対応しています。また同時にそれをどういう形でstrにするかも指定できるので、先の例だとgammaを下二桁の少数で、という指定をしているので0.17として出力されています。

これでもだいぶ便利なのですが、コードを書いていて「新しい変数hogeも名前の先頭に追加したいなー」となったとき、先頭にhogeを追加すると、順番がひとつづつずれてしまうので、0,1,2を1,2,3にしなくてはならなくなり、面倒です。

これを回避するためには、最後にhogeを追加して、3番めの引数を文字列の先頭に持ってくる、というformat内部の並びと、文字の対応が取れないコードになってしまいます。ちょっとアホっぽいですね。

hoge = 0.2
estimator = 'svm'
gamma = 0.166
eta = 100
pstr = 'hoge-{3:.3f}_estimator-{0}_gamma-{1:.2f}_eta-{2}'.format(estimator,gamma,eta,hoge)

print(pstr) # hoge-0.200_estimator-svm_gamma-0.17_eta-100

辞書の展開わたし

これを回避するのが、辞書を展開して渡してあげる、という方法です。(format(**some_dict))

展開した辞書の値をほしいときには{key}としてあげます。具体的には一番最初の例だとこんな感じ。

pstr = 'estimator-{estimator}'.format(**params)
print(pstr) # estimator-svm

この書き方の良い点は、stringの中にkeyを書くことになるので、ぱっと見たときにこの文字列が何になるのか、がわかりやすいという点です。

短所として長くなると手で書いているのが面倒になるという点がありますが、それでも読みやすいわかりやすいというメリットは大きいと思います。

しかしこの方法は文字にしたいものが最初からdictionaryになっている場合にしか使えません。

locals()

なのですが、これと組み込み関数のlocals()を用いるとその場合でも楽に変換できます。

localsはその場に定義されている変数を辞書にして返す関数です。なのでこれとformatを組み合わせると、その場にある変数の文字列を先の記法で作ることができます。

val = 100
name = 'some_method'

tstr = 'val={val}_name={name}'.format(**locals())
print(tstr) # val=100_name=some_method

0,1,2と指定する場合に比べて、明らかに見やすいですね。

まとめ

localsとか展開渡しとか全然知らずにアホっぽいコード量産していました。ドキュメントはちゃんと読みましょう。