関数型プログラミング

 

 

Advertisement

関数型プログラミング

 Python はオブジェクト指向言語でありながら、関数型プログラミングとしての機能も備えています。命令を処理するために複数の手法(パラダイム)を備える言語のことをマルチパラダイム言語とよぶことがありますが、Python もそうしたマルチパラダイム言語のひとつです。

 オブジェクト指向において、オブジェクトは内部データとそれを変更するためのメソッドが一体となったカプセルであり、プログラムはメソッドの使用によって内部データを変化させていく手続きです。一方で関数型プログラミングにおいては、できる限り状態の変更を避けるようにして、プログラムは関数同士で受け渡されるデータの流れとして表現されます。

 Python ではこの2種類のパラダイムが結び合わされています。Python においては関数自体も含めてすべてがオブジェクトなので、関数の間で受け渡されるのはオブジェクトです。関数型スタイルでは、このオブジェクトの流れを制御するようにコードが記述されます。
 

関数型で記述するメリット

 オブジェクト指向においてメソッドはオブジェクト自身に備わるものなので、あるオブジェクトのもつ内部データを変更したいと思っても、必ずしも目的に合うようなメソッドが備わっているとは限りません。そうした場合、オブジェクトのデータを異なるクラスのオブジェクトのデータに渡して処理したあとで、もう一度元のクラスのオブジェクトにデータを渡すという手順を踏むことになります。

 関数型プログラミングでは、一般に関数の引数に渡すデータの種類を問わない(あるいは幅広い種類のデータを扱える)ことが多いので、上記のような複雑な手続きを経る必要はなく、より短いコードで処理できます。
 

Python における関数型プログラミング

 Python においては、イテレータ、ジェネレータ、lambda(ラムダ)式、内包表記 (コンプリヘンション)、高階関数、デコレータなどの機能を使って関数型スタイルのコードを記述します。こうしたツールを使いこなせるようになると、驚くほど短く簡潔なコードを書くことができるようになります。
 とはいえ、複雑な処理を一行のコードにまとめてしまうようなやり方が推奨されているわけではありません。コードを短縮しすぎると逆に可読性が落ちるというパラドックスに遭遇します。特にチームで運用するコードであれば過剰な短縮はメンテナンスの障害ともなりえます。時として書いた本人でさえも、時間が経って見直したときにコードの意味がわからなくなることがあります。コードの短縮と可読性のバランスには常に気を配っておく必要があります。
 

東京大学のデータサイエンティスト育成講座 ~Pythonで手を動かして学ぶデ―タ分析~

新品価格
¥3,218から
(2019/8/6 11:41時点)

関数オブジェクト

 Python で関数を使用するときは、関数名(引数) と記述します。コードを実行すると、その関数の機能に応じて様々なクラスのオブジェクトが返ります。たとえば、オブジェクトの長さを返す組み込み関数 len() に "Python" という文字列を渡すと「6」という intクラスのオブジェクトが返ります。

# FUNC_01

# In[1]

x = len("Python")

print(x)
print(type(x))
6
<class 'int'>

 今度は () を添えずに len という関数名だけを変数 x に渡して、print(x) で表示させてみます。

# In[2]

x = len

print(x)
print(type(x))
<built-in function len>
<class 'builtin_function_or_method'>

 変数 x は関数オブジェクト (builtin_function_or_method クラスのインスタンス)を参照していることがわかります。x() にオブジェクトを渡すと、そのオブジェクトは参照先の len() に渡され、オブジェクトの長さを返します。

# In[3]

# xに関数lenを格納
x = len

# 関数xに文字列を渡す
y = x("Python")

print(y)
6

 Python ではすべてがオブジェクトです。もちろん関数もオブジェクトです。
 関数をオブジェクトであることを明示するために、関数のことを敢えて 関数オブジェクト とよぶことがあります。関数型プログラミングでは関数オブジェクトの受け渡しがコードの核となります。

 Python のリストはほとんどあらゆるオブジェクトを格納できます。もちろん、関数オブジェクトを要素にもつこともできます。それほど頻繁に使うわけではありませんが、複数の関数をリストにまとめておくと便利なこともあります。まずは簡単な例を見てみましょう。

# FUNC_02

# In[1]

import math

# 関数リスト
func_list = [math.sqrt, math.exp, math.cos]

# √10, exp(10), cos(10)を計算
for f in func_list:
    print(f(10.0))
3.1622776601683795
22026.465794806718
-0.8390715290764524

 このコードのループ処理では最初にループ変数 f に関数オブジェクト math.sqrt を入れてます。そして関数 f に 10.0 を渡して平方根を表示します。次に f に math.exp を入れて、10.0 を渡してexp(10.0) を表示します。最後に f に math.cos を入れて、10.0 を渡して cos(10.0) を表示させています。

 次のサンプルは、あるオブジェクトを float() に渡し、その戻り値を int() に渡し、その戻り値を str() に渡すというコードです。

# In[2]

# 関数のリスト
func_list = [float, int, str]

x = "10.5"

for k in range(3):
    x = func_list[k](x)

print(x)
10

 最終的に「10」という文字列 (strクラスのオブジェクト) が返ります。ループ処理のブロックに記述されている

x = func_list[k](x)

という部分では、func_list のインデックス k の要素に x を渡し、その戻り値を x に入れるという再帰的な処理が行われています。