雑記 in hibernation

頭の整理と備忘録

lambda式って超便利

コーディングの文法でlambda式ってあるじゃないですか。あれ、何が有り難いのかイマイチよくわかってなかったんですけど、その恩恵がようやく理解できたのでPythonでのlambda式の使い方について備忘録にします。あんまり細かいtipsは記事化してもキリがないなあとは思っているので、コーディングの諸々に関するネタはあんまり書かないつもりではいるのですが、まあ今回はブログの賑やかしにでもなれば、くらいの感じで。

なお、今回主に参考にさせていただいた記事はこちらです。

note.nkmk.me

qiita.com

基礎 of 基礎

まずlambda式の記法から。ざっくりいうと「関数のお手軽版」みたいな的な感じらしい。

普通に関数を宣言する場合とlambda式を用いた場合との対応関係はこんな感じです。

  • defで定義する関数を使用する場合
# 2変数の和を返す関数
def sum_func(a,b):
    return a+b

sum_func(1,2)
3


  • lambdaを使用する場合
# 2変数の和を返す関数
sum_lambda = lambda a,b:a+b

sum_lambda(1,2)
3


じゃあ普通に関数定義すればよくね?

って思っちゃいますよね。僕は思っちゃいました。少なくとも上記の例を見る限りではlambdaの利点は見えてはきませんね。

ところがどっこい、ちゃんとlambdaでの記法が活きてくるケースがあるのです。

例えば、リストや辞書などの配列操作でsortやmap, filterなどを使う場合。狙いの操作に相当する関数をどこかで定義する必要がありますが、その場限りの処理のために新たに関数を定義するのも大袈裟な気もします。そこでlambdaを使うと、新たに関数を定義せずに(名前を付けずに)、簡潔に処理を記述することができます。

lambdaは無名で使ってナンボ、ということみたいです。

例1:リストをソートする

点群のリストを原点からの距離にしたがってソートするケースを考えます。

# リストの定義:各点の名前と座標(2次元)
point_list = [["a",1,2],["b",-4,1],["c",2,-2],["d",5,3],["e",3,3],["f",2,5]]
point_list
[['a', 1, 2],
 ['b', -4, 1],
 ['c', 2, -2],
 ['d', 5, 3],
 ['e', 3, 3],
 ['f', 2, 5]]


まずはlambdaを利用せず、関数を定義するケースです。 こんな感じ。

# 関数を定義して、、、、
def culc_distance(point):
  return math.sqrt(point[1]**2 + point[2]**2)

# 適用
sorted(point_list, key=culc_distance)
[['a', 1, 2],
 ['c', 2, -2],
 ['b', -4, 1],
 ['e', 3, 3],
 ['f', 2, 5],
 ['d', 5, 3]]


一方、lambdaを利用すれば一行ですっきり書けちゃいます。

# lambdaなら一行でいけちゃう
sorted(point_list, key=lambda x: math.sqrt(x[1]**2 + x[2]**2))
[['a', 1, 2], ['c', 2, 2], ['b', 4, 1], ['e', 3, 3], ['f', 2, 5], ['d', 5, 3]]


例2:pandas,DataFrameをソートする

pandasでも同じように恩恵をうけることができます。

またしても点群を原点からの距離にしたがってソートするケースを考えます。

# リストの定義:各点の名前と座標
point_list = [["a",1,2],["b",-4,1],["c",2,-2],["d",5,3],["e",3,3],["f",2,5]]
point_df = pd.DataFrame(point_list, columns=["name","x","y"])
point_df
name x y
0 a 1 2
1 b -4 1
2 c 2 -2
3 d 5 3
4 e 3 3
5 f 2 5


距離のカラムを作成したのち、これを元にソートを行います。apply時にlambdaを利用することで距離を計算する処理を簡潔に記載することができます。

point_df["distance"] = point_df.apply(lambda p: math.sqrt(p["x"]**2 + p["y"]**2) , axis=1)
point_df.sort_values("distance")
name x y distance
0 a 1 2 2.23607
2 c 2 -2 2.82843
1 b -4 1 4.12311
4 e 3 3 4.24264
5 f 2 5 5.38516
3 d 5 3 5.83095


※僕が調べた限りでは、データフレームではリストのソートとは異なり、df.sort_valuesのキーにlambdaを記述することなどはできないみたいでした。そのため一度ソートの基準になる列を作成してからソートを実施する2stepの手順になっています。もっと効率的な書き方があればご教示いただきたいところです。


補足

総じて、敢えて名前を付けて定義しなくても関数的な処理が実装できる使い勝手の良さがlambdaの利点のようです。実際、冒頭に掲載したリンク先の情報によると、pep8においてもlambda式で名前を割り当てる使い方は非推奨(=だったら普通にdefで関数を定義したまえよ、という考え)とのこと。

ラムダ式は呼び出し可能なオブジェクトを引数で渡すときなどに名前を付けずに使うためのもので、名前を付けて関数を定義する場合はdefを使うべき、というのがPEP8の考え方。


おわりに

ということで、lambdaについての備忘録でした。有効利用していきましょう。