[Python] 引数における値渡しと参照渡し

Pythonでは、関数に引数を渡す際に、値渡しと参照渡しの概念が重要です。

Pythonの引数渡しは、実際には「参照の値渡し」として機能します。

これは、関数に渡されるオブジェクトの参照がコピーされることを意味します。

イミュータブルなオブジェクト(例:intstrtuple)は変更できないため、関数内での変更は元のオブジェクトに影響を与えません。

一方、ミュータブルなオブジェクト(例:listdict)は変更可能であり、関数内での変更は元のオブジェクトに影響を与えます。

この記事でわかること
  • Pythonの引数がオブジェクトの参照を渡す仕組み
  • 値渡しと参照渡しの違いとその影響
  • デフォルト引数や可変長引数の使い方
  • 引数を用いたデータ処理の最適化方法
  • 引数の動作を制御する方法

目次から探す

Pythonの引数の動作

Pythonにおける引数の動作は、他のプログラミング言語と異なる部分があります。

特に、値渡しと参照渡しの概念が混在しているため、理解が必要です。

ここでは、Pythonの引数の動作について詳しく解説します。

値渡しの例

Pythonでは、整数や浮動小数点数などのイミュータブルなオブジェクトは、値渡しのように動作します。

以下の例を見てみましょう。

def modify_number(x):
    # 引数に10を加える
    x += 10
    print(f"関数内のx: {x}")
num = 5
modify_number(num)
print(f"関数外のnum: {num}")
関数内のx: 15
関数外のnum: 5

この例では、関数内でxに10を加えていますが、関数外のnumは変更されていません。

これは、整数がイミュータブルであるため、関数内での変更が関数外に影響を与えないためです。

参照渡しの例

一方、リストや辞書などのミュータブルなオブジェクトは、参照渡しのように動作します。

以下の例を見てみましょう。

def modify_list(lst):
    # リストに新しい要素を追加
    lst.append(4)
    print(f"関数内のlst: {lst}")
my_list = [1, 2, 3]
modify_list(my_list)
print(f"関数外のmy_list: {my_list}")
関数内のlst: [1, 2, 3, 4]
関数外のmy_list: [1, 2, 3, 4]

この例では、関数内でリストに新しい要素を追加していますが、関数外のmy_listも変更されています。

これは、リストがミュータブルであり、関数内での変更が関数外にも影響を与えるためです。

リストや辞書の引数の動作

リストや辞書はミュータブルなオブジェクトであり、関数に渡すと参照渡しのように動作します。

これにより、関数内での変更が関数外にも影響を与えることがあります。

def modify_dict(d):
    # 辞書に新しいキーと値を追加
    d['new_key'] = 'new_value'
    print(f"関数内のd: {d}")
my_dict = {'key1': 'value1'}
modify_dict(my_dict)
print(f"関数外のmy_dict: {my_dict}")
関数内のd: {'key1': 'value1', 'new_key': 'new_value'}
関数外のmy_dict: {'key1': 'value1', 'new_key': 'new_value'}

この例では、辞書に新しいキーと値を追加していますが、関数外のmy_dictも変更されています。

タプルや文字列の引数の動作

タプルや文字列はイミュータブルなオブジェクトであり、関数に渡すと値渡しのように動作します。

これにより、関数内での変更が関数外に影響を与えません。

def modify_string(s):
    # 文字列に新しい文字を追加
    s += " world"
    print(f"関数内のs: {s}")
my_string = "hello"
modify_string(my_string)
print(f"関数外のmy_string: {my_string}")
関数内のs: hello world
関数外のmy_string: hello

この例では、関数内で文字列に新しい文字を追加していますが、関数外のmy_stringは変更されていません。

これは、文字列がイミュータブルであるためです。

値渡しと参照渡しの影響

Pythonにおける値渡しと参照渡しの動作は、プログラムの挙動に大きな影響を与えることがあります。

ここでは、関数内での引数の変更、グローバル変数への影響、そしてメモリ使用量への影響について詳しく解説します。

関数内での引数の変更

関数内で引数を変更する場合、その引数がイミュータブルかミュータブルかによって結果が異なります。

イミュータブルなオブジェクト(例:整数、文字列、タプル)は関数内で変更しても元のオブジェクトには影響を与えません。

一方、ミュータブルなオブジェクト(例:リスト、辞書)は関数内で変更すると元のオブジェクトにも影響を与えます。

def modify_data(data):
    if isinstance(data, list):
        # リストの場合、要素を追加
        data.append('new_item')
    elif isinstance(data, int):
        # 整数の場合、値を変更
        data += 10
    print(f"関数内のdata: {data}")
my_list = [1, 2, 3]
my_int = 5
modify_data(my_list)
print(f"関数外のmy_list: {my_list}")
modify_data(my_int)
print(f"関数外のmy_int: {my_int}")
関数内のdata: [1, 2, 3, 'new_item']
関数外のmy_list: [1, 2, 3, 'new_item']
関数内のdata: 15
関数外のmy_int: 5

