関数 (Function)

関数

関数って何?

プログラミングの勉強をするときに、いくつかのハードルがあるといわれていますが、関数もその一つです。関数というと数学で学んできた関数(とその特性)を思い浮かべると思いますが、プログラミングにおける関数は、数学の関数とは違うものだと考えて頭をいったんリセットして「プログラミングにおける関数」の勉強をしてください。

日本人は、外国語の壁や中高校における数学教育から、Functionといえば専門用語としての関数、関数といえば「数学の関数」を強く想起します。しかしながら、関数を表す英語の Function の主要な意味は「日常用語として使われる機能」ですので、英語の圏の人は、プログラミングで出てくる Functionを「関数」ではなく、単純に「機能」(処理手順の塊)としてとらえており、日本人のような数学の関数の特性に引きずられた混乱はないのかもしれません。

プログラミングにおける関数とは、何かに対する一連の処理手順をひとまとまりに記述し、名前を付けて簡単に再利用できるようにしたものです。プログラミング言語によっては、数学の関数との混同を避けるためか、同様な機能をサブルーチン (subroutine)、あるいは手続き:プロシージャ― (procedure) と呼んでいるものもあります。

「プログラミングにおける関数」の「数学の関数」との違いをまとめると以下のようになります。

  • プログラムの関数は引数がなくても良い
  • プログラムの関数は返り値がなくても良い
  • 返り値がある関数は、すべての関数が同じ引数に対して必ず同じ返り値を返すわけではない

なぜ関数を定義するのか

プログラミング言語における関数とは何か

  • ソフトウェアにおける部品の作成:再利用化
    • 工業社会も部品を発明し、部品を組み合わせて多様で高品質な製品を作ることで成功した
  • 一連の処理を一塊にまとめる
    • 処理手順のモジュール化局所化
  • 一連の処理に名前を付ける
    • 抽象化:処理手順の詳細を知らなくても名前から処理を推定して使用できる
  • 一連の処理の入力と出力を明らかにする
    • その処理手順が入力として何を必要としなにを生成するのかを明確にする
  • あることに関する処理をその関数一か所のみに限定する

関数の定義

関数定義の基本形を以下に示します。

def funcName(arg1, arg2, ...):
    処理1
    処理2
    処理3
    return 返り値
  • def 関数名(引数1,引数2、...): の形式で関数定義を開始する
    • 関数名は基本的に英小文字と数字、下線(アンダースコア [ _ ])で記述する
    • 引数は0個を含めていくつ指定してもよい
    • 引数の名前も関数名の使用文字に準じる
    • 関数定義の最後にコロン [ : ]を忘れないこと。
  • 関数定義の2行目から書く処理は、インデントして記述すること。
    • 2つ目以降の処理のインデントの空白文字数は最初の処理のインデントの空白文字数にそろえること
  • インデントして処理が書かれていれば、行間が数行あいていても関数定義は継続する
    • インデントのない処理が書かれると、その前で関数定義が終了しているとみなされる。
  • 一般的には関数の最後に返り値を伴ったreturnを置く。
    • 関数の最後に必ずreturnを置く必要はない。returnが置かれていない関数は返り値がない関数と認識される。
    • returnのあとに返り値が書かれていない場合には、その関数は返り値がない関数と認識される。
    • returnは関数定義の最後だけでなく、if文を伴って、関数定義の途中に置かれることもある。
      • if文の条件判断によりreturnが実行される場合には、関数のそれ以降の処理は実行されない。

まずは、引数:入力が2つの数値で、その和を返す関数を定義してみましょう。

def plus(a, b):
    return a + b

print(plus(2,3)) # plus()が返す値を表示
5

この関数の例は、数学の関数の機能と同様で、引数を与えると、その引数に対応した答えを返します。

def printplus(a, b):
    print(a + b)     # この後にreturn がないので値を返さない

print(printplus(2,3))
5
None

関数の活用

数値を記録したリスト(配列)の操作

  • 最大値
  • 最小値
  • 総計値
  • 平均値

最大値を返す関数

多数の数値が記録されたリストの中の最大値を返す関数を作成しよう。

データがテストの点などと想定すると必ず0よりも大きいので、最大値の最初の候補を0とおいて、以下のような処理ができる。

  • 暫定最大値を0と置く
  • すべてのリストの要素に対して以下の処理を繰り返す
    • 注目しているリストの要素が暫定最大値よりも大きいならば
      • その要素を暫定の最大値とする
  • 現在の暫定最大値を真の最大値として返す
