Lesson 8
関数と再帰
1. 機能
数学では、数nの階乗は
# 3を計算してください! res = 1 for i in range(1, 4): res *= i print(res) # 5を計算してください! res = 1 for i in range(1, 6): res *= i print(res)
しかし、最初のコードで間違いを犯すと、この誤ったコードが階乗計算をコピーしたすべての場所に表示されます。さらに、コードはそれより長くなります。プログラミング言語で同じロジックを書き直すのを避けるために、関数があります。
関数は、プログラムの残りの部分から分離され、呼び出されたときにのみ実行されるコードセクションです。既に関数sqrt()
、 len()
、 print()
満たしています。彼らはすべて共通のものを持っています:彼らはパラメータ(ゼロ、1つ、またはいくつか)を取ることができ、値を返すことができます(返されないかもしれませんが)。たとえば、関数sqrt()
は1つのパラメータを受け入れ、値(与えられた数の平方根sqrt()
を返します。 print()
関数はさまざまな引数をとり、何も返しません。
今度はfactorial()
という関数を書く方法を説明したいと思いますfactorial()
は、その数値の階乗を数値で返します。
def factorial(n): res = 1 for i in range(1, n + 1): res *= i return res print(factorial(3)) print(factorial(5))
我々はいくつかの説明をしたい。まず、関数コードはプログラムの先頭に置かれますfactorial()
正確にはfactorial()
関数factorial()
を使用する場所の前)。この例の最初の行のdef factorial(n):
は、関数の説明です。 因数という語は識別子(私たちの関数の名前)です。識別子の直後に、私たちの関数が受け取る(括弧内の)パラメータのリストがあります。このリストは、カンマで区切られたパラメータの識別子で構成されています。我々の場合、リストは1つのパラメータn
構成されます。行の最後にコロンを入れます。
その後、関数本体に行きます。 Pythonでは、本文はインデントされなければなりません(Tabまたは4つのスペースでいつものように)。この関数は、n!それを変数res
格納します。関数の最後の行はreturn res
であり、関数を終了し、変数res
値を返します。
return
文は、関数の任意の場所に置くことができます。その実行は関数を終了し、関数が呼び出された場所に指定された値を返します。関数が値を返さない場合、return文は実際には何らかの値を返すことはありません(まだ使用できますが)。関数によっては値を返す必要がなく、return文を省略することができます。
別の例を挙げたいと思います。ここでは、2つの数値を受け取り、それらの最大値を返す関数max()
があります(実際には、この関数はすでにPython構文の一部になっています)。
def max(a, b): if a > b: return a else: return b print(max(3, 5)) print(max(5, 3)) print(max(int(input()), int(input())))
これで、3つの数値をとり最大値を返す関数max3()
を書くことができます。
def max(a, b): if a > b: return a else: return b def max3(a, b, c): return max(max(a, b), c) print(max3(3, 5, 4))
Pythonの組み込み関数max()
は、さまざまな数の引数を受け入れ、それらの最大値を返すことができます。このような関数の記述方法の例を次に示します。
def max(*a): res = a[0] for val in a[1:]: if val > res: res = val return res print(max(3, 5, 4))
a
という単一のタプルにパラメータを集めます。
2. ローカル変数とグローバル変数
関数の中で、その外側のどこかで宣言された変数を使うことができます:
def f(): print(a) a = 1 f()
ここで変数a
は1に設定され、関数f()
は、関数f
を宣言したときにこの変数が初期化されていないにもかかわらず、この値を出力します。その理由は、関数f()
(最後の文字列)を呼び出すときに、変数a
すでに値を持っているからです。そのため、関数f()
はそれを表示できます。
そのような変数(関数の外側で宣言されていますが、関数内で使用可能)はグローバルと呼ばれます。
しかし、関数の内部でいくつかの変数を初期化すると、この変数をその変数の外側で使用することはできなくなります。例えば:
def f(): a = 1 f() print(a)
NameError: name 'a' is not defined
。関数内で宣言されたそのような変数はローカルと呼ばれます。関数を終了すると、それらは利用できなくなります。
ここで本当に魅力的なのは、関数内の大域変数の値を変更するとどうなりますか?
def f(): a = 1 print(a) a = 0 f() print(a)
このプログラムは数字1と0を出力します。変数a
値が関数内で変更されたにもかかわらず、関数の外では同じままです!これは、関数の不注意な変更からグローバル変数を保護するために行われます。したがって、ある変数が関数内で変更された場合、その変数はローカル変数になり、その変更によって同じ名前のグローバル変数は変更されません。
もっと正式には、Pythonインタプリタは、この関数のコード内に変数の値を変更する少なくとも1つの命令がある場合、関数のローカル変数を考慮します。その変数は、初期化の前に使用することもできません。変数の値を変更する命令-演算子=
、 +=
、およびループなどの変数の使用for
パラメーター。しかし、change-variableステートメントが決して実行されなくても、インタープリタはそれをチェックすることができず、変数はまだローカルです。例:
def f(): print(a) if False: a = 0 a = 1 f()
UnboundLocalError: local variable 'a' referenced before assignment
。つまり、関数f()
では、変数a
を変更するコマンドが含まれているため、識別子a
はローカル変数になります。変更命令は決して実行されないが、インタプリタはそれをチェックしない。したがって、変数a
を出力しようとすると、初期化されていないローカル変数にアピールすることになります。
関数で変数を変更できるようにするには、 global
変数keywordを使用して関数内でこの変数を宣言する必要があります。
def f(): global a a = 1 print(a) a = 0 f() print(a)
この例では、変数a
がグローバルとして宣言され、関数内で変数a
を変更するとグローバルに変更されるため、1 1の出力が出力されます。
しかし、関数内の大域変数の値を変更しない方が良いです。関数がいくつかの変数を変更しなければならない場合は、この値を返し 、関数を呼び出すときに変数を明示的にこの値に代入することを選択します。これらのルールに従えば、関数のロジックはコードロジックとは独立して動作するため、このような関数をあるプログラムから別のプログラムに簡単にコピーでき、時間を節約できます。
たとえば、変数fに保存したい与えられた数値の階乗をプログラムが計算すると仮定します。あなたがそれをしてはならない方法は次のとおりです:
def factorial(n): global f res = 1 for i in range(2, n + 1): res *= i f = res n = int(input()) factorial(n) print(f) # 変数fで他のものをやっている
これは、別の時間を使用するのが難しいため、 悪いコードの例です。明日、 "階乗"関数を使用する別のプログラムが必要な場合、ここからこの関数をコピーして新しいプログラムに貼り付けることはできません。そのプログラムに変数f
が含まれていないことを確認する必要があります。
次のようにこの例を書き換えるほうがはるかに良いです:
# プログラムからプログラムにコピーできるコードチャンクの開始 def factorial(n): res = 1 for i in range(2, n + 1): res *= i return res # コードの終わり n = int(input()) f = factorial(n) print(f) # 変数fで他のものをやっている
関数は複数の値を返すことができると言うと便利です。 2つ以上の値のリストを返す例を次に示します。
return [a, b]
そのようなリストの関数を呼び出して、複数の代入でそれを使用することができます:
n, m = f(a, b)
3. 再帰
上記のように、関数は別の関数を呼び出すことができます。しかし、関数自体も呼び出すことができます!それを説明するために、階乗計算関数の例を考えてみましょう。 0!= 1,1、= 1であることはよく知られている。 nの値を計算する方法!大きなnのために? (n-1)!の値を計算できれば、n!= n・(n-1)!となるので、簡単にn!を計算できます。しかし(n-1)を計算する方法!? (n-2)!、(n-1)!=(n-1)・(n-2)!を計算したなら!計算方法(n-2)!? If ...最終的には、0になります。これは1に等しくなります。したがって、階乗を計算するには、階乗の値を小さい整数に使用できます。この計算はPythonを使って行うことができます:
def factorial(n): if n == 0: return 1 else: return n * factorial(n - 1) print(factorial(5))
関数自体が呼び出される状況を再帰と呼び、このような関数を再帰と呼びます。
再帰関数はプログラミングの強力なメカニズムです。残念ながら、それらは常に効果的ではなく、しばしば間違いにつながります。最も一般的なエラーは、関数呼び出しの連鎖が決して終わらない無限の再帰です (実際には、あなたのコンピュータの空きメモリが足りなくなると終了します)。無限再帰の例:
def f(): return f()無限再帰を引き起こす最も一般的な2つの理由:
- 停止条件が正しくありません。たとえば、階乗計算プログラムで
if n == 0
チェックを忘れると、factorial(0)
はfactorial(-1)
、factorial(-2)
などと呼ばれます。 - 不正なパラメータを持つ再帰呼び出し。関数たとえば、
factorial(n)
関数呼び出しfactorial(n)
、我々はまた、呼の無限鎖を取得します。
したがって、再帰関数をコーディングするときは、最初に再帰関数が終了する理由を考えるために、停止条件に確実に到達する必要があります。