この例では、リストは関数内で変更され、関数外にも影響を与えていますが、整数は影響を受けていません。

グローバル変数への影響

関数内でグローバル変数を変更する場合、globalキーワードを使用する必要があります。

ミュータブルなオブジェクトは参照渡しのため、globalキーワードなしでも変更が反映されますが、イミュータブルなオブジェクトはglobalを使わないと変更が反映されません。

global_list = [1, 2, 3]
global_int = 5
def modify_global():
    global global_int
    # グローバル変数のリストに要素を追加
    global_list.append(4)
    # グローバル変数の整数を変更
    global_int += 10
modify_global()
print(f"グローバル変数のglobal_list: {global_list}")
print(f"グローバル変数のglobal_int: {global_int}")
グローバル変数のglobal_list: [1, 2, 3, 4]
グローバル変数のglobal_int: 15

この例では、globalキーワードを使用して整数を変更していますが、リストはそのまま変更が反映されています。

メモリ使用量への影響

Pythonでは、イミュータブルなオブジェクトを変更する際に新しいオブジェクトが作成されるため、メモリ使用量に影響を与えることがあります。

一方、ミュータブルなオブジェクトはそのまま変更されるため、メモリ使用量は比較的安定しています。

import sys
def memory_usage(obj):
    # オブジェクトのメモリ使用量を表示
    print(f"メモリ使用量: {sys.getsizeof(obj)} バイト")
my_string = "hello"
my_list = [1, 2, 3]
memory_usage(my_string)
memory_usage(my_list)
# 文字列を変更
my_string += " world"
# リストを変更
my_list.append(4)
memory_usage(my_string)
memory_usage(my_list)
メモリ使用量: 54 バイト
メモリ使用量: 80 バイト
メモリ使用量: 60 バイト
メモリ使用量: 88 バイト

この例では、文字列を変更するたびに新しいオブジェクトが作成され、メモリ使用量が増加しています。

一方、リストはそのまま変更されるため、メモリ使用量の増加は少ないです。

Pythonでの引数の扱い方

Pythonでは、関数の引数を柔軟に扱うことができます。

ここでは、デフォルト引数、可変長引数、キーワード引数、引数のアンパッキングについて解説します。

デフォルト引数とその注意点

デフォルト引数を使用すると、関数を呼び出す際に引数を省略できるようになります。

ただし、デフォルト引数にはミュータブルなオブジェクトを使用しないように注意が必要です。

以下の例を見てみましょう。

def greet(name, message="こんにちは"):
    # デフォルト引数を使用した挨拶
    print(f"{message}, {name}!")
greet("太郎")
greet("花子", "おはよう")
こんにちは, 太郎!
おはよう, 花子!

デフォルト引数にミュータブルなオブジェクトを使用すると、予期しない動作を引き起こすことがあります。

例えば、リストをデフォルト引数にすると、関数の呼び出し間でリストが共有されてしまいます。

def append_to_list(value, my_list=[]):
    # デフォルト引数にリストを使用
    my_list.append(value)
    return my_list
print(append_to_list(1))
print(append_to_list(2))
[1]
[1, 2]

この例では、リストが関数の呼び出し間で共有されているため、2回目の呼び出しでリストに2が追加されています。

可変長引数の使い方

可変長引数を使用すると、任意の数の引数を関数に渡すことができます。

*argsを使用して位置引数を、**kwargsを使用してキーワード引数を受け取ることができます。

def print_args(*args):
    # 可変長位置引数を表示
    for arg in args:
        print(arg)
def print_kwargs(**kwargs):
    # 可変長キーワード引数を表示
    for key, value in kwargs.items():
        print(f"{key}: {value}")
print_args(1, 2, 3)
print_kwargs(name="太郎", age=25)
1
2
3
name: 太郎
age: 25

キーワード引数の利用法

キーワード引数を使用すると、関数を呼び出す際に引数の順序を気にせずに指定できます。

これにより、コードの可読性が向上します。

def describe_person(name, age, city):
    # キーワード引数を使用した人物の説明
    print(f"{name}は{age}歳で、{city}に住んでいます。")
describe_person(age=30, city="東京", name="花子")
花子は30歳で、東京に住んでいます。

この例では、引数の順序を変えても正しく動作します。

引数のアンパッキング

引数のアンパッキングを使用すると、リストや辞書を関数の引数として展開できます。

*を使用してリストを、**を使用して辞書をアンパックします。

def add(a, b, c):
    # 3つの数値を加算
    return a + b + c
numbers = [1, 2, 3]
print(add(*numbers))
person_info = {'name': '太郎', 'age': 25, 'city': '大阪'}
describe_person(**person_info)
6
太郎は25歳で、大阪に住んでいます。

この例では、リストと辞書をアンパックして関数に渡しています。

これにより、関数の引数として個別に指定する必要がなくなります。

応用例

Pythonの引数の扱い方を理解することで、さまざまな応用が可能になります。

ここでは、関数の引数としてのリスト操作、辞書を用いた設定管理、オブジェクトの引数としての利用、引数を用いたデータ処理の最適化について解説します。

関数の引数としてのリスト操作

リストを関数の引数として渡すことで、リストの要素を動的に操作することができます。

以下の例では、リストの要素をすべて2倍にする関数を定義しています。

def double_elements(lst):
    # リストの各要素を2倍にする
    for i in range(len(lst)):
        lst[i] *= 2
numbers = [1, 2, 3, 4]
double_elements(numbers)
print(numbers)
[2, 4, 6, 8]

この例では、リストnumbersの各要素が2倍になっています。

リストはミュータブルなオブジェクトであるため、関数内での変更が関数外にも反映されます。

辞書を用いた設定管理

辞書を用いることで、設定情報を管理することができます。

以下の例では、辞書を使ってアプリケーションの設定を管理し、関数に渡しています。

def configure_app(settings):
    # 設定情報を表示
    for key, value in settings.items():
        print(f"{key}: {value}")
app_settings = {
    'theme': 'dark',
    'language': 'ja',
    'autosave': True
}
configure_app(app_settings)
theme: dark
language: ja
autosave: True

この例では、辞書app_settingsを使ってアプリケーションの設定を管理し、関数configure_appに渡しています。

辞書を使うことで、設定項目を簡単に追加・変更できます。

オブジェクトの引数としての利用

オブジェクトを関数の引数として渡すことで、オブジェクトの属性を操作することができます。

以下の例では、クラスPersonのインスタンスを関数に渡し、属性を変更しています。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
def celebrate_birthday(person):
    # 年齢を1歳増やす
    person.age += 1
    print(f"おめでとう、{person.name}さん!{person.age}歳になりました。")
taro = Person("太郎", 29)
celebrate_birthday(taro)
おめでとう、太郎さん!30歳になりました。

この例では、オブジェクトtaroの属性ageが関数内で変更されています。

オブジェクトはミュータブルであるため、関数内での変更が関数外にも反映されます。

引数を用いたデータ処理の最適化

引数を用いることで、データ処理を効率的に行うことができます。

以下の例では、リスト内包表記を使ってリストの要素をフィルタリングしています。

def filter_even_numbers(numbers):
    # 偶数のみを抽出
    return [num for num in numbers if num % 2 == 0]
data = [1, 2, 3, 4, 5, 6]
even_numbers = filter_even_numbers(data)
print(even_numbers)
[2, 4, 6]

この例では、リストdataから偶数のみを抽出しています。

リスト内包表記を使うことで、コードを簡潔にし、処理を効率化しています。

引数を用いることで、関数を汎用的にし、さまざまなデータセットに対して適用可能にしています。

よくある質問

Pythonでは本当に値渡しと参照渡しがあるのか?

Pythonでは、厳密には「値渡し」と「参照渡し」という用語は使われません。

Pythonの引数渡しは「オブジェクトの参照を渡す」という形で行われます。

イミュータブルなオブジェクト(例:整数、文字列、タプル)は、関数内で変更されても元のオブジェクトには影響を与えません。

一方、ミュータブルなオブジェクト(例:リスト、辞書)は、関数内での変更が元のオブジェクトにも影響を与えます。

したがって、Pythonの引数渡しは「参照渡し」に近い動作をしますが、実際にはオブジェクトの参照を渡していると考えるのが正しいです。

引数を変更すると元の変数も変わるのはなぜ?

引数を変更すると元の変数も変わるのは、ミュータブルなオブジェクトが関数に渡されると、そのオブジェクトの参照が渡されるためです。

関数内でそのオブジェクトを変更すると、同じ参照を持つ元の変数も変更されます。

例えば、リストや辞書はミュータブルであるため、関数内での変更が元の変数に影響を与えます。

これに対して、イミュータブルなオブジェクトは関数内で変更されても新しいオブジェクトが作成されるため、元の変数には影響を与えません。

引数の動作を制御する方法はあるのか?

引数の動作を制御する方法として、以下のような方法があります:

  • コピーを渡す: ミュータブルなオブジェクトを関数に渡す際に、copyモジュールを使ってオブジェクトのコピーを渡すことで、元のオブジェクトへの影響を防ぐことができます。

例:import copy; new_list = copy.deepcopy(original_list)

  • イミュータブルなオブジェクトを使用する: 可能であれば、イミュータブルなオブジェクトを使用することで、関数内での変更が元の変数に影響を与えないようにすることができます。
  • 関数内で新しいオブジェクトを作成する: 関数内で新しいオブジェクトを作成し、元のオブジェクトを変更しないようにすることも一つの方法です。

まとめ

Pythonの引数の動作は、オブジェクトの参照を渡すという形で行われます。

この記事では、値渡しと参照渡しの違い、引数の動作がプログラムに与える影響、そして引数の扱い方について詳しく解説しました。

これらの知識を活用して、より効率的でバグの少ないコードを書くことを目指しましょう。

  • URLをコピーしました!
目次から探す