def saidai(num_list):
    nmax = 0
    for i in range(len(num_list)):
        if num_list[i] > nmax:
            nmax = num_list[i]
    return nmax

lst = [6,4,2,3,8,5,0]
print(lst, "の最大値は", saidai(lst))
[6, 4, 2, 3, 8, 5, 0] の最大値は 8

上記のプログラムでは、リストの要素の最大値が負の値だと誤って0を返すという問題があり、汎用的には使用できない。

最大値を求める汎用的な関数としては、与えられるデータの範囲がどのようなものであっても正しい値を返せるようにしたい。

リストの最初の要素を暫定的な最大値とみなして処理を行うと、上記のような問題はなくなる。

  • リストの最初の要素を暫定最大値と置く
  • 2番目以降のすべてのリストの要素に対して以下の処理を繰り返す
    • 注目しているリストの要素が暫定最大値よりも大きいならば
      • その要素を暫定最大値とする
  • 現在の暫定最大値を真の最大値として返す
def saidai(num_list):
    nmax = num_list[0]
    for i in range(1, len(num_list)):
        if num_list[i] > nmax:
            nmax = num_list[i]
    return nmax

lst = [6,4,2,3,8,5,0]
print(lst, "の最大値は", saidai(lst))
nil = []
print(nil, "の最大値は", saidai(nil))
[6, 4, 2, 3, 8, 5, 0] の最大値は 8
PythonError: Traceback (most recent call last):
  File "/lib/python312.zip/_pyodide/_base.py", line 596, in eval_code_async
    await CodeRunner(
  File "/lib/python312.zip/_pyodide/_base.py", line 410, in run_async
    coroutine = eval(self.code, globals, locals)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<exec>", line 11, in <module>
  File "<exec>", line 2, in saidai
IndexError: list index out of range

上記のプログラムは引数のリストの要素数が少なくとも1以上でなければならない。

最大値を求めたいのであれば、前提としてリストには2つ以上の値が格納されているはずなので、上記の制約条件には実質的な問題はない。

ただし、空のリストが与えられた時も関数が正しく動作することを望むのであれば、リストの要素が空のときは、最大値がないことをNoneで示すという方法も考えられる。

  • リストが空の場合
    • Noneを返し関数を終了する
  • リストの最初の要素を暫定最大値と置く
  • 2番目以降のすべてのリストの要素に対して以下の処理を繰り返す
    • 注目しているリストの要素が暫定最大値よりも大きいならば
      • その要素を暫定最大値とする
  • 現在の暫定最大値を真の最大値として返す
def saidai(num_list):
    if num_list == []:
        return None
    nmax = num_list[0]
    for i in range(1, len(num_list)):
        if num_list[i] > nmax:
            nmax = num_list[i]
    return nmax

lst = [6,4,2,3,8,5,0]
print(lst, "の最大値は", saidai(lst))
nil = []
print(nil, "の最大値は", saidai(nil))
[6, 4, 2, 3, 8, 5, 0] の最大値は 8
[] の最大値は None

この関数は、整数のリストを対象に考えてきたが、実際には、引数は整数のリストに限定されない。

リスト中の要素が、相互に大小の比較が可能な要素であれば、実数値でも、文字や文字列でも構わない。

整数と実数は比較可能なので混在していて構わない。

数値と文字列は比較できないので混在させてはならない。

Pythonにはsaidai()と同じような機能を持つmax()という組み込み関数があり、アルゴリズムやプログラミングの学習目的でなければ、max()を利用するとよい。

print(max(['x', 'a', 'b', 'c', 'x', 'd']))
print(max( [7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]))
print(max(7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1))      # 引数はリストでなくてもよい

Pythonらしいプログラム。共通テストの受験者は見る必要はない。

def saidai(num_list):
    if num_list == []:
        return None
    nmax = num_list[0]
    for v in num_list:
        if v > nmax:
            nmax = v
    return nmax

print(saidai([6,4,2,3,8,5,0]))

ちなみに、関数名や変数名を日本語で書くと以下の様に書けて実際に動く。少しわかりやすいような気もする…

日本語の入力をするためには、プログラムの入力中に日本語入力切り替えの手間が必要で思考の邪魔になる可能性があることや、特に、変数名以外の部分に日本語が入力されてしまうとエラーになるので、現状では日本語の関数名や変数名を使用することはお勧めしない。

def 最大値(数値リスト):
    if 数値リスト == []:
        return None
    暫定最大値 = 数値リスト[0]
    for 要素 in 数値リスト:
        if 要素 > 暫定最大値:
            暫定最大値 = 要素
    return 暫定最大値              # 最後まで処理を行うと、暫定最大値が真の最大値となる

print(最大値([6,4,2,3,8,5,0]))
8

最大値の位置を返す関数

引数を持たない関数

返り値のない関数


数学関数

Pythonには、数学的な計算を効率的に行うためのmathモジュールが用意されています。以下に、主要な数学関数とその使用例を示します。

dir(math)

算術関数機能備考
sqrt(x)xの平方根を返します。
fabs(x)xの絶対値を返します。
exp(x)ネイピア数(e)のx乗を返します。
log(x)xの自然対数を返します。
log(x, base)baseを底とするxの対数を返します。
sin(x)ラジアンで指定した角度xのサイン値を返します。
cos(x)ラジアンで指定した角度xのコサイン値を返します。
tan(x)ラジアンで指定した角度xのタンジェント値を返します。

import math

x = 2
result = math.sqrt(x)
print(x, "の平方根は", result)  # 出力: 4.0

y = -5.5
result = math.fabs(y)
print(y, "の絶対値は", result)  # 出力: 5.5

result = math.exp(x)
print("eの", x, "乗は", result)  # 出力: 7.38905609893065

x = 10
result = math.log(x)
print(x, "の自然対数は", result)  # 出力: 2.302585092994046
base = 2
result_with_base = math.log(x, base)
print(base, "を底とする", x, "の対数は", result_with_base)  # 出力: 3.321928094887362

x = math.pi / 2  # 90度をラジアンに変換
sin_result = math.sin(x)
cos_result = math.cos(x)
tan_result = math.tan(x)

print(x, "のサイン値は", sin_result)  # 出力: 1.0
print(x, "のコサイン値は", cos_result)  # 出力: 6.123233995736766e-17 (ほぼ0)
print(x, "のタンジェント値は", tan_result)  # 出力: 1.633123935319537e+16 (非常に大きな値)
2 の平方根は 1.4142135623730951
-5.5 の絶対値は 5.5
eの 2 乗は 7.38905609893065
10 の自然対数は 2.302585092994046
2 を底とする 10 の対数は 3.3219280948873626
1.5707963267948966 のサイン値は 1.0
1.5707963267948966 のコサイン値は 6.123233995736766e-17
1.5707963267948966 のタンジェント値は 1.633123935319537e+16

math ライブラリ

e, pi


反復処理と再帰処理

ある処理を繰り返し行うためには、これまでに行ってきたように

整数値nの階乗n!を求めるプログラムを作成したい。

(階乗は math.factorial(n)で求めることができる。。。けどね)

n! = n×(n−1)×(n−2)×…×1

例えば、n を 7 とすると n! = 7*6*5*4*3*2(*1), すなわち n! = 5040 となる。

このように、nの階乗は n と n から 1 を引いた値が1になるまでの数値を繰り返し乗算することによって得られる。


def factorial(n):
    result = n
    while n > 1:
        n = n - 1
        result = result * n
    return result

num = 7  # 例えば100にすると?
print(num, "の階乗は", factorial(num))
7 の階乗は 5040
def factorial(n):
    result = 1                # 乗算に対する単位元を設定する
    while n > 1:
        result = result * n
        n = n - 1
    return result

num = 7  # 例えば100にすると?
print(num, "の階乗は", factorial(num))

for文での作成

def factorial(n):
    result = 1
    for i in range(1, n + 1):
        result = result * i
    return result

num = 7  # 例えば100にすると?
print(num, "の階乗は", factorial(num))
def factorial(n):
    result = n
    for i in range(1, n):
        result = result * i
    return result

num = 7  # 例えば100にすると?
print(num, "の階乗は", factorial(num))
def factorial(n):
    result = n
    for i in range(n - 1, 0, -1):
        result = result * i
    return result

num = 7  # 例えば100にすると?
print(num, "の階乗は", factorial(num))

再帰処理

ある処理を行うために、その関数自身を利用する。

必ず停止条件(これ以上再帰を行わない条件)を明示し、それが成立したら再帰を終了すること。

def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n - 1)

num = 7  # 例えば100にすると?
print(num, "の階乗は", factorial(num))
7 の階乗は 5040