6-2. 高階関数

Pythonにおける高階関数について説明します。

参考

max

例として、関数 max について考察します。max は与えられたリストの要素のうち、最大のものを返します。

[1]:
ls = [3,-8,1,0,7,-5]
max(ls)
[1]:
7

maxkey というキーワード引数として、たとえば関数 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では、オブジェクトの一種に他なりません。 実際に、abslambda x: -x という式の値を調べてみてください。

[8]:
abs
[8]:
<function abs(x, /)>
[9]:
lambda x: -x
[9]:
<function __main__.<lambda>(x)>

したがって、Pythonでは、関数を他の種類のデータ(数やリストや文字列など)と同様に、 関数の引数にしたり、リストの要素にしたり、することができます。

リストからイテラブルへ

以上の例では、maxsorted はリストを受け取っていましたが、 リストではなく、タプルでもいいですし、文字列でも構いません。

[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']

すなわち、maxsorted は、一般にイテラブルを引数に取ることができます。

イテラブルについては4-2に説明がありましたが、 簡単に言うと、イテラブルとはfor文の in の後に来ることができるものです。 maxsorted は、イテラブルの各要素を次々と求めて、 その中の最大値を求めたり、整列した結果をリストとして返したりします。

以下の例では、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 0x7fe150284640>

何が返ったか、よくわからないと思います。 以下のように、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 を、 mapmax を使って定義してください。

[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 を返します。

すると、以下のように、filterpos を適用すると 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)))
[ ]: