見出し画像

Python チュートリアル 第4版 4.7.1 引数のデフォルト値 の補足

Python チュートリアル 第4版 4.7.1 引数のデフォルト値 を読んで、補足したくなった事をつらつらと書きます。

まず、Python チュートリアル 第4版 4.7.1 引数のデフォルト値 の最初のサンプルプログラムですが、仮引数にデフォルト値を設定する例としては分かりやすいでしょう。

ただ、この ask_ok関数には、これまでに説明がされてない新しい項目を使っている所が三箇所あります。このサンプルプログラムの動作を理解するには、この三箇所を理解する必要があります。

このサンプルプログラムの ask_ok関数のメッセージを日本語にして、print組み込み関数で呼び出しているのが下記のプログラムです。このプログラムを使って、この三箇所に付いて補足します。

def ask_ok(prompt, retries=4, reminder='もう一度やり直してください'):
    while True:
        ok = input(prompt)  # ☆1
        if ok in ('y', 'ye', 'yes'):  # ☆2
            return True
        if ok in ('n', 'no', 'nop', 'nope'):  # ☆2
            return False
        retries = retries - 1
        if retries < 0: 
            raise ValueError('無効なユーザー応答')  # ☆3
        print(reminder)

print(ask_ok('本当にやめたいですか?'))


ok = input(prompt) # ☆1 の箇所ですが、簡単に説明すると、input組み込み関数は引数(があればそれを)を出力して、次に入力から1行を読み込み、文字列に変換して(末尾の改行を除いて)返します。今は、キーボードから入力された一行を返す関数だと理解していればよいでしょう。

if ok in ('y', 'ye', 'yes'): # ☆2 の箇所ですが、簡単に説明すると、この場合の in 演算は、タプル('y', 'ye', 'yes') 項目のどれかと ok の文字列が一致すれば True、そうでなければ False を返します。
if ok in ('n', 'no', 'nop', 'nope'): # ☆2 の箇所は、タプルの項目が違うだけなので説明を省きます。

蛇足ですが、if ok in 'yes': とした場合は、 'e', 'es' 's' でも True を返してしまうので注意が必要です。in および not in 演算は、一般に単純な包含判定に使われますが、(str, bytes, bytearray のような) 特殊なシーケンスでは部分シーケンス判定にも使われるためです。文字列は特殊なシーケンスにあたる為、部分シーケンス判定が行われます。

in に付いて詳しく知りたい時は⬇️を御覧ください。


raise ValueError('無効なユーザー応答') # ☆3 の箇所ですが、raise文は、例外(エラー)を発生させます。(例外を受け止める処理が無ければ)プログラムは例外により終了します。

今は raise ValueError('無効なユーザー応答') を実行すると ValueError が発生し、'無効なユーザー応答' を表示してプログラムが止まるくらいの理解で大丈夫です。

例外については、Python チュートリアル 第4版 8章 で解説がされています。本書を読み進めて 8章に目を通した時に、正しい raise文の正しい実行内容が分かります。


次に二番目のサンプルプログラムに付いて補足します。まず二番目のサンプルプログラムを示します。

i = 5

def f(arg=i):
    print(arg)

i = 6
f()


Python チュートリアル 第4版 4.7.1 本文の説明で書かれているスコープは、プログラムテキスト上の範囲を指すものです。Python チュートリアル 第4版 9章(9.2) で解説がされています。本書を読み進めて9章に目を通した時に、正しいスコープの意味が分かるので、今はその程度の理解で大丈夫です。

このサンプルプログラムでは、f関数が定義されているプログラムテキストの位置より前(上)で定義や代入されている i の最新の値が仮引数 arg のデフォルト値になります。このデフォルト値は関数が定義される時に一度だけ評価されて決まります。関数を呼び出す度にデフォルト値が評価されて決まるわけではないので注意が必要です。『仮引数のデフォルト値は関数定義の時に決まり、関数を呼び出す度に、そのデフォルト値が使われる。』と覚えておきましょう。

ちなみに、サンプルプログラムを次の様に変更すると、仮引数 i のデフォルト値は 6 になります。

i = 5

i = 6

def f(arg=i):  # f関数が定義される時点での i の最新値は 6 なので、arg仮引数のデフォルト値は 6 になる。
    print(arg)

f()


蛇足ですが、サンプルプログラムを次の様に変更すると、NameError: name 'f' is not defined になります。

i = 5

i = 6

f()  # この f関数呼び指しの時点のスコープで、f関数本体の定義がされてないのでエラーになる。

def f(arg=i):
    print(arg)


