Python中的函数参数传递机制:可变对象与不可变对象的区别
字数 968 2025-11-28 12:49:01

Python中的函数参数传递机制:可变对象与不可变对象的区别

知识点描述
在Python中,函数参数传递既不是纯粹的值传递(pass by value),也不是纯粹的引用传递(pass by reference),而是采用了一种称为"对象引用传递"(pass by object reference)的机制。理解这个机制的关键在于区分可变对象和不可变对象在函数参数传递时的不同行为。

详细讲解

1. 基本概念:可变对象 vs 不可变对象

首先我们需要明确Python中的两种基本对象类型:

  • 不可变对象:创建后不能被改变的对象

    • 数字(int, float, complex)
    • 字符串(str)
    • 元组(tuple)
    • 布尔值(bool)
    • frozenset
  • 可变对象:创建后可以被修改的对象

    • 列表(list)
    • 字典(dict)
    • 集合(set)
    • 字节数组(bytearray)
    • 自定义对象

2. 对象引用传递的基本原理

Python中的变量实际上是对对象的引用(类似于指针)。当我们将变量传递给函数时,传递的是这个引用的副本,而不是对象本身的副本。

def test_function(x):
    print(f"函数内x的id: {id(x)}")

a = 10
print(f"函数外a的id: {id(a)}")
test_function(a)  # 传递的是a指向的对象的引用

3. 不可变对象在函数中的行为

由于不可变对象不能被修改,任何"修改"操作实际上都会创建一个新对象:

def modify_immutable(x):
    print(f"修改前x的id: {id(x)}")
    x = x + 1  # 这实际上创建了一个新对象
    print(f"修改后x的id: {id(x)}")
    return x

a = 10
print(f"调用前a的id: {id(a)}")
result = modify_immutable(a)
print(f"调用后a的值: {a}")  # 输出: 10(原值未改变)
print(f"调用后a的id: {id(a)}")  # id未改变
print(f"返回值: {result}")

执行过程分析:

  1. 函数调用时,x获得了对整数10的引用
  2. 执行x = x + 1时,由于整数是不可变的,Python创建了一个新整数11
  3. x现在指向新对象11,但原始的a仍然指向10

4. 可变对象在函数中的行为

可变对象可以在原地修改,这会导致原始对象发生变化:

def modify_mutable(lst):
    print(f"修改前lst的id: {id(lst)}")
    lst.append(4)  # 原地修改,不创建新对象
    print(f"修改后lst的id: {id(lst)}")

my_list = [1, 2, 3]
print(f"调用前my_list: {my_list}")
print(f"调用前my_list的id: {id(my_list)}")
modify_mutable(my_list)
print(f"调用后my_list: {my_list}")  # 输出: [1, 2, 3, 4](原值被修改)
print(f"调用后my_list的id: {id(my_list)}")

执行过程分析:

  1. 函数调用时,lst获得了对列表对象的引用
  2. lst.append(4)在原地修改列表,不创建新对象
  3. 由于my_list和lst指向同一个对象,所以my_list看到的变化

5. 重新绑定 vs 原地修改

关键区别在于操作的类型:

def demonstrate_difference(lst):
    # 情况1:原地修改(影响外部)
    lst.append(100)  # 修改原对象
    
    # 情况2:重新绑定(不影响外部)
    lst = [1, 2, 3]  # 创建新对象,lst指向新对象
    return lst

original = [10, 20]
print(f"原始列表: {original}")
result = demonstrate_difference(original)
print(f"函数后original: {original}")  # 输出: [10, 20, 100]
print(f"函数返回值: {result}")  # 输出: [1, 2, 3]

6. 实际应用中的注意事项

避免意外的副作用:

# 有问题的写法:可能意外修改传入的列表
def bad_function(data, items=[]):  # 默认参数在定义时求值
    items.append(data)
    return items

# 正确的写法:防御性拷贝或使用None
def good_function(data, items=None):
    if items is None:
        items = []
    items.append(data)
    return items

需要修改但不想影响原对象:

def safe_modification(lst):
    # 创建副本进行操作
    new_lst = lst.copy()  # 或 list(lst) 或 lst[:]
    new_lst.append("new_item")
    return new_lst

original = [1, 2, 3]
modified = safe_modification(original)
print(f"原列表: {original}")  # [1, 2, 3]
print(f"新列表: {modified}")  # [1, 2, 3, "new_item"]

7. 特殊情况:元组包含可变对象

def modify_tuple_element(t):
    # 虽然元组本身不可变,但它包含的对象可能可变
    t[1].append(99)  # 修改元组中的列表

my_tuple = (1, [2, 3], 4)
print(f"修改前: {my_tuple}")  # (1, [2, 3], 4)
modify_tuple_element(my_tuple)
print(f"修改后: {my_tuple}")  # (1, [2, 3, 99], 4)

总结

  • Python传递的是对象引用的副本
  • 对不可变对象的"修改"会创建新对象,不影响原变量
  • 对可变对象的原地修改会影响所有引用该对象的变量
  • 使用赋值操作(=)是重新绑定,使用方法(如append)是原地修改
  • 在编写函数时要注意可能产生的副作用,必要时使用副本
Python中的函数参数传递机制:可变对象与不可变对象的区别 知识点描述 在Python中,函数参数传递既不是纯粹的值传递(pass by value),也不是纯粹的引用传递(pass by reference),而是采用了一种称为"对象引用传递"(pass by object reference)的机制。理解这个机制的关键在于区分可变对象和不可变对象在函数参数传递时的不同行为。 详细讲解 1. 基本概念:可变对象 vs 不可变对象 首先我们需要明确Python中的两种基本对象类型: 不可变对象 :创建后不能被改变的对象 数字(int, float, complex) 字符串(str) 元组(tuple) 布尔值(bool) frozenset 可变对象 :创建后可以被修改的对象 列表(list) 字典(dict) 集合(set) 字节数组(bytearray) 自定义对象 2. 对象引用传递的基本原理 Python中的变量实际上是对对象的引用(类似于指针)。当我们将变量传递给函数时,传递的是这个引用的副本,而不是对象本身的副本。 3. 不可变对象在函数中的行为 由于不可变对象不能被修改,任何"修改"操作实际上都会创建一个新对象: 执行过程分析: 函数调用时,x获得了对整数10的引用 执行 x = x + 1 时,由于整数是不可变的,Python创建了一个新整数11 x现在指向新对象11,但原始的a仍然指向10 4. 可变对象在函数中的行为 可变对象可以在原地修改,这会导致原始对象发生变化: 执行过程分析: 函数调用时,lst获得了对列表对象的引用 lst.append(4) 在原地修改列表,不创建新对象 由于my_ list和lst指向同一个对象,所以my_ list看到的变化 5. 重新绑定 vs 原地修改 关键区别在于操作的类型: 6. 实际应用中的注意事项 避免意外的副作用: 需要修改但不想影响原对象: 7. 特殊情况:元组包含可变对象 总结 Python传递的是对象引用的副本 对不可变对象的"修改"会创建新对象,不影响原变量 对可变对象的原地修改会影响所有引用该对象的变量 使用赋值操作(=)是重新绑定,使用方法(如append)是原地修改 在编写函数时要注意可能产生的副作用,必要时使用副本