ラムダ式との出会い
Pythonについて勉強中の江藤です。 先週、Pythonで画像処理を行うライブラリについてしらべていたところ、Qiitaの『PILでEXIF Orientationタグを考慮して処理』という記事で以下のような方法が掲載されていました。
# Orientation タグ値にしたがった処理
# PIL における Rotate の角度は反時計回りが正
convert_image = {
1: lambda img: img,
2: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT), # 左右反転
3: lambda img: img.transpose(Image.ROTATE_180), # 180度回転
4: lambda img: img.transpose(Image.FLIP_TOP_BOTTOM), # 上下反転
5: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Pillow.ROTATE_90), # 左右反転&反時計回りに90度回転
6: lambda img: img.transpose(Image.ROTATE_270), # 反時計回りに270度回転
7: lambda img: img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Pillow.ROTATE_270), # 左右反転&反時計回りに270度回転
8: lambda img: img.transpose(Image.ROTATE_90), # 反時計回りに90度回転
}
img = Image.open(file_path)
exif = img._getexif()
orientation = exif.get(0x112, 1)
new_img = convert_image[orientation](img)
何これ、全然わからん
convert_image の箇所で何か技巧的なことをしているのですが、構文が全く理解出来ませんでした。 当初、これって 難読なPythonアンチパターンなんじゃない?と思って見なかったことにしたのですが、 先輩によるとこれは 簡潔で良いコードなのだそうです。 という訳で、一つずつ砕いて理解しました。
この記事はその際の記録です。
その1 : lambda 式
lamnda 式って何?
lambda から始まる箇所のことを「無名関数」とか「ラムダ式」と言うそうで、「関数をより簡潔に書くための書式」だそうです。
Python における Lambda 式の構文は下記のようになるそうです
lamnda [引数]: [返り値]
私が苦手な 三項演算子によく似ています。
実際に書いてみる
これだけ見てもよく分からないので、簡単な関数を書いて、それをラムダ式にしてみました。 以下のような関数を書いてみました。引数に取った数に1を加える関数です
def add_one(num):
return num + 1
a = 10
print add_one(a) # => 11
関数 add_one を先ほどのラムダ式の構文に当てはめると、こうなります。
add_one_lambda = lambda num: num + 1
a = 10
print add_one_lambda(a) # => 11
なんとなく、ラムダ式の箇所で何が起こっているのかが分かりました。
その2 : データ構造”辞書”
辞書って何?
キーによって値を指定するデータ構造のことを、Pythonだと「辞書(dict)」と呼ぶそうです。 他の言語において「連想配列」とか「ハッシュマップ」と呼ばれているものと同じものの様です。 これは、研修で使ったJavaやPHPでも同じようなものがあったので、使い方はなんとなく分かりました。
実際に書いてみる
例えば
ret_val_map = {
1: "C",
2: "PHP",
3: "Java",
4: "Python"
}
lang_type = 4
print "Do you like " + ret_val_map[lang_type] + "?"
# => Do you like Python?
その3 : ラムダ式と辞書の合わせ技
最初に掲載したサンプルコードだと、ラムダ式と辞書を合わせて使っていました。 上の例における “PHP” や “Java” の箇所にラムダ式が入っている感じでしょうか。
ポイントは呼び出すときの書式
辞書の変数名[辞書の添え字](ラムダ式の引数)
を覚えるのが大変ということな気がします。
実際に書いてみる
add_type の値によって、加算する数値が変わるようにしてみました。
ret_val_map = {
1: lambda num: num + 1,
2: lambda num: num + 10,
3: lambda num: num + 100
}
add_type = 2
num = 1
print ret_val_map[add_type](num) # => 11
最初のサンプルと同じ形になりました! これで、あのコードがなにをしているのかが漸く分かりました。
おわりに
こんなに長々と書いているのに ほとんど断定の文がないという、 非常に危うい記事が出来上がってしまいました。
この記事を書くために、この記事に載せた以外にもサンプルプログラムを書いて実行してと繰り返していたのですが、
ラムダ式だと簡潔に書けるということがなんとなく分かってきました。
例えば、最後に載せたサンプルをラムダ式を用いずに関数で書いてみたのが以下の物になります。
def get_value(type, num):
# 値を加算するための関数定義
def add_one(num):
return num + 1
def add_ten(num):
return num + 10
def add_hundred(num):
return num + 100
# 引数 type によって処理を分岐
if type == 1:
return add_one(num)
elif type == 2:
return add_ten(num)
elif type == 3:
return add_hundred(num)
add_type = 2
num = 1
print get_value(2, 1)
コードの行数もインデントも増えました。 この場合はラムダ式で書いた方が良さそうです。 適材適所だとは思うのですが、必要となったときにパパッと書けるように、 あの独特の構文に慣れていこうと思います。
江藤 光
まだまだ気持ちは新人です。