次に三番目のサンプルプログラムを示します。同じ説明の繰り返しになるのでサンプルプログラムに注釈を追加しました。注釈の説明だけで動作を理解出来ると思います。

def f(a, L=[]):  # 関数が定義される時に、一度だけ空のリストが L仮引数のデフォルト値に設定される。

    L.append(a)  # 実引数のリストが省略されて呼び出された場合、関数が定義された時に設定された L仮引数のデフォルトのリストに項目が追加されていく。 

    return L     # リスト L を返す。

print(f(1))
print(f(2))
print(f(3))


ちなみに、Python チュートリアル 第4版 4.7.1 本文の説明で書かれている、このサンプルプログラムの出力結果は間違っています。正確にはこうなります。

出力結果

[1]
[1, 2]
[1, 2, 3]

このサンプルプログラムのように、仮引数のデフォルト値にリスト等の可変オブジェクトを使う場合は、関数を呼び出す度にデフォルト値の内容が変化する可能性があるので気をつけてください。

次に最後のサンプルプログラムと、その出力結果を示します。このサンプルプログラムは、関数の定義時ではなく、関数呼び出す度に、空のリストを仮引数のデフォルト値として設定する例です。関数呼び出し時に初期化した可変オブジェクトを、仮引数のデフォルト値として使いたい場合は、このような工夫が必要になります。なお、次に示すサンプルプログラムには、print組み込み関数で f関数を呼び出す部分を追加しています。サンプルプログラムに追加している注釈と合わせて出力結果を見ればサンプルプログラムの動作が理解出来ると思います。

def f(a, L=None):  # 実引数のリストが省略されて呼び出された場合 L に None が代入される。
    if L is None:  # L が None なら・・・
        L = []         # 空の新しいリストを L に代入。
    L.append(a)    # リスト L に、項目 a を追加。 
    return L       # リスト L を返す。

print(ret := f(1))    # 代入式 を使っているで Python 3.8 以降で実行してください。 
print(f(2, ret))
print(f(3, ret))

print(ret := f(100))  # 代入式 を使っているで Python 3.8 以降で実行してください。
print(f(200, ret))
print(f(300, ret))
print(f(400, ret))


出力結果

[1]
[1, 2]
[1, 2, 3]
[100]
[100, 200]
[100, 200, 300]
[100, 200, 300, 400]


ここで、最後のサンプルプログラムの f関数を呼び出す部分を移植した、三番目のサンプルプログラムと、その出力結果を示します。最後のサンプルプログラムの出力結果と見比べて動作の違いを理解してください。

def f(a, L=[]):  # 関数が定義される時に、一度だけ空のリストが L仮引数のデフォルト値に設定される。

    L.append(a)  # 実引数のリストが省略されて呼び出された場合、関数が定義された時に設定された L仮引数のデフォルトのリストに項目が追加されていく。

    return L     # リスト L を返す。

print(ret := f(1))    # 代入式 を使っているで Python 3.8 以降で実行してください。 
print(f(2, ret))
print(f(3, ret))

print(ret := f(100))  # 代入式 を使っているで Python 3.8 以降で実行してください。
print(f(200, ret))
print(f(300, ret))
print(f(400, ret))


出力結果

[1]
[1, 2]
[1, 2, 3]
[1, 2, 3, 100]
[1, 2, 3, 100, 200]
[1, 2, 3, 100, 200, 300]
[1, 2, 3, 100, 200, 300, 400]


まとめとして、Python チュートリアル 第4版 4.7.1 引数のデフォルト値 の要は、『仮引数のデフォルト値は関数定義の時に決まり、関数を呼び出す度に、そのデフォルト値が使われる。』だと思います。引数のデフォルト値がらみのバグが出た時に、これを理解していないと、いつまでもバグが取れないのでしっかりと理解しておきましょう。



//オマケ//

"""
__defaults__ は、デフォルト値を持つ引数に対するデフォルト値が収められたタプルで、デフォルト値を持つ引数がない場合には None になります。
"""

def f(a, L=[]):

    L.append(a)

    return L

print(f.__defaults__, end='\n\n')  # 引数のデフォルト値表示

print(ret := f(1))

print(f.__defaults__, end='\n\n')  # 引数のデフォルト値表示

print(f(2, ret))

print(f.__defaults__)  # 引数のデフォルト値表示


出力結果

([],)

[1]
([1],)

[1, 2]
([1, 2],)






#Pythonチュートリアル第4版
#Pythonチュートリアル
#Python #Python3
#引数のデフォルト値
#デフォルト値
#input関数
#in演算
#raise文
#共通のシーケンス演算リンク
#サンプルプログラム