初めてのPython(15章)

4部は、3部でやったステートメントを組み合わせてひとまとまりの処理をさせる関数について。

関数についての基礎

Pythonではあらゆるものがオブジェクトなので、関数も定義すると関数オブジェクトが作成される。
作成された関数オブジェクトは定義した関数名の変数に代入される(リファレンスが張られる)。
ビルドインオブジェクト同様に、別の名前の変数に関数オブジェクトを代入することも可能。

関数の定義

defステートメントを使って定義する。
defステートメントは複合ステートメントだから、行末にコロン(:)が必要。
引数は関数名に続けて中括弧で括って、コンマ(,)区切りで並べる。
何かしらの復帰値を返したい時は、returnステートメントを使う。

def <func_name>([arg1[, arg2, ...]]):
    <statement>
    
    return <value>

return の場合、値を返すと関数の処理が終わるが、
値は返すが、
『処理は終わらさず一旦停止し、何らかの処理が終わり、関数の処理を再開し、また値を返す』
というようなことをしたい場合は、yieldステートメントを使う。

関数の呼び出し

関数名に続けて中括弧を並べて呼び出す。
引数を渡す場合には中括弧の中に渡す順番で記述する。

<func_name>([arg1[, arg2, ...]])

復帰値がある関数の呼び出しでは、復帰値を変数に代入する。

retval = <func_name>([arg1[, arg2, ...]])

関数呼び出しは関数定義より前には行えない(関数オブジェクトが作成されていないから)。
関数さえ定義されていれば呼び出せるので、条件に合わせて同じ名前の関数を定義し分けることも出来る。

if <条件1>:
    def test_func():
        <statement1>

else:
    def test_func():
        <statement2>

test_func()

引数

関数に引数を渡すと、代入処理が行われ、呼び出し元と呼び出し先で共有リファレンスが形成される。
そのため、関数内で引数の値を書き換える処理をした場合に、
オブジェクトがmutableかimmutableかで呼び出し元の変数への影響が異なる。
この点がCの参照渡しとは異なる(mutableなオブジェクトの場合は、Cの参照渡しと同じ動作)。

immutableなオブジェクトの場合

immutableなので、変数に対して更新処理をすると新しいオブジェクトが作成され、
関数内の引数が保持しているリファレンス『のみ』新しいオブジェクトに更新される。
そのため、呼び出し元の引数への影響はない。
リファレンスを渡しているが、動きはCの値渡しと同じ。

mutableなオブジェクトの場合

mutableなので、変数に対して更新処理をすると既存オブジェクトに変更が加わる。
既存オブジェクトに変更が加わってしまうため、関数内の引数/呼び出し元の引数ともに影響を受ける。
Cの参照渡しに近い動きをする。

ポルモーフィズム

Pythonには変数に型の定義が無いため、
同じ関数に対して別の型のオブジェクトを渡せば、オブジェクトに応じた処理が可能になる。
オブジェクトによって挙動が変わる、この特性のことを『多態性(ポルモーフィズム)』という。
関数内で使っている演算子に渡したオブジェクトが対応していればオブジェクトに応じた処理が行われ、
対応していなければ例外が発生し、自動的にエラー処理が行われる。

>>> def func_add(x, y):
...     return x + y
... 
>>> func_add(1, 2)
3
>>> func_add("ham", "egg")
hamegg
>>> func_add({"spam":1}, {"bacon":2})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in func_add
TypeError: unsupported operand type(s) for +: 'dict' and 'dict'
>>> 

これはPythonの柔軟性が可能にする動きなので、
特定の型を想定した関数を作ってしまうと柔軟性を失わせてしまう。
『どの型に対応させるか』ではなく、『どのインフェースに対応させるか』を考えて作る