関数の記述_2(Python_16)

この連載では、Pythonについて色々な形で再学習に取り組んでいます。前回の記事はこちらになります。

hirocom777.hatenadiary.org

前回は、関数の記述方法についてご紹介しました。Pythonには便利な組み込み関数がたくさんあります。

今回も関数の記述です。引数の指定方法について、さらに突き詰めていきたいと思います。

可変長キーワード引数

前回、位置引数、キーワード引数、可変長位置引数をご紹介しました。そして、キーワード引数にも可変長引数があります。これを可変長キーワード引数と呼びます。可変長キーワード引数を指定するには、以下のように記述します。

def 関数名(**引数):
    # 処理

引数名は任意で良いのですが、慣例的に「**kwargs」とすることが多いです。以下は可変長キーワード引数の例です。

def print_keyword_args(**kwargs):
    print(args)

print_keyword_args(name = 'Bob',gender = 'mail',age = 25)
# {'name': 'Bob', 'gender': 'mail', 'age': 25}をが返ってくる

この様に、可変長キーワード引数で渡された値は辞書の要素として処理されます。 また、関数呼び出し時に辞書に「**」をつけて引数に指定することで、辞書を直接渡すこともできます。

d = {'name': 'Bob', 'gender': 'mail', 'age': 25}
print_keyword_args(**d)
# {'name': 'Bob', 'gender': 'mail', 'age': 25}が返ってくる

参照の値渡し

プログラミング言語では、関数に引数で値を渡す場合、主に「値渡し」と「参照渡し」があります。ところがPythonでは「参照の値渡し」という手法を取ります。いったいどのように動作するのでしょうか。それぞれの動きと特徴について見ていきましょう。

  • 値渡し
    値渡しは、引数で指定された値をコピーして、関数の中で使用していく方式です。C言語は基本的に値渡しです(ポインターを使って指定する方法もあるが、ここでは省略します)。関数内に値をコピーするためメモリを消費します。渡された値が関数内で変更されても、関数外では影響がありません。

  • 参照渡し
    参照渡しは、値そのものを渡すのではなく、値のキー(メモリーのアドレス、idなど)を渡します。結果、呼び出し側と同じものを操作することになるので、関数内での変更は関数外にも影響があります。逆にこれを使用して、呼び出し側に値を返すようなプログラムも書けます。また実装にもよりますが、値渡しよりもメモリの消費は少ない傾向にあります。VBAには値渡しと参照渡しの両方があります。

  • 参照の値渡し
    参照の値渡しは、値のキーを変数にコピーして渡します。Pythonではid関数で取得できるオブジェクトIDが、キーになります。変数にコピーされたIDが指すオブジェクトを関数内で変更した場合、関数外でも同じオブジェクトIDで指定されるオブジェクトの内容が影響を受けます。これは参照渡しと同じような動きです。

    しかしながら、関数内の変数には別のオブジェクトIDに上書きできます。この場合、関数外には影響を及ぼしません。

参照の値渡しの動き

Pythonでは、この参照の値渡しの動きをきちんと認識していないと、関数のプログラムミスの原因になります。たとえば以下のプログラムの場合を見てみましょう。

def incriment(val):
    val = val + 1
    print(val)

val_1 = 1
incriment(val_1) # 2が返ってくる
print(val_1) # 1が返ってくる(関数内の変更は影響されない)

関数の中で変数の値に1を足していますが、関数の外では値は変わっていません。これは一見値渡しの動作に見えますが、関数内の変数val_1は、値を1足した時点で別のオブジェクトになっているのです。以下のプログラムではどうでしょう。

def append_list(l_1):
    l_1.append(1)
    print(l_1)

list_1 = [0]
append_list(list_1) # [0, 1]が返ってくる
print(list_1) # [0, 1]が返ってくる(関数内の変更が影響する)

関数内での変更が、関数外にも反映されています。appendメソッドでリストに要素を追加した場合、既存のオブジェクトの修正になるので別のオブジェクトにはならないからです。これは一見参照渡しの動作に見えます。

以上の結果をもって「イミュータブルは値渡しで、ミュータブルは参照渡し」と解釈している記述が見受けられますが、違います。

以下のプログラムでは、動作が変わってきます。

def change_list(l_1):
    l_1 = l_1 + [1]
    print(l_1)

list_1 = [0]
change_list(list_1) # [0, 1]が返ってくる
print(list_1) # [0]が返ってくる

関数は同じ値を返してきますが、関数内の変更は関数外に反映されません。リスト同士を結合すると、新しいオブジェクトに書き換わるからです。

イミュータブルなオブジェクトを渡した場合でも、関数内の変更が反映される場合があります。

def change_tuple(t_1):
    t_1[1][0] = 'X'
    print(t_1)

tuple_1 = (['A'],['B'])
change_tuple(tuple_1) # (['A'], ['X'])を返す
print(tuple_1) # (['A'], ['X'])を返す

このプログラムでは、関数内の変更が関数外にも反映されています。タプル(イミュータブルなオブジェクト)内のミュータブルなオブジェクト(この場合はリスト)は変更可能でしたね。この場合はオブジェクトの修正になります。

次回はモジュール

いかがでしょうか。Pythonの関数の性質がわかってきたと思います。次回はモジュールです。プログラムでよく使うコードを部品にできます。お楽しみに!!

Python再学習のまとめはこちら!!