【Python】内部関数(関数内関数)

2023年1月12日

Pythonの内部関数について解説します。

内部関数(関数の中にある関数)

コード例

# 九九の表を表示('-'で表いっぱいまで挟む)

print('九九の表を表示します。')

while True:
    upper = int(input('何の段まで(1から9) : '))
    if 1 <= upper <= 9:
        break

def times_tables(n: int) -> None:
    """九九の表を表示"""
    def put_bar(n: int) -> None:
        """n個の'-'を連続表示して改行"""
        print('-' * n)

    if   1 <= n <= 3: w = 2
    else            : w = 3

    put_bar(n * w)
    for i in range(1, n + 1):
        for j in range(1, n + 1):
            print(f'{i * j:{w}d}', end='')
        print()
    put_bar(n * w)

times_tables(upper)

実行結果(1)

九九の表を表示します。
何の段まで(1から9) : 3
------
 1 2 3
 2 4 6
 3 6 9
------

実行結果(2)

九九の表を表示します。
何の段まで(1から9) : 9
---------------------------
  1  2  3  4  5  6  7  8  9
  2  4  6  8 10 12 14 16 18
  3  6  9 12 15 18 21 24 27
  4  8 12 16 20 24 28 32 36
  5 10 15 20 25 30 35 40 45
  6 12 18 24 30 36 42 48 54
  7 14 21 28 35 42 49 56 63
  8 16 24 32 40 48 56 64 72
  9 18 27 36 45 54 63 72 81
---------------------------

関数times_tablesの中に関数put_barが入っています。

このように入れ子になっている関数を内部関数といいます。

内部関数は外側の関数よりもインデントを1段階深くする必要があります。

関数times_tablesの仮引数nには実引数upperが代入されるのに対し、

関数put_barの仮引数nには実引数n * wが代入されています。

このことから関数times_tablesと関数put_barの仮引数nは全く別のものだとわかります。

実際に3と入力すると前者が3、後者が3×2=6というのが結果として出ています。

関数名 仮引数 実引数
times
_tables
n upper
put
_bar
n n * w

なぜこのように区別されるのかを次のコードを通して解説していきます。

内部関数の変数

コード例

# スコープによる変数の変化

n = 1

def outer():
    def inner():
        n = 3
        print(f'inner() n = {n}')
    n = 2
    inner()
    print(f'outer() n = {n}')

print(f'global  n = {n}')
outer()
print(f'global  n = {n}')

実行結果

global  n = 1
inner() n = 3
outer() n = 2
global  n = 1

このコードでは変数nの値を表示させています。

n = 1と代入した後に変数nの値を表示させているので1なのはわかるでしょう。

次に関数outer内でn = 2に更新してさらに関数inner内でn = 3に更新して表示しているので3になります。

関数outerに戻ってnの値を表示させると2になり、最後に表示しているnの値は最初と同じ1です。

同じ変数名nなのに値がそれぞれ異なるのは同じ名前のものが3つあるからです。

名前空間とスコープ

変数や関数などとそれを参照する名前の対応付けを名前空間といいます。

スコープは名前がそのまま通用する範囲をいいます。

Pythonのプログラムは変数などの名前を見つけた時にそれがどこにあるかを名前空間から探します。

その探す順番は見つけた範囲(スコープ)からまず探します。

実行結果2行目のnの値は関数inner内から探して、その中でn = 3と更新してあるので3と表示されます。

同様に3行目のnの値は関数outer内から探してn = 2と更新してあるので2と表示されます。

見つけた範囲になかった場合はそれよりも1段階広い範囲から探します。(狭い範囲に探索範囲を移すことは通常ではありません)

もし関数inner内からn = 3を削除するとその範囲から見つからないので関数outer内から探してn = 2を見つけます。

さらに関数inner内からn = 3、関数outer内からn = 2を削除するとinner内やouter内から見つからないので3つとも一番最初のn = 1となります。

これらのことは仮引数にも適応されるので同じ名前にしても名前空間とスコープによりちゃんと区別して動いてくれます。

Python

Posted by ほりえりお