【Python超入門】ユーザー定義関数の作り方(def文)

Pythonにおけるユーザー定義関数の作り方(def文)について入門者向けにまとめました。

「関数」と「引数」の違い

関数とは、「ある処理をひとまとまりにしたもの」です。たとえば、「ファイアボルト」という攻撃魔法を唱えると、敵に「魔力を2倍して10を足した」ダメージを与えるとします。この「数字を2倍して1を足す」という処理を、以下のように、関数としてまとめておくことで、何度でも簡単に使うことができます。

数学的な表現

先程の「数字を2倍して1を足す」という処理を、一次関数で表すとこうなります。

$$ f(x) = 2x + 10 $$

  • f(x) は関数
  • x は変数(魔力が入る)

魔力が10, 20, 30の魔法使いが「ファイアボルト」を唱えた場合、

$$ f(10) = 2×10 + 10 = 30 $$
$$ f(20) = 2×20 + 10 = 50 $$
$$ f(30) = 2×30 + 10 = 70 $$

とダメージを計算できます。同じ関数に違う引数を渡すことで、結果が変わるのがポイントです。このように、関数を使えば同じ処理を何度でも簡潔な表現で呼び出すことができます。

Pythonの関数

プログラミングの世界では、関数に渡す「入力の値」のことを引数(ひきすう)といいます。
関数には、主に以下の種類があります。今回は、「ユーザー定義関数」について解説します。

種類 説明
組み込み関数 Pythonに最初から備わっている関数 print(), len(), type()
ユーザー定義関数 自分で def 文を使って作る関数
ラムダ関数(無名関数) 名前を付けずに一時的に使える関数 lambda x: x * 2
外部ライブラリ関数 他の人が作った便利な関数。importして使う math.sqrt(), random.choice(), np.array()

関数の定義と呼び出し

Pythonでは、関数を定義する際に def 関数名(): を使います。
C言語などと違い、型の指定は不要で、シンプルに書けるのが特徴です。

書式

関数の中身(処理内容とreturn文)は、インデント(字下げ)を使ってブロックとして記述します。

def 関数名(引数1, 引数2, ...):
    処理内容
    return 返り値
部分 説明
def 「関数を定義するよ」という構文(def文)
関数名 関数の名前。英語で「何をするか」がわかる名前にするのが基本(例:calc_firebolt_damage
() 引数を入れる丸括弧。引数がない場合は空のままでOK
: 関数の始まりを示すコロン
return 処理の結果(返り値)を呼び出し元に返す。return文は省略可能

引数1, 引数2をそれぞれ「第1引数」「第2引数」といいます。

サンプルコード①

以下は、関数を定義し、呼び出す例です。

# ファイアボルトのダメージを計算する関数
def calc_firebolt_damage(magic_power):
    # 魔力をもとにダメージを計算(魔力×2 + 10)
    damage = magic_power * 2 + 10
    return damage  # 計算したダメージを返す

# 各キャラクターの魔力
luna_magic = 10   # 魔法使いルーナ
pikori_magic = 20 # 勇者ぴこり
akubi_magic = 30  # 僧侶あくび

# ファイアボルトによるダメージを計算
luna_damage = calc_firebolt_damage(luna_magic)
pikori_damage = calc_firebolt_damage(pikori_magic)
akubi_damage = calc_firebolt_damage(akubi_magic)

# 結果を表示
print(f"魔法使いルーナは「ファイアボルト」を放った!敵に {luna_damage} のダメージを与えた!")
print(f"勇者ぴこりは「ファイアボルト」を放った!敵に {pikori_damage} のダメージを与えた!")
print(f"僧侶あくびは「ファイアボルト」を放った!敵に {akubi_damage} のダメージを与えた!")
魔法使いルーナは「ファイアボルト」を放った!敵に 30 のダメージを与えた!
勇者ぴこりは「ファイアボルト」を放った!敵に 50 のダメージを与えた!
僧侶あくびは「ファイアボルト」を放った!敵に 70 のダメージを与えた!

関数 calc_firebolt_damage は、魔力(magic_power)を引数として受け取り、「魔力を2倍して10を足したダメージ」をreturn文で返します。
このように、関数を使えば「同じ処理を何度も書かずに呼び出す」ことができます。

サンプルコード② 複数の引数

先ほどの「ファイアボルト魔法」の関数を複数の引数で定義してみましょう。
たとえば、以下のように魔力だけでなく「武器の攻撃力」や「属性ボーナス」なども加味してダメージを計算することができます。

# ファイアボルトのダメージを計算する関数(引数1:魔力, 引数2:武器の攻撃力, 引数3:属性ボーナスを考慮)
def calc_firebolt_damage(magic_power, weapon_attack, element_bonus):
    # ダメージ計算式:魔力×2 + 武器攻撃力 + 属性ボーナス
    damage = magic_power * 2 + weapon_attack + element_bonus
    return damage

# 魔法使いルーナ(魔力10、杖の攻撃力5、炎属性ボーナス+3)
luna_damage = calc_firebolt_damage(10, 5, 3)

# 勇者ぴこり(魔力20、剣の攻撃力10、炎属性ボーナス+0)
pikori_damage = calc_firebolt_damage(20, 10, 0)

# 僧侶あくび(魔力30、聖杖の攻撃力2、炎属性ボーナス+5)
akubi_damage = calc_firebolt_damage(30, 2, 5)

# 結果を表示
print(f"魔法使いルーナは「ファイアボルト」を放った!敵に {luna_damage} のダメージを与えた!")
print(f"勇者ぴこりは「ファイアボルト」を放った!敵に {pikori_damage} のダメージを与えた!")
print(f"僧侶あくびは「ファイアボルト」を放った!敵に {akubi_damage} のダメージを与えた!")
魔法使いルーナは「ファイアボルト」を放った!敵に 28 のダメージを与えた!
勇者ぴこりは「ファイアボルト」を放った!敵に 50 のダメージを与えた!
僧侶あくびは「ファイアボルト」を放った!敵に 67 のダメージを与えた!
キャラクター 魔力 武器攻撃力 属性ボーナス ダメージ計算式 ダメージ
ルーナ 10 5 3 10×2 + 5 + 3 = 28 28
ぴこり 20 10 0 20×2 + 10 + 0 = 50 50
あくび 30 2 5 30×2 + 2 + 5 = 67 67

サンプルコード③ return文を省略

以下は、サンプルコード②からreturn文を省略したものです。

# ファイアボルトのダメージを計算する関数(引数1:魔力, 引数2:武器の攻撃力, 引数3:属性ボーナス)
def calc_firebolt_damage(magic_power, weapon_attack, element_bonus):
    # ダメージ計算式:魔力×2 + 武器攻撃力 + 属性ボーナス
    damage = magic_power * 2 + weapon_attack + element_bonus
    # return文がないと、呼び出し元には何も返らない(Noneになる)

# 魔法使いルーナ(魔力10、杖の攻撃力5、炎属性ボーナス+3)
luna_damage = calc_firebolt_damage(10, 5, 3)

# 勇者ぴこり(魔力20、剣の攻撃力10、炎属性ボーナス+0)
pikori_damage = calc_firebolt_damage(20, 10, 0)

# 僧侶あくび(魔力30、聖杖の攻撃力2、炎属性ボーナス+5)
akubi_damage = calc_firebolt_damage(30, 2, 5)


print(f"勇者ぴこりは「ファイアボルト」を放った!敵に {pikori_damage} のダメージを与えた!")
print(f"僧侶あくびは「ファイアボルト」を放った!敵に {akubi_damage} のダメージを与えた!")
魔法使いルーナは「ファイアボルト」を放った!敵に None のダメージを与えた!
勇者ぴこりは「ファイアボルト」を放った!敵に None のダメージを与えた!
僧侶あくびは「ファイアボルト」を放った!敵に None のダメージを与えた!

return文を省略すると、関数の処理は実行されますが、結果を返さないため、呼び出し元では None となってしまいます。
このような場合、以下のように関数内部に結果を表示する処理を追加します。

# ファイアボルトのダメージを計算する関数(引数1:魔力, 引数2:武器の攻撃力, 引数3:属性ボーナス, 引数4:キャラクター名)
def calc_firebolt_damage(magic_power, weapon_attack, element_bonus, name):
    # ダメージ計算式:魔力×2 + 武器攻撃力 + 属性ボーナス
    damage = magic_power * 2 + weapon_attack + element_bonus
    # 結果を表示
    print(f"{name}は「ファイアボルト」を放った!敵に {damage} のダメージを与えた!")

# ファイアボルトのダメージを計算:魔法使いルーナ(魔力10、杖の攻撃力5、炎属性ボーナス+3)
calc_firebolt_damage(10, 5, 3, "魔法使いルーナ")

# ファイアボルトのダメージを計算:勇者ぴこり(魔力20、剣の攻撃力10、炎属性ボーナス+0)
 calc_firebolt_damage(20, 10, 0, "勇者ぴこり")

# ファイアボルトのダメージを計算:僧侶あくび(魔力30、聖杖の攻撃力2、炎属性ボーナス+5)
calc_firebolt_damage(30, 2, 5, "僧侶あくび")
魔法使いルーナは「ファイアボルト」を放った!敵に 28 のダメージを与えた!
勇者ぴこりは「ファイアボルト」を放った!敵に 50 のダメージを与えた!
僧侶あくびは「ファイアボルト」を放った!敵に 67 のダメージを与えた!

デフォルト引数

関数を定義するときに引数に初期値(デフォルト値)を設定することができます。これを「デフォルト引数」といいます。
「デフォルト引数」を使うことで、引数を省略して関数を呼び出しても、あらかじめ決めたデフォルト値が使われるようになります。

書式

def 関数名(引数1=初期値1, 引数2=初期値2, ...):
    処理内容
    return 返り値

サンプルコード

以下は、第3引数をデフォルト引数に設定した例です。

# ファイアボルトのダメージを計算する関数
# 引数1: 魔力(必須)
# 引数2: 武器の攻撃力(必須)
# 引数3: 属性ボーナス(省略時は0)

def calc_firebolt_damage(magic_power, weapon_attack, element_bonus=0):
    # ダメージ計算式:魔力×2 + 武器攻撃力 + 属性ボーナス
    damage = magic_power * 2 + weapon_attack + element_bonus
    return damage

# 魔法使いルーナ(魔力10、杖の攻撃力5、炎属性ボーナス+3)
luna_damage = calc_firebolt_damage(10, 5, 3)

# 勇者ぴこり(魔力20、剣の攻撃力10、属性ボーナスなし → 省略)
pikori_damage = calc_firebolt_damage(20, 10)

# 僧侶あくび(魔力30、聖杖の攻撃力2、炎属性ボーナス+5)
akubi_damage = calc_firebolt_damage(30, 2, 5)

# 結果を表示
print(f"魔法使いルーナは「ファイアボルト」を放った!敵に {luna_damage} のダメージを与えた!")
print(f"勇者ぴこりは「ファイアボルト」を放った!敵に {pikori_damage} のダメージを与えた!")
print(f"僧侶あくびは「ファイアボルト」を放った!敵に {akubi_damage} のダメージを与えた!")
魔法使いルーナは「ファイアボルト」を放った!敵に 28 のダメージを与えた!
勇者ぴこりは「ファイアボルト」を放った!敵に 50 のダメージを与えた!
僧侶あくびは「ファイアボルト」を放った!敵に 67 のダメージを与えた!

勇者ぴこり が放つ「ファイアボルト」のダメージを計算する際、関数の呼び出しでは、属性ボーナスの引数を省略しているため、デフォルト値の0として扱われます

「ローカル変数」と「グローバル変数」

グローバル変数とは、関数の外で定義された変数で、どの関数からでも「参照」できます。
一方、ローカル変数とは、関数の中で定義された変数で、その関数の中でしか使えません。

サンプルコード①

以下のコードを例に見てみましょう。

# ファイアボルトのダメージを計算する関数(引数1:魔力, 引数2:武器の攻撃力, 引数3:属性ボーナスを考慮)
def calc_firebolt_damage(magic_power, weapon_attack, element_bonus):
    # ダメージ計算式:魔力×2 + 武器攻撃力 + 属性ボーナス
    damage = magic_power * 2 + weapon_attack + element_bonus
    return damage

# 魔法使いルーナ(魔力10、杖の攻撃力5、炎属性ボーナス+3)
luna_damage = calc_firebolt_damage(10, 5, 3)

# 勇者ぴこり(魔力20、剣の攻撃力10、炎属性ボーナス+0)
pikori_damage = calc_firebolt_damage(20, 10, 0)

# 僧侶あくび(魔力30、聖杖の攻撃力2、炎属性ボーナス+5)
akubi_damage = calc_firebolt_damage(30, 2, 5)

# 結果を表示
print(f"魔法使いルーナは「ファイアボルト」を放った!敵に {luna_damage} のダメージを与えた!")
print(f"勇者ぴこりは「ファイアボルト」を放った!敵に {pikori_damage} のダメージを与えた!")
print(f"僧侶あくびは「ファイアボルト」を放った!敵に {akubi_damage} のダメージを与えた!")
魔法使いルーナは「ファイアボルト」を放った!敵に 30 のダメージを与えた!
勇者ぴこりは「ファイアボルト」を放った!敵に 50 のダメージを与えた!
僧侶あくびは「ファイアボルト」を放った!敵に 70 のダメージを与えた!

上記コードの中だと、「ローカル変数」と「グローバル変数」は以下のとおりです。

  • ローカル変数 → 関数内で定義されているdamagemagic_powerweapon_attackelement_bonus
  • グローバル変数 → 関数外で定義されているluna_damagepikori_damageakubi_damage

サンプルコード②

グローバル変数を関数内で書き換えるには、以下のように関数内で global 宣言を使う必要があります。

# グローバル変数:ファイアボルトの使用回数
firebolt_count = 0

# ファイアボルトのダメージを計算し、使用回数を更新する関数
def calc_firebolt_damage(magic_power, weapon_attack, element_bonus):
    global firebolt_count  # グローバル変数を関数内で使う宣言(global宣言)
    firebolt_count += 1    # 使用回数を1増やす

    damage = magic_power * 2 + weapon_attack + element_bonus
    return damage

# 魔法使いルーナ(魔力10、杖の攻撃力5、炎属性ボーナス+3)
luna_damage = calc_firebolt_damage(10, 5, 3)

# 勇者ぴこり(魔力20、剣の攻撃力10、炎属性ボーナス+0)
pikori_damage = calc_firebolt_damage(20, 10, 0)

# 僧侶あくび(魔力30、聖杖の攻撃力2、炎属性ボーナス+5)
akubi_damage = calc_firebolt_damage(30, 2, 5)

# 結果を表示
print(f"魔法使いルーナは「ファイアボルト」を放った!敵に {luna_damage} のダメージを与えた!")
print(f"勇者ぴこりは「ファイアボルト」を放った!敵に {pikori_damage} のダメージを与えた!")
print(f"僧侶あくびは「ファイアボルト」を放った!敵に {akubi_damage} のダメージを与えた!")

# ファイアボルトの使用回数を表示
print(f"ファイアボルトは合計 {firebolt_count} 回使われました!")
魔法使いルーナは「ファイアボルト」を放った!敵に 30 のダメージを与えた!
勇者ぴこりは「ファイアボルト」を放った!敵に 50 のダメージを与えた!
僧侶あくびは「ファイアボルト」を放った!敵に 70 のダメージを与えた!
ファイアボルトは合計 3 回使われました!

グローバル変数 firebolt_countは、関数の外で定義され、関数内からは global 宣言で更新可能です。

【Python超入門】「ローカル変数」と「グローバル変数」の違いとglobal宣言
Pythonにおける「ローカル変数」と「グローバル変数」の違いについて入門者向けにまとめました。

デコレーター

デコレーターとは?

デコレーター(decorator)とは、関数に追加の処理(前処理・後処理など)を自動的に組み込むための機能です。
関数の定義を変更せずに、機能を拡張できるのが特徴です。「ログ出力」や「認証チェック」など複数の関数に同じ処理を追加したいときに便利です。

使い方はとてもシンプルで、対象の関数の上に @デコレータ名 を書くだけです。

サンプルコード①

# デコレータの定義
def decorator(func):
    def wrapper():
        print("前処理")   # 関数の前に実行される処理(元の関数を実行する前のログ保存など)
        func()            # 元の関数を実行
        print("後処理")   # 関数の後に実行される処理(元の関数を実行した後のログ保存など)
    return wrapper

# デコレーターを関数に適用
@decorator
def main():
    print("メイン処理")

# 関数の呼び出し
main()
前処理
メイン処理
後処理
用語 意味
decorator デコレーター関数。処理を追加するための関数
func デコレート対象の関数(この例では main
wrapper() 実際に前後の処理+元の関数をまとめて実行する関数
@decorator main() 関数に decorator を適用する記法

引数付き関数への対応

元の関数に引数がある場合、wrapper()関数*args**kwargs を使うことで、デコレーター側でもその引数を受け取ることができます。

サンプルコード②

以下は、関数の引数をデコレーター側で受け取り、if文で動作を条件分岐させる例です。

# デコレーターの定義
def battle_event(func):
    def wrapper(*args, **kwargs):
        print("■戦闘開始!")
        print(f"引数一覧: {args} → ログに保存")
        # キーワード引数を加工(kwargsは辞書)
        if "critical" in kwargs and kwargs["critical"]:
            print("☆クリティカル攻撃発動!")

        func(*args, **kwargs)  # 引数をそのまま渡す
        print("■戦闘終了!")
    return wrapper

# 関数の定義
@battle_event
def attack(name, target, damage, critical=False):
    print(f"{name}は{target} に攻撃し、 {damage} のダメージを与えた!")

# 関数の呼び出し(通常攻撃)
attack("勇者ぴこり", "スライム", 30)

print("勇者ぴこりは前に進む")

# 関数の呼び出し(クリティカル攻撃)
attack("勇者ぴこり", "メタルビー", 100, critical=True)
■戦闘開始!
引数一覧: (‘勇者ぴこり’, ‘スライム’, 30) → ログに保存
勇者ぴこりはスライム に攻撃し、 30 のダメージを与えた!
■戦闘終了!
勇者ぴこりは前に進む
■戦闘開始!
引数一覧: (‘勇者ぴこり’, ‘メタルビー’, 100) → ログに保存
☆クリティカル攻撃発動!
勇者ぴこりはメタルビー に攻撃し、 100 のダメージを与えた!
■戦闘終了!

サンプルコード②の解説

攻撃処理の前後に「戦闘開始」「戦闘終了」などの演出を自動で追加するために、デコレータ関数(battle_eventを定義しています。

def battle_event(func):
    def wrapper(*args, **kwargs):
        print("■戦闘開始!")
        print(f"引数一覧: {args} → ログに保存")
        # キーワード引数を加工(kwargsは辞書)
        if "critical" in kwargs and kwargs["critical"]:
            print("☆クリティカル攻撃発動!")

        func(*args, **kwargs)  # 引数をそのまま渡す
        print("■戦闘終了!")
    return wrapper
  • wrapper() が実際に処理を包み込む関数で、前処理・後処理を追加します。
  • *args**kwargs を使うことで、元の関数の引数を受け取ります。そして、 kwargs["critical"]True のときだけ、if文で特殊演出(☆クリティカル攻撃発動!)を追加します。

複数のデコレーターを重ねがけ

Pythonでは、1つの関数に複数のデコレータを重ねて適用することができます
これにより、前処理・後処理・ログ出力などを段階的に追加できます。

@A
@B
def func():
    ...

上記のように、関数 func に対して2つのデコレーターA``Bを適用すると、以下のように処理されます。

  1. デコレーターBfunc を包む
  2. デコレーターAB(func) をさらに包む
  3. 実行時は A(B(func))() の順で処理される

サンプルコード③

# デコレータの定義(魔法詠唱の演出用)
def spell_animation(func):
    def wrapper(*args, **kwargs):
        print("✨ 魔法陣が光り始めた…")
        func(*args, **kwargs)
        print("💥 魔法が炸裂した!")
    return wrapper

# デコレータ(ログ記録用)
def battle_log(func):
    def wrapper(*args, **kwargs):
        print("...ログ記録開始")
        func(*args, **kwargs)
        print("...ログ記録終了")
    return wrapper

# 魔法攻撃用の関数(デコレータを重ねがけ)
@battle_log
@spell_animation
def cast_spell(name, target, spell):
    print(f"{name}は{target} に {spell} を唱えた!")

# 呼び出し
cast_spell("勇者ぴこり", "ゴブリン", "ファイアボール")
…ログ記録開始
✨ 魔法陣が光り始めた…
勇者ぴこりはゴブリン に ファイアボール を唱えた!
💥 魔法が炸裂した!
…ログ記録終了
【Python】デコレーターの使い方(関数の上に@をつけるやつ)
Pythonでデコレーターを使う方法について入門者向けにまとめました。

参照渡しのような動作

Pythonでは、関数に引数を渡すときに「オブジェクトへの参照(アドレス)」が値として渡されます。
そのため、リスト・辞書などのミュータブル(変更可能)なオブジェクトは、関数内で中身を変更すると元の変数にも影響が及びます

サンプルコード①

以下は、関数にリストを引数を渡し、変更したときの例です。

def modify_inventory(bag):
    # リストに要素を追加
    bag.append("回復薬")
    print(f"関数内: アイテム袋 → {bag}")

# 勇者ぴこりの装備袋
pikori_bag = ["剣", "盾", "地図"]

# 関数の呼び出し
modify_inventory(pikori_bag)
print(f"関数外: アイテム袋 → {pikori_bag}")
関数内: アイテム袋 → [‘剣’, ‘盾’, ‘地図’, ‘回復薬’]
関数外: アイテム袋 → [‘剣’, ‘盾’, ‘地図’, ‘回復薬’]

bagpikori_bag同じリストオブジェクトを参照しているため、関数内で追加された「回復薬」が関数外にも反映されます。

サンプルコード②

以下は、関数にリストを引数を渡し、再代入したときの例です。

def modify_inventory(bag):
    # 変数bagにリストを再代入
    bag = ["弓", "盾", "地図"]
    print(f"関数内: アイテム袋 → {bag}")

# 勇者ぴこりの装備袋
pikori_bag = ["剣", "盾", "地図"]
# 関数の呼び出し
modify_inventory(pikori_bag)
print(f"関数外: アイテム袋 → {pikori_bag}")
関数内: アイテム袋 → [‘弓’, ‘盾’, ‘地図’]
関数外: アイテム袋 → [‘剣’, ‘盾’, ‘地図’]

bag に新しいリストを再代入したことで、関数内の変数は別のオブジェクトを指すようになり、元の pikori_bag には影響しません。このように、再代入は影響しないところが通常の参照渡しとは異なり、混乱しやすいポイントです。

IDの確認

Pythonの id() 関数は、オブジェクトの識別番号(identity)を返します。この番号は、オブジェクトがメモリ上に存在する場所(アドレス)に対応する整数値であり、オブジェクトの生存期間中は一意です。サンプルコード①と②の「オブジェクトの参照」をid() 関数で確認してみましょう。

■サンプルコード①の確認

def modify_inventory(bag):
    bag.append("回復薬")
    print(f"関数内: アイテム袋 → {bag}")
    print(f"関数内: 変数 bag の ID → {id(bag)}")

# 勇者ぴこりの装備袋
pikori_bag = ["剣", "盾", "地図"]
modify_inventory(pikori_bag)
print(f"関数外: アイテム袋 → {pikori_bag}")
print(f"関数外: 変数 pikori_bag の ID → {id(pikori_bag)}")
関数内: アイテム袋 → [‘剣’, ‘盾’, ‘地図’, ‘回復薬’]
関数内: 変数 bag の ID → 140583200747904
関数外: アイテム袋 → [‘剣’, ‘盾’, ‘地図’, ‘回復薬’]
関数外: 変数 pikori_bag の ID → 140583200747904

id() 関数で確認すると、関数内外で2つのリストが同じID(=同じオブジェクト)を指していることがわかります

■サンプルコード②の確認

def modify_inventory(bag):
    # 変数bagにリストを再代入
    bag = ["弓", "盾", "地図"]
    print(f"関数内: アイテム袋 → {bag}")
    print(f"関数内: 変数 bag の ID → {id(bag)}")

# 勇者ぴこりの装備袋
pikori_bag = ["剣", "盾", "地図"]
# 関数の呼び出し
modify_inventory(pikori_bag)
print(f"関数外: アイテム袋 → {pikori_bag}")
print(f"関数外: 変数 pikori_bag の ID → {id(pikori_bag)}")
関数内: アイテム袋 → [‘弓’, ‘盾’, ‘地図’]
関数内: 変数 bag の ID → 139866289410752
関数外: アイテム袋 → [‘剣’, ‘盾’, ‘地図’]
関数外: 変数 pikori_bag の ID → 139866291745216

id() 関数で確認すると、関数内外で2つのリストが異なるID(=異なるオブジェクト)を指していることがわかります。

種別 説明
IDが同じになる場合(サンプルコード①) 同じオブジェクトを複数の変数が参照している場合(例:a = b)、IDは同じになります。これは「同じアイテム袋を複数のラベルで呼んでいる」ような状態です。
IDが異なる場合(サンプルコード②) 新しいオブジェクトを作成した場合(例:再代入やコピー)、IDは異なるものになります。これは「新しいアイテム袋を用意して、別のラベルを貼った」ような状態です。

Pythonは「オブジェクトの参照を値として渡す」という独特な方式を採用しており、C++などの「通常の参照渡し」とは動作が異なる点に注意が必要です。

種別 説明 動作
通常の参照渡し(C++など) 変数そのもの(=メモリの場所)を渡す 関数内で変数の中身を変更したり、再代入すると元の変数も変わる
参照渡しのような動作(Python) 変数が指すオブジェクトへの参照を値として渡す 関数内でオブジェクトの中身を変更すれば反映されるが、再代入しても元の変数には影響しない
【Python】ミュータブル・イミュータブルなオブジェクトの違いと注意点
Pythonにおけるミュータブル・イミュータブルなオブジェクトの違いと注意点について解説します。

値渡しのような動作

数値・文字列・タプルなどのイミュータブル(変更不可)なオブジェクトの場合、関数内で値を変更しても元の変数には影響しません
この挙動は、値渡しのように見えるため、混乱しやすいポイントです。

サンプルコード①

以下は、数値(int)の変数を関数内で変更する例です。

def modify_power(power):
    power = 999
    print(f"関数内: 魔力 → {power}")

# 勇者ぴこりの魔力
pikori_power = 100
modify_power(pikori_power)
print(f"関数外: 魔力 → {pikori_power}")
関数内: 魔力 → 999
関数外: 魔力 → 100

関数内で power = 999 に再代入しても、元の pikori_power は変化しません。これは power が新しいオブジェクトを参照するようになったためです。

サンプルコード②

以下は、文字列(str)の場合の変数を関数内で変更する例です。

def rename_hero(name):
    name = "伝説のぴこり"
    print(f"関数内: 名前 → {name}")

pikori_name = "ぴこり"
rename_hero(pikori_name)
print(f"関数外: 名前 → {pikori_name}")
関数内: 名前 → 伝説のぴこり
関数外: 名前 → ぴこり

文字列もイミュータブルなので、関数内で再代入しても外には影響しません

IDの確認

id() 関数で、オブジェクトの識別番号(identity)を確認してみましょう。

■サンプルコード①のIDを確認

def modify_power(power):
    print(f"関数内: ID → {id(power)}")
    power = 999
    print(f"関数内: 再代入後の ID → {id(power)}")

pikori_power = 100
print(f"関数外: ID → {id(pikori_power)}")
modify_power(pikori_power)
関数外: ID → 10860264
関数内: ID → 10860264
関数内: 再代入後の ID → 139795537873424

出力結果では、関数内の再代入後のIDが変化していることがわかります。つまり、新しいオブジェクトが作られたということになります。
Pythonでは「オブジェクトの参照を値として渡す」ため、イミュータブルなオブジェクトは再代入しても元の変数に影響しないという挙動になります。

【Python】ミュータブル・イミュータブルなオブジェクトの違いと注意点
Pythonにおけるミュータブル・イミュータブルなオブジェクトの違いと注意点について解説します。

デフォルト引数の罠

Pythonでは、関数の引数に初期値(デフォルト値)を設定することができます。しかし、以下のようにミュータブル(変更可能)なオブジェクトを初期値にすると、予期しない動作が起こることがあります

サンプルコード

def add_to_list(value, lst=[]):
    lst.append(value)
    return lst

print(add_to_list("剣"))  # ['剣']
print(add_to_list("盾"))  # ['剣', '盾'] ← あれ?前回の値が残ってる!
[‘剣’]
[‘剣’, ‘盾’]

Pythonでは、関数のデフォルト引数は関数が定義された瞬間に一度だけ評価されます。そのとき、ミュータブルなオブジェクト(例:リスト [])が使われると、そのオブジェクトへの参照が保存され続けます。つまり、lst の初期値 [] は、関数が定義されたときに一度だけ作られたあと保持され続けるため、 関数を何度呼び出しても、同じリストが使い回されてしまいます。です。これを避けるには、以下のようにデフォルト値に None を使い、関数内で新しいリストを作ります

def add_to_list(value, lst=None):
    if lst is None:
        lst = []
    lst.append(value)
    return lst

print(add_to_list("剣"))  # ['剣']
print(add_to_list("盾"))  # ['盾'] ← 前回の値は残っていない!
[‘剣’]
[‘盾’]

None は「引数に何も渡されなかったよ」という指示です。そのときだけ新しいリスト [] を作ることで、毎回別のオブジェクトが使われるようになります。

【Python】ミュータブル・イミュータブルなオブジェクトの違いと注意点
Pythonにおけるミュータブル・イミュータブルなオブジェクトの違いと注意点について解説します。

練習問題

本ページで学んだ内容を定着させるとともに、「Python 3 エンジニア認定基礎試験」や「基本情報技術者試験」の対策にも役立つ練習問題を用意しましたので、チャレンジしてみてください。

【問題1】関数の基本構文(難易度★☆☆☆☆)

次のうち、Pythonで関数を定義する正しい方法を選びなさい。

A. function greet():
B. def greet:
C. def greet():
D. define greet():


正解:C
解説:Pythonでは、関数は def 関数名(): の形式で定義します。

【問題2】return文の役割(難易度★☆☆☆☆)

次のコードの出力結果として正しいものを選びなさい。

def add(a, b):
    return a + b

print(add(3, 5))

A. 35
B. 8
C. None
D. “3 + 5”


正解:B
解説:return文により、3 + 5 の結果である 8 が返されます。

【問題3】引数の数(難易度★★☆☆☆)

次の関数呼び出しでエラーになるものを選びなさい。

def greet(name, age):
    return f"{name}は{age}歳です"

A. greet("アリス", 20)
B. greet("ボブ")
C. greet("キャロル", 25)
D. greet(name="ダン", age=30)


正解:B
解説:引数が2つ必要なのに、1つしか渡されていないためエラーになります。

【問題4】デフォルト引数(難易度★★☆☆☆)

次のコードの出力結果として正しいものを選びなさい。

def greet(name="勇者"):
    return f"こんにちは、{name}さん!"

print(greet())

A. こんにちは、勇者さん!
B. こんにちは、nameさん!
C. エラーになる
D. None


正解:A
解説:引数を省略した場合、デフォルト値「勇者」が使われます。

【問題5】関数の戻り値(難易度★★★☆☆)

次のコードの出力結果として正しいものを選びなさい。

def power(base, exponent):
    return base ** exponent

print(power(2, 3))

A. 6
B. 8
C. 9
D. 5


正解:B
解説:2 の 3 乗は 8 です。

【問題6】関数のネスト(難易度★★★☆☆)

次のコードの出力結果として正しいものを選びなさい。

def outer():
    def inner():
        return "内側"
    return inner()

print(outer())

A. outer
B. inner
C. 内側
D. None


正解:C
解説:outer() は inner() を呼び出し、その戻り値「内側」を返します。

【問題7】関数とスコープ(難易度★★★★☆)

次のコードの出力結果として正しいものを選びなさい。

x = 10

def show():
    x = 5
    return x

print(show())
print(x)

A. 5 と 10
B. 10 と 5
C. 5 と 5
D. 10 と 10


正解:A
解説:関数内の x はローカル変数で、外の x とは別物です。

【問題8】関数とリスト(難易度★★★☆☆)

次のコードの出力結果として正しいものを選びなさい。

def add_item(bag):
    bag.append("ポーション")

items = ["剣", "盾"]
add_item(items)
print(items)

A. [“剣”, “盾”]
B. [“剣”, “盾”, “ポーション”]
C. [“ポーション”]
D. エラーになる


正解:B
解説:リストは参照渡しされるため、元のリストに変更が反映されます。

【問題9】関数の戻り値がない場合(難易度★★☆☆☆)

次のコードの出力結果として正しいものを選びなさい。

def shout():
    print("やったー!")

result = shout()
print(result)

A. やったー!
B. None
C. やったー! と None
D. エラーになる


正解:C
解説:print() によって「やったー!」が表示され、戻り値がないため result は None。

【問題10】関数と条件分岐(難易度★★★☆☆)

次のコードの出力結果として正しいものを選びなさい。

def judge(score):
    if score >= 80:
        return "合格"
    else:
        return "不合格"

print(judge(85))

A. 合格
B. 不合格
C. 85
D. None


正解:A
解説:score が 85 のため、条件 score >= 80 を満たし、「合格」が返されます。

【問題11】デフォルト引数の利用(難易度★★☆☆☆)

次のコードの出力結果として正しいものを選びなさい。

def greet(name="旅人"):
    return f"{name}さん、ようこそ!"

print(greet("ルーナ"))
print(greet())

A. ルーナさん、ようこそ! と 旅人さん、ようこそ!
B. 旅人さん、ようこそ! と ルーナさん、ようこそ!
C. ルーナさん、ようこそ! と None
D. エラーになる


正解:A
解説:引数を省略した場合、デフォルト値「旅人」が使われます。

【問題12】ローカル変数とグローバル変数(難易度★★★☆☆)

次のコードの出力結果として正しいものを選びなさい。

x = 100

def show():
    x = 50
    print("関数内:", x)

show()
print("関数外:", x)

A. 関数内: 50 関数外: 100
B. 関数内: 100 関数外: 50
C. 関数内: 50 関数外: 50
D. エラーになる


正解:A
解説:関数内の x はローカル変数で、外の x とは別物です。

【問題13】グローバル変数の変更(難易度★★★★☆)

次のコードの出力結果として正しいものを選びなさい。

count = 0

def increment():
    global count
    count += 1

increment()
print(count)

A. 0
B. 1
C. エラーになる
D. None


正解:B
解説:global 宣言により、関数内でグローバル変数 count を変更できます。

【問題14】デコレーターの基本(難易度★★★★☆)

次のコードの出力結果として正しいものを選びなさい。

def decorator(func):
    def wrapper():
        print("前処理")
        func()
        print("後処理")
    return wrapper

@decorator
def hello():
    print("こんにちは")

hello()

A. こんにちは
B. 前処理 → こんにちは → 後処理
C. エラーになる
D. decorator が出力される


正解:B
解説:@decorator により、hello() は wrapper() に置き換えられます。

【問題15】参照渡しのような動作(難易度★★★☆☆)

次のコードの出力結果として正しいものを選びなさい。

def add_item(bag):
    bag.append("ポーション")

inventory = ["剣"]
add_item(inventory)
print(inventory)

A. [“剣”]
B. [“剣”, “ポーション”]
C. [“ポーション”]
D. エラーになる


正解:B
解説:リストはミュータブルなオブジェクトで、関数内の変更が外部に反映されます。

【問題16】値渡しのような動作(難易度★★★☆☆)

次のコードの出力結果として正しいものを選びなさい。

def change_name(name):
    name = "伝説の勇者"

hero = "ぴこり"
change_name(hero)
print(hero)

A. ぴこり
B. 伝説の勇者
C. None
D. エラーになる


正解:A
解説:文字列はイミュータブルなため、関数内の変更は外部に影響しません。

【問題17】デフォルト引数の罠①(難易度★★★★☆)

次のコードの出力結果として正しいものを選びなさい。

def add_log(entry, log=[]):
    log.append(entry)
    return log

print(add_log("A"))
print(add_log("B"))

A. [“A”] と [“B”]
B. [“A”] と [“A”, “B”]
C. [“A”] と [“B”, “A”]
D. エラーになる


正解:B
解説:デフォルト引数のリストは関数定義時に一度だけ生成され、使い回されます。

【問題18】デフォルト引数の罠②(難易度★★★★☆)

次のコードの出力結果として正しいものを選びなさい。

def add_item(item, bag=None):
    if bag is None:
        bag = []
    bag.append(item)
    return bag

print(add_item("剣"))
print(add_item("盾"))

A. [“剣”] と [“盾”]
B. [“剣”] と [“剣”, “盾”]
C. [“盾”] と [“剣”]
D. エラーになる


正解:A
解説:None を使って毎回新しいリストを生成することで、使い回しを防いでいます。

【問題19】デコレーターの応用(難易度★★★★★)

次のコードの出力結果として正しいものを選びなさい。

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print("呼び出しログ:", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log_decorator
def attack(power):
    print(f"{power}のダメージを与えた!")

attack(30)

A. 呼び出しログ: attack → 30のダメージを与えた!
B. attack → 30
C. 30のダメージを与えた!
D. エラーになる


正解:A
解説:デコレーターによって attack() は wrapper() に置き換えられ、関数名がログとして表示された後、元の attack 関数が実行されます。

【問題20】デフォルト引数の罠③(難易度★★★★★)

次のコードの出力結果として正しいものを選びなさい。

def summon_beast(beast="ドラゴン", summoned=[]):
    summoned.append(beast)
    return summoned

print(summon_beast())
print(summon_beast("フェニックス"))

A. [“ドラゴン”] と [“フェニックス”]
B. [“ドラゴン”] と [“ドラゴン”, “フェニックス”]
C. [“ドラゴン”] と [“ドラゴン”, “ドラゴン”]
D. エラーになる


正解:B
解説:デフォルト引数のリストは関数定義時に一度だけ生成されるため、複数回の呼び出しで同じリストが使われ続けます。

課題(実践力強化)

Pythonプログラミングの実践力を強化したい人向けの課題を用意しました。仕様通りに動くプログラムを書いたりエラーメッセージを読んでコードを修正するという作業は、プログラミングおいて重要ですので、是非チャレンジしてみてください。

課題① コード作成(難易度★★☆☆☆)

以下の仕様を満たすPythonコードを1から書いてください。

  • 関数 greet() を定義する
  • 実行すると「こんにちは、ぴこさん!」と表示される
こんにちは、ぴこさん!

仕様を満たすコードは次のようになる。

def greet():
    print("こんにちは、ぴこさん!")

greet()

課題② コード作成(難易度★★★☆☆)

以下の仕様を満たすPythonコードを1から書いてください。

  • 関数 multiply() を定義する
  • 引数として2つの整数を受け取り、それらの積を返す
  • 関数を使って 34 を掛け算し、結果を表示する
12

仕様を満たすコードは次のようになる。

def multiply(a, b):
    return a * b

print(multiply(3, 4))

課題③ コード作成(難易度★★★☆☆)

以下の仕様を満たすPythonコードを1から書いてください。

  • 関数 is_even() を定義する
  • 引数として整数を受け取り、偶数なら True、奇数なら False を返す
  • 関数を使って 7 を判定し、結果を表示する
False

仕様を満たすコードは次のようになる。

def is_even(n):
    return n % 2 == 0

print(is_even(7))

課題④ コード作成(難易度★★★★☆)

以下の仕様を満たすPythonコードを1から書いてください。

  • 関数 repeat_message() を定義する
  • 引数として文字列 msg と整数 times を受け取り、msgtimes 回繰り返して表示する
  • 関数を使って「ぴこ!」を3回表示する
ぴこ!ぴこ!ぴこ!

仕様を満たすコードは次のようになる。

def repeat_message(msg, times):
    print(msg * times)

repeat_message("ぴこ!", 3)

課題⑤ コード作成(難易度★★★★☆)

以下の仕様を満たすPythonコードを1から書いてください。

  • 関数 get_status() を定義する
  • 引数 hp(整数)を受け取り、以下の条件で文字列を返す
    • hp が 50 以上なら「元気ぴこ!」
    • hp が 20 以上なら「ちょっと疲れぴこ…」
    • それ未満なら「ぴこぴこ限界ぴこ…」
  • 関数を使って hp=15 のときの結果を表示する
ぴこぴこ限界ぴこ…

仕様を満たすコードは次のようになる。

def get_status(hp):
    if hp >= 50:
        return "元気ぴこ!"
    elif hp >= 20:
        return "ちょっと疲れぴこ…"
    else:
        return "ぴこぴこ限界ぴこ…"

print(get_status(15))

課題⑥ エラー修正(難易度★★★☆☆)

以下のコードを実行すると、エラーが発生します。エラーメッセージを読み、正しいコードに修正してください。

def hello()
    print("ぴこぴここんにちは!")
SyntaxError: expected ‘:’

関数定義の行にコロン : が抜けています。Pythonでは def 関数名(): のようにコロンが必要です。

修正後のコードは次のようになります。

def hello():
    print("ぴこぴここんにちは!")

課題⑦ エラー修正(難易度★★★★☆)

以下のコードを実行すると、エラーが発生します。エラーメッセージを読み、正しいコードに修正してください。

def show_message(msg):
    print(msg)

show_message
TypeError: ‘function’ object is not callable

関数を呼び出すには、() が必要です。show_message だけでは関数オブジェクトを参照しているだけで、実行されません。

修正後のコードは次のようになります。

def show_message(msg):
    print(msg)

show_message("ぴこぴこメッセージ!")

課題⑧ エラー修正(難易度★★★★☆)

以下のコードを実行すると、エラーが発生します。エラーメッセージを読み、正しいコードに修正してください。

def add(a, b):
    return a + b

result = add(5)
print(result)
TypeError: add() missing 1 required positional argument: ‘b’

関数 add() は2つの引数 ab を必要としますが、呼び出し時に1つしか渡されていません。

修正後のコードは次のようになります。

def add(a, b):
    return a + b

result = add(5, 3)
print(result)

課題⑨ バグ修正(難易度★★★★☆)

以下のコードは、関数内でリストに「ぴこ」を追加するつもりですが、関数外にある元のリストmy_listにも反映されてしまう意図しない動作が起きます。バグを取り除いて、my_listが変更されないようにしてください。

def add_item(items):
    items.append("ぴこ")

my_list = ["こん"]
add_item(my_list)
print(my_list)
[‘こん’, ‘ぴこ’]

リストはミュータブル(変更可能)なオブジェクトで、関数に渡すと「参照渡しのような動作」になります。元のリストを守りたい場合は、コピーを使いましょう。

修正後のコードは次のようになります。

def add_item(items):
    copied = items.copy()
    copied.append("ぴこ")
    return copied

my_list = ["こん"]
new_list = add_item(my_list)
print(my_list)     # ['こん']
print(new_list)    # ['こん', 'ぴこ']

課題⑩ バグ修正(難易度★★★☆☆)

以下のコードは、関数内で数値を変更すると、元の変数にも反映されてしまい、混乱しています。バグを取り除いて、意図通りの動作を確認してください。

def power_up(hp):
    hp += 50

my_hp = 100
power_up(my_hp)
print(my_hp)
100

整数はイミュータブル(変更不可)なオブジェクトなので、「値渡しのような動作」になります。関数内で変更しても、元の変数には影響しません。変更結果を返して受け取る必要があります。

修正後のコードは次のようになります。

def power_up(hp):
    return hp + 50

my_hp = 100
my_hp = power_up(my_hp)
print(my_hp)  # 150

課題⑪ バグ修正(難易度★★★★★)

以下のコードは、関数を複数回使っても毎回空のリストから始まるつもりですが、意図しない動作が起きます。バグを取り除いて、毎回独立したリストが使われるようにしてください。

def collect(item, box=[]):
    box.append(item)
    return box

print(collect("ぴこ"))
print(collect("ぽこ"))
[‘ぴこ’]
[‘ぴこ’, ‘ぽこ’]

デフォルト引数にミュータブルなオブジェクト(リストなど)を使うと、関数を呼び出すたびに同じオブジェクトが使われてしまいます。これは「デフォルト引数の罠」と呼ばれます。

修正後のコードは次のようになります。

def collect(item, box=None):
    if box is None:
        box = []
    box.append(item)
    return box

print(collect("ぴこ"))  # ['ぴこ']
print(collect("ぽこ"))  # ['ぽこ']

関連ページ(もっと学びたい人へ)

Pythonの基礎から応用例まで、以下ページから学ぶことができます。

【Python超入門】基礎から応用例まで幅広く解説
PythonについてPythonは、統計処理や機械学習、ディープラーニングといった数値計算分野を中心に幅広い用途で利用されているプログラミング言語です。他のプログラミング言語と比較して「コードが短くて読みやすい、書きやすい」「ライブラリが豊...
この記事を書いた人
西住技研

プログラミング言語「Python」を研究、仕事、趣味でデータ分析や作業自動化などに活用してきたノウハウを情報発信しています。
筆者の詳しいプロフィールやお問合せはこちらのページまで。
YoutubeX(旧Twitter)でも情報発信中です!

西住技研をフォローする
Python基礎

コメント