6-2. 高階関数¶
Pythonにおける高階関数について説明します。
参考
max
¶
例として、関数 max
について考察します。max
は与えられたリストの要素のうち、最大のものを返します。
[1]:
ls = [3,-8,1,0,7,-5]
max(ls)
[1]:
7
max
に key
というキーワード引数として、たとえば関数 abs
を与えることができます。 (キーワード引数について詳しくは、3-3を参照してください。)
[2]:
max(ls, key=abs)
[2]:
-8
この場合、各要素に関数 abs
が適用されて、その結果が最も大きい要素が返ります。 (各要素に abs
適用した結果の中の最大値が返るわけではないことに注意してください。) なお、abs(x)
は x
の絶対値を返します。
この場合、max
という関数は、関数を引数として受け取っています。
一般に、関数を引数として受け取ったり返値として返したりする関数を高階関数といいます。
sorted
¶
sorted
も高階関数で、max
と同様に key
というキーワード引数を取ります。
[3]:
sorted(ls, key=abs)
[3]:
[0, 1, 3, -5, 7, -8]
このように、各要素に関数 abs
を適用した結果によって、各要素をソートします。
リストを降順にソートするには、次のような関数を用いればよいです。
[4]:
def invert(x):
return -x
[5]:
sorted(ls, key=invert)
[5]:
[7, 3, 1, 0, -5, -8]
なお、リストを降順にソートするには、reverse
というキーワード引数に True
を指定するという方法もあります。
[6]:
sorted(ls, reverse=True)
[6]:
[7, 3, 1, 0, -5, -8]
ラムダ式¶
上の invert
のような簡単な関数の場合、 いちいち def
で定義するのは面倒と思いませんか。
そのようなときは、lambda
を使ったラムダ式(または無名関数)を用いることができます。 上の例は、以下のように書くことができます。
[7]:
sorted(ls, key=lambda x: -x)
[7]:
[7, 3, 1, 0, -5, -8]
lambda x: -x
という式は、x
をもらって -x
を返す関数を表します。 return
は書かないことに注意してください。
さて、ここまで関数と呼んでいたものは、Pythonでは、オブジェクトの一種に他なりません。 実際に、abs
や lambda x: -x
という式の値を調べてみてください。
[8]:
abs
[8]:
<function abs(x, /)>
[9]:
lambda x: -x
[9]:
<function __main__.<lambda>(x)>
したがって、Pythonでは、関数を他の種類のデータ(数やリストや文字列など)と同様に、 関数の引数にしたり、リストの要素にしたり、することができます。
リストからイテラブルへ¶
以上の例では、max
や sorted
はリストを受け取っていましたが、 リストではなく、タプルでもいいですし、文字列でも構いません。
[10]:
max((3,-8,1,0,7,-5))
[10]:
7
[11]:
sorted((3,-8,1,0,7,-5))
[11]:
[-8, -5, 0, 1, 3, 7]
[12]:
max('hello world')
[12]:
'w'
[13]:
sorted('hello world')
[13]:
[' ', 'd', 'e', 'h', 'l', 'l', 'l', 'o', 'o', 'r', 'w']
すなわち、max
や sorted
は、一般にイテラブルを引数に取ることができます。
イテラブルについては4-2に説明がありましたが、 簡単に言うと、イテラブルとはfor文の in
の後に来ることができるものです。 max
や sorted
は、イテラブルの各要素を次々と求めて、 その中の最大値を求めたり、整列した結果をリストとして返したりします。
以下の例では、max
にファイルオブジェクトが渡されます。 ファイルオブジェクトはイテレータですので、イテラブルでもあります。 ファイルオブジェクトをfor文の in
の後に指定すると、 ファイルの各行が文字列として得られます。 以下の例では、key
として関数 len
が指定されていますので、 ファイルの中で最も長い行が表示されます。
[14]:
with open('jugemu.txt', 'r', encoding='utf-8') as f:
print(max(f, key=len))
グーリンダイのポンポコピーのポンポコナーの、
辞書もイテラブルです。max
に辞書与えると、最大のキーが返ります。
[15]:
max({3:10, 5:2, 9:1})
[15]:
9
練習¶
辞書 d
が与えられたとき、 最大の値を持つキー(複数個ならばそのいずれか)を返す関数 max_value_key(d)
を、 max
を使って定義してください。
ヒント:辞書 d
のキー k
に対して、k
に対応する値を返す関数は lambda k: d[k]
という式で表すことができます。
[16]:
def max_value_key(d):
...
上のセルで解答を作成した後、以下のセルを実行し、実行結果が True
になることを確認してください。
[17]:
print(max_value_key({3:10, 5:2, 9:1}) == 3)
False
map
¶
以下は内包表記の例です。
[18]:
[abs(x) for x in [3,-8,1,0,7,-5]]
[18]:
[3, 8, 1, 0, 7, 5]
リストの各要素に関数 abs
が適用された結果がリストになります。
同様のことを、高階関数 map
を用いて行うことができます。 関数 map
は、2番目の引数としてイテラブルを取ります。1番目の引数は関数です。 例を見ましょう。
[19]:
map(abs, [3,-8,1,0,7,-5])
[19]:
<map at 0x11288c2b0>
何が返ったか、よくわからないと思います。 以下のように、map
の結果をfor文の in
の後に指定してみましょう。
[20]:
for x in map(abs, [3,-8,1,0,7,-5]):
print(x)
3
8
1
0
7
5
すなわち、map
が返すものはイテレータです。 このイテレータは、2番目の引数のイテラブル(この例ではリスト)の各要素に 関数 abs
を適用したものを、次々と返すようなイテレータです。
内包表記を使えば、以下のようにリストにまとめることができます。
[21]:
[x for x in map(abs, [3,-8,1,0,7,-5])]
[21]:
[3, 8, 1, 0, 7, 5]
関数 list
をイテレータに適用してもよいです。
[22]:
list(map(abs, [3,-8,1,0,7,-5]))
[22]:
[3, 8, 1, 0, 7, 5]
しかし、これでは、イテレータがどのように動くのか、よくわからないと思います。
以下では、呼ばれるたびにメッセージを出力する関数 abs1
を用います。
[23]:
def abs1(x):
print('abs called on', x)
return abs(x)
[24]:
it = map(abs1, [3,-8,1,0,7,-5])
このように、map
がイテレータを返した時点では、各要素に対する計算は何も行われません。
このイテレータに next
を適用するとどうなるか、見てください。
[25]:
next(it)
abs called on 3
[25]:
3
[26]:
next(it)
abs called on -8
[26]:
8
関数 next
が呼ばれるたびに、次の要素を求める計算が行われていることがわかります。
イテレータはイテラブルですから、map
の結果にさらに map
を適用することができます。
[27]:
list(map(lambda x: x+1, map(abs, [3,-8,1,0,7,-5])))
[27]:
[4, 9, 2, 1, 8, 6]
lambda x: x+1
は、x
をもらって x+1
を返す関数です。 すなわち、引数に 1
を足した結果を返します。
関数 sum
は、max
と同様に、イテラブルを受け取って、その要素の総和を返します。 したがって、map
が返したイテレータに対しても適用できます。 (イテレータをリストに変換する必要はありません。)
[28]:
sum(map(lambda x: x+1, map(abs, [3,-8,1,0,7,-5])))
[28]:
30
練習¶
数のリストが与えられたとき、その要素の絶対値の最大値を返す関数 max_abs
を、 map
と max
を使って定義してください。
[29]:
def max_abs(ln):
...
上のセルで解答を作成した後、以下のセルを実行し、実行結果が True
になることを確認してください。
[30]:
print(max_abs([3,-8,1,0,7,-5]) == 8)
False
filter
¶
関数 filter
もイテラブルをもらってイテレータを返します。 最初の引数としては、真理値を返す関数を指定します。
[31]:
def pos(x):
if x>0:
return True
else:
return False
この関数 pos
は、引数が正ならば True
、そうでなければ False
を返します。
すると、以下のように、filter
は pos
を適用すると True
が返る要素のみからなるイテレータを返します。
[32]:
list(filter(pos, [3,-8,1,0,7,-5]))
[32]:
[3, 1, 7]
filter
は、条件付き内包表記に対応しています。 同じ計算を以下のようにして行うことができます。
[33]:
[x for x in [3,-8,1,0,7,-5] if pos(x)]
[33]:
[3, 1, 7]
練習¶
数のリスト ln
と数 n
を受け取って、ln
の要素のうち、n
より大きい個数を返す関数 number_of_big_numbers(ln, n)
を、for文やwhile文を用いずに、filter
を用いて定義してください。
[34]:
def number_of_big_numbers(ln, n):
...
上のセルで解答を作成した後、以下のセルを実行し、実行結果が True
になることを確認してください。
[35]:
print(number_of_big_numbers([10, 0, 7, 1, 5, 2, 9], 5) == 3)
False
練習¶
ファイル名 file
と整数 n
を受け取って、そのファイルをオープンし、 (改行文字も含めて)長さが n
より長い行の数を返す関数 number_of_long_lines(file,n)
を定義してください。 (ファイルは encoding='utf-8'
でオープンしてください。)
[36]:
def number_of_long_lines(file, n):
...
上のセルで解答を作成した後、以下のセルを実行し、実行結果が True
になることを確認してください。
[37]:
print(number_of_long_lines('jugemu.txt', 10) == 6)
False
練習の解答¶
[38]:
def max_value_key(d):
return max(d, key=lambda k: d[k])
[39]:
def max_abs(ln):
return max(map(abs, ln))
[40]:
def number_of_big_numbers(ln, n):
return sum(map(lambda x: 1, filter(lambda x: x>n, ln)))
[41]:
def number_of_long_lines(file, n):
with open(file, 'r', encoding='utf-8') as f:
return sum(map(lambda x: 1, filter(lambda x: len(x)>n, f)))
[ ]: