Python 入門

Python 関数呼び出し

関数呼び出しはプログラミングにおける核心的な概念であり、定義済みのコードブロックを実行し、プログラムの至る所でそれらを再利用することを可能にします。

本章では、関数呼び出しの内部メカニズム、実引数(Arguments)を渡す方法、そしてそれらの実引数が関数内部の操作とどのように相互作用するかについて深く掘り下げます。

1. 関数呼び出しの理解

関数呼び出しは、特定の関数を実行するようプログラムに命じる「式(Expression)」です。関数を呼び出すとき、プログラムは実行フローをその関数のコードブロックへとジャンプさせ、内部のコードを実行した後、通常は呼び出し元に戻って後続の処理を継続します。

関数呼び出しの基本構文は以下の通りです:

function_name(argument1, argument2, ...)
  • function_name: 実行したい関数の名前です。
  • (): 引数が必要かどうかにかかわらず、関数を呼び出す際は常に丸括弧が必要です。
  • argument1, argument2, ...: 入力として関数に渡す値(実引数)です。実引数は任意であり、引数を必要としない関数も存在します。

1.1 引数を持つ関数の呼び出し

名前を引数として受け取り、挨拶を表示する greet という関数を例に見てみましょう。

def greet(name):
  """引数として渡された人に挨拶をする関数。"""
  print(f"こんにちは、{name}さん!")

# 実引数 "Alice" を使用して関数を呼び出す
greet("Alice")  # 出力: こんにちは、Aliceさん!

# 実引数 "Bob" を使用して関数を呼び出す
greet("Bob")    # 出力: こんにちは、Bobさん!

この例では、greet("Alice") が関数呼び出しにあたります。文字列 "Alice"greet 関数に渡される「実引数」です。

1.2 引数を持たない関数の呼び出し

実行時に実引数を一切必要としない関数もあります。

def say_hello():
  """シンプルな挨拶を表示する関数。"""
  print("こんにちは!")

# 関数を呼び出す
say_hello()  # 出力: こんにちは!

say_hello が引数を受け取らない場合でも、呼び出し時には必ず丸括弧 () を記述しなければなりません。

2. 関数への実引数の渡し方

実引数(Arguments)は、関数呼び出し時に提供する具体的な値です。関数はこれらの値を使用して内部の処理を行います。Python では、関数に実引数を渡す方法がいくつかあります。

2.1 位置引数 (Positional Arguments)

位置引数は、その位置や順序に従って厳密に関数へ渡されます。関数は左から右への順序で、定義されたパラメータに値を割り当てるため、渡す順序が非常に重要になります。

def describe_person(name, age, city):
  """姓名、年齢、都市に基づいて人物を説明する関数。"""
  print(f"姓名: {name}, 年齢: {age}, 都市: {city}")

# 位置引数を使用して関数を呼び出す
describe_person("Charlie", 30, "London")  # 出力: 姓名: Charlie, 年齢: 30, 都市: London

この例では、呼び出し時の位置によって "Charlie"name に、30age に、"London"city に割り当てられています。

       重要なヒント: 位置引数を誤った順序で提供すると、予期しない結果になったりエラーが発生したりする可能性があります。

2.2 キーワード引数 (Keyword Arguments)

キーワード引数は、「パラメータ名=値」という形式で明示的に指定して渡す方法です。これにより、パラメータ名さえ正しければ、任意の順序で実引数を渡すことができます。

def describe_person(name, age, city):
  """姓名、年齢、都市に基づいて人物を説明する関数。"""
  print(f"姓名: {name}, 年齢: {age}, 都市: {city}")

# キーワード引数を使用して関数を呼び出す
describe_person(age=30, name="Charlie", city="London")  # 出力: 姓名: Charlie, 年齢: 30, 都市: London

キーワード引数を使うことで、どの値がどのパラメータに対応するかが明確になり、コードの可読性が向上するだけでなく、順序間違いによるバグのリスクを大幅に軽減できます。

2.3 デフォルト引数

関数のパラメータにデフォルト値を指定しておくことができます。呼び出し時にその引数が提供されなかった場合、プログラムは自動的に設定されたデフォルト値を使用します。

def greet(name="Guest"):
  """引数として渡された人に挨拶をする。
  名前が提供されない場合、デフォルトで "Guest" と挨拶する。"""
  print(f"こんにちは、{name}さん!")

# 実引数なしで関数を呼び出す
greet()  # 出力: こんにちは、Guestさん!

# 実引数を1つ渡して関数を呼び出す
greet("David")  # 出力: こんにちは、Davidさん!

この例では、name パラメータのデフォルト値は "Guest" です。引数なしで呼び出すとデフォルト値が、値を渡すとその値が使用されます。

2.4 位置引数とキーワード引数の併用

1つの関数呼び出し内で位置引数とキーワード引数を混ぜて使うことができますが、位置引数は常にキーワード引数よりも前に配置しなければなりません。

def describe_person(name, age, city="Unknown"):
  """姓名、年齢、都市に基づいて人物を説明する。
  都市 (city) にはデフォルト値 "Unknown" が設定されている。"""
  print(f"姓名: {name}, 年齢: {age}, 都市: {city}")

# 位置引数とキーワード引数を併用して呼び出す
describe_person("Eve", 25, city="Paris")  # 出力: 姓名: Eve, 年齢: 25, 都市: Paris
describe_person("Eve", 25) # 出力: 姓名: Eve, 年齢: 25, 都市: Unknown

       重要なヒント: 一度キーワード引数を使用し始めると、それ以降の引数はすべてキーワード引数である必要があります。例えば describe_person(name="Eve", 25, "Paris") と記述すると SyntaxError(構文エラー)が発生します。

3. 引数の受け渡しと可変性 (Mutability)

Python が関数引数をどのように処理するかを理解することは、特にリスト(Lists)や辞書(Dictionaries)のような「ミュータブル(可変)」なデータ型を扱う際に非常に重要です。

Python は「オブジェクト参照渡し (pass by object reference)」と呼ばれるメカニズムを採用しています。これは、実引数を渡す際、オブジェクトのコピーではなく、メモリ上のオブジェクトへの参照(メモリアドレス)を渡していることを意味します。

3.1 イミュータブルなデータ型

整数(Integers)、浮動小数点数(Floats)、文字列(Strings)、タプル(Tuples)などのイミュータブルなデータ型の場合、関数内部での再代入による変更は、関数外部の元のオブジェクトには影響しません。イミュータブルなオブジェクトは作成後に変更できないため、変更を試みると Python はメモリ上に新しいオブジェクトを生成します。

def modify_number(x):
  """入力された数値を変更しようとする関数。"""
  x = x + 1
  print(f"関数内部: x = {x}")

number = 10
modify_number(number)  # 出力: 関数内部: x = 11
print(f"関数外部: number = {number}")  # 出力: 関数外部: number = 10

この例では、関数内で x の値を増やしても、元の number 変数には影響しません。x = x + 1 の時点で、関数内部では新しい整数オブジェクトが作成されているからです。

3.2 ミュータブルなデータ型 (Mutable Data Types)

リスト(Lists)や辞書(Dicts)などのミュータブルなデータ型の場合、関数内部で行った変更(要素の追加や削除など)は、関数外部の元のオブジェクトに直接影響を与えます。これは、参照が指しているメモリ上の同一オブジェクトを操作しているためです。

def modify_list(my_list):
  """入力されたリストを変更する関数。"""
  my_list.append(4)
  print(f"関数内部: my_list = {my_list}")

my_list = [1, 2, 3]
modify_list(my_list)  # 出力: 関数内部: my_list = [1, 2, 3, 4]
print(f"関数外部: my_list = {my_list}")  # 出力: 関数外部: my_list = [1, 2, 3, 4]

modify_list 関数がリストに 4 を追加すると、元の my_list も変更されます。append メソッドはリストオブジェクトを「インプレース(破壊的)」に修正するためです。

3.3 予期せぬ変更を避ける方法

関数によって元のリストや辞書が書き換えられるのを防ぎたい場合は、関数内部(または引数として渡す際)でオブジェクトのコピー (Copy) を作成します。.copy() メソッドや、list() / dict() コンストラクタを使用するのが一般的です。

def modify_list_safely(my_list):
  """入力リストのコピーを安全に修正する関数。"""
  new_list = my_list.copy()  # リストのコピーを作成
  new_list.append(4)
  print(f"関数内部: new_list = {new_list}")

my_list = [1, 2, 3]
modify_list_safely(my_list)  # 出力: 関数内部: new_list = [1, 2, 3, 4]
print(f"関数外部: my_list = {my_list}")  # 出力: 関数外部: my_list = [1, 2, 3]

この例では my_list.copy() によって新しいリストオブジェクトを作成し、それを new_list に割り当てて操作しているため、元の my_list は保護されます。