久々に読めないコードに対峙
これまでPython以外にもJava、JavaScript、PHP、C言語、C#、VBA、VB、Delphi、CSS、HTMLなんかを触ってきて、「プログラムってだいたいこんな感じの書き方」みたいなものがあると思っている。
そんな中、他人のコードを読もうとしたときに???となった。
一般的なのか分からんけど、メモしておく。
謎のコードは関数をいい感じに使うためのものだった
まず、何を見たのかというと、
from functools import partial, reduce
from toolz import pipe
from toolz.functoolz import curry
from toolz.curried import map as cmap
class Xxx:
def hogehoge(self, a, b):
...
def bar(self, a, b, c):
...
def fugafuga(self, a, b, c):
...
def main(self):
hoge = curry(self.hogehoge)("XXX")
foo = partial(self.bar, a='xxx')
fuga = reduce(self.fugafuga, "yyy")
...
with path.open() as fp:
return pipe(
json.load(fp),
cmap(itemgetter("from", "to")),
partial(sorted, key=len, reverse=True)
)
(゚Д゚)ハァ? カレー?
パーシャル?直訳だと「部分的」とかそんな感じだよな
パイプはシェルとかのアレだよな
リデュース・・・・英語の意味も知らん
とりあえず意味を調べていく。
curry(外部ライブラリ)
カリー化という概念があるらしい。
Wikipediaによると、複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である
とある。
はぁ(;・∀・)
JSの書き方が分かりやすい。
普通の関数は
function sum(x, y){
return x + y;
}
console.log(sum(1, 2))
// 3
として、カリーっぽい書き方をすると
function _sum(x){
return function(y){
return x + y;
}
}
tochu = _sum(1)
console.log(tochu(2))
// 3
となる。
tochu
にセットされた戻り値は、普通の関数sum
でいうところの第一引数が埋まった関数(第二引数だけ足らないsum
)になる。
なので、tochu
に引数を与えると、sum
と同様の結果になる。
なるほど、これがカリー化か。
###メリット・使いどころ
ググったところだと、
-
引数がすぐに揃わない、順々に埋めていきたいとき
# hogehoge が hoge(a, b, c) をカリー化したものとして tochu = hogehoge(a) # b を算出処理 ... tochu = tochu(b) # c を算出処理 ... kekka = tochu(c)
-
引数を部分的な固定をしたいとき(これはpartialとも機能が被る)
# hogehoge が sum(a, b) をカリー化したものとして kotei = hogehoge(1) print(kotei(2)) # 3 print(kotei(3)) # 4
partial(標準ライブラリ)
部分適用
という概念になるらしい。
カリー化とは厳密には区別されるものらしい。
関数をカリー化すると、結果的に部分適用の関数ができるみたいな理解をしている。(正確性は謎)
カリー化の引数を部分的に固定したいときの例のままで、指定の関数の引数を一部固定したものを新たな関数の変数として生成する。
###メリット・使いどころ
-
引数を部分的な固定をしたいとき
kotei = partial(sum_, a=1) print(kotei(2)) # 3 print(kotei(3)) # 4
reduce(標準ライブラリ)
これは結構解説しているサイトも多い。
python2.xの時は標準の関数だったらしい。
中々動作が言葉にするとややこしい。
reduce関数では、
- 第一引数に引数を2つ取る関数を指定する
- 第二引数にはイテラブルなオブジェクト(リストや辞書)を指定する
ことが前提となる。
動作は、以下のとおり。
def func(x, y):
return x * y
some_list = [1, 2, 3, 4, 5]
print(reduce(func, some_list))
# 120
-
some_listの最初の2つの要素に関数funcが適用される。
→
func(some_list[0], some_list[1])
-
最初の2つの要素を使って計算された値をresult1とすると、今度はこの値とsome_list[2]をfuncは引数にして、計算する。
→
func(result1, some_list[[2])
-
あとは、リストの要素がなくなるまで繰り返す。
→
func(...(func(func(some_list[0], some_list[[1]), some_list[2])...), some_list[[n])
###メリット・使いどころ
同じ関数を繰り返し適用する場合に使用するのだろうけど、なかなか「ここだ!」というところが出てこない。
階乗の算出や合計の算出、パンくずリストの文字列生成に使うというのも考えられるけど、すでにそれ用の関数はあるので、これを使うことはパッと思いつかないかな・・・。
覚えておいて、「あ、reduce使えばいいじゃん」となる日を待つ感じになりそう。
pipe(外部ライブラリ)
shellで使うパイプと同じように、最初の関数の結果(値でも良さそう)が、次に指定した関数の引数になる。
def double(a):
return a + a
def square(a):
return a * a
print(pipe(1, double, square))
# 4
###メリット・使いどころ
- 値の編集において、順繰りに編集関数をかけたいときに使えそう
- ただ、使用する関数は、引数が1つでないと動かなそう(イテラブルのオブジェクトをどうにかするるとか、実は複数引数でも動かせるとかあるかもだけど)
- 複数必要な場合は、partialやcurryを使って引数を抑える工夫が必要・・・
- だから今回見つけたコードはやたら部分適用やカリー化を使ってたのかΣ(゚Д゚)
まとめ
使わないでもコードは書けるけど、書き方をコンパクトにできるという利点のあるこれらの機能。
誰かにコードを見られたとき、「こいつ・・・できる!!」感を出せる反面、ぱっと理解してもらえないというデメリットもあると思う。
ここぞというときのテクニックですな。
ちなみにカリーとは人の名前(ハスケル・カリー)から来ていて、食べ物とは何ら関係ないらしい(;´Д`)
2021/5/8 加筆
functools
は標準ライブラリで、toolz
は外部ライブラリであることをコメントより教えていただいた。
標準ライブラリ
Pythonをインストールした時点で入ってるライブラリ。
ドキュメントに普通に載ってる。
functools
以外には、itertools
とoperator
があるらしい。(version3.9.4では)
Pythonのバージョンが違うと標準ライブラリから消えるとかもあるとかあるらしいので、Pythonのバージョンアップの時に見るポイントなのかもしれないと思った。
外部ライブラリ
pip install xxxx
でインストールして使うライブラリ。
本稿で扱ったライブラリは
https://github.com/pytoolz/toolz
にあるのだけど、メジャーなライブラリなのかはよく分からない(´・ω・)
現時点のgithubのstarは3.5kあるけど、多いのか少ないのか分らん。