欢迎光临
我们一直在努力

Python中可变对象与不可变对象

我们来深入解析Python中一个核心概念:可变(Mutable)对象和不可变(Immutable)对象。这个概念直接关系到你的代码在内存中如何运作,尤其是在进行赋值、拷贝、传参等操作时,理解它可以帮助你避免许多难以发现的bug。

核心定义

判断一个对象是可变的还是不可变的,标准只有一个:当对象创建之后,它里面的值能否被改变?

  • 不可变对象:对象创建后,其内容(或状态)无法被修改。如果你尝试修改它,Python实际上会创建一个新的对象,而不是在原有对象上修改。

  • 可变对象:对象创建后,其内容(或状态)可以被修改。你可以在原内存地址上直接修改对象的值,而不会创建新对象。


  • 1. 常见的不可变对象

    • 整数(int)

    • 浮点数(float)

    • 布尔值(bool)

    • 字符串(str)

    • 元组(tuple) —— 注意:元组本身不可变,但如果它包含可变对象(如列表),那个内部对象是可变的。

    • 冻结集合(frozenset)

    示例:看似"修改"了不可变对象

    name = "hello"
    print(f"原始对象的内存地址: {id(name)}") # id() 可以查看对象的内存地址

    name = name + " world"
    print(f"新对象的内存地址: {id(name)}")

    # 输出:
    # 原始对象的内存地址: 1399876543210 (示例地址)
    # 新对象的内存地址: 1399876545678 (地址变了!)

    发生了什么?
    当执行 name = name + " world" 时,Python并没有在原来的字符串 "hello" 后面添加内容。因为字符串是不可变的,它不能直接在原处修改。所以,Python在内存中创建了一个全新的字符串对象 "hello world",然后将变量 name 重新指向了这个新对象。原来的 "hello" 对象如果没有其他变量引用,最终会被垃圾回收。


    2. 常见的可变对象

    • 列表(list)

    • 字典(dict)

    • 集合(set)

    • 自定义类的实例对象(默认情况下)

    示例:修改可变对象

    my_list = [1, 2, 3]
    print(f"修改前列表的内存地址: {id(my_list)}")

    my_list.append(4) # 在列表末尾添加一个元素
    print(f"修改后列表的内容: {my_list}")
    print(f"修改后列表的内存地址: {id(my_list)}")

    # 输出:
    # 修改前列表的内存地址: 1399876547890 (示例地址)
    # 修改后列表的内容: [1, 2, 3, 4]
    # 修改后列表的内存地址: 1399876547890 (地址没变!)

    示例2:

    # 创建一个可变对象(列表)
    a = [1, 2, 3] # a 指向一个列表对象 [1, 2, 3]
    print(f"初始状态:")
    print(f"a = {a}, id(a) = {id(a)}")

    b = a # b 指向同一个列表对象
    print(f"\\n执行 b = a 后:")
    print(f"a = {a}, id(a) = {id(a)}")
    print(f"b = {b}, id(b) = {id(b)}")
    print(f"a is b? {a is b}") # True,确实是同一个对象

    # 情况1:通过 a 修改列表内容(不是重新赋值)
    a.append(4) # 修改可变对象的内容
    print(f"\\n执行 a.append(4) 后:")
    print(f"a = {a}, id(a) = {id(a)}")
    print(f"b = {b}, id(b) = {id(b)}")
    print(f"b 看到了变化!因为 a 和 b 指向同一个列表")

    # 情况2:重新给 a 赋值(让 a 指向新对象)
    a = [5, 6, 7] # a 现在指向一个新列表
    print(f"\\n执行 a = [5, 6, 7] 后:")
    print(f"a = {a}, id(a) = {id(a)}")
    print(f"b = {b}, id(b) = {id(b)}")
    print(f"b 仍然指向原来的列表,不受 a 重新赋值的影响")

    输出:

    初始状态:
    a = [1, 2, 3], id(a) = 1399876543210

    执行 b = a 后:
    a = [1, 2, 3], id(a) = 1399876543210
    b = [1, 2, 3], id(b) = 1399876543210
    a is b? True

    执行 a.append(4) 后:
    a = [1, 2, 3, 4], id(a) = 1399876543210
    b = [1, 2, 3, 4], id(b) = 1399876543210
    b 看到了变化!因为 a 和 b 指向同一个列表

    执行 a = [5, 6, 7] 后:
    a = [5, 6, 7], id(a) = 1399876545678
    b = [1, 2, 3, 4], id(b) = 1399876543210
    b 仍然指向原来的列表,不受 a 重新赋值的影响

    发生了什么?
    当我们调用 my_list.append(4) 时,Python直接在原有的列表对象上进行了修改。它没有创建新的列表,只是改变了原列表的内部状态。因此,列表的内存地址在修改前后是完全相同的。


    3. 为什么这个区别很重要?

    理解可变与不可变,对于编写正确的代码至关重要,尤其是在以下场景:

    a) 变量赋值和引用

    内存示意图:

    栈内存:
    a: [10] → a: [20] (a的值被直接修改)
    b: [10] b: [10] (b独立存储,不受影响)

    2. 对于引用类型(Reference Types)

    // 假设有一个 Person 类
    Person a = new Person("Alice"); // a 存储的是对象的引用(内存地址)
    Person b = a; // 把引用(地址)复制给 b
    a.setName("Bob"); // 通过 a 修改对象内容
    System.out.println(b.getName()); // 输出: "Bob" (b 指向同一个对象)

    a = new Person("Charlie"); // a 指向一个新对象
    System.out.println(b.getName()); // 输出: "Bob" (b 仍然指向原来的对象)

    b) 函数参数的传递

    Python的参数传递方式是"对象引用传递"。这意味着函数内部接收的是指向实际对象的引用。

    c) 作为字典的键

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

    这是一个需要特别注意的地方。元组本身是不可变的,这意味着你不能给元组添加或删除元素,也不能替换元组中的某个元素。但是,如果元组中包含了可变对象(如列表),那么这个可变对象的内容是可以被修改的。

    python

    t = (1, 2, [3, 4]) # 元组中包含一个列表
    print(t) # 输出: (1, 2, [3, 4])

    # t[0] = 10 # 错误!不能替换元组的元素,会报 TypeError
    # t.append(5) # 错误!元组没有 append 方法

    t[2][0] = 999 # 可以修改元组内部列表的元素
    print(t) # 输出: (1, 2, [999, 4]) (元组的内容看起来变了!)

    解释:
    虽然元组存储的引用本身不能变,但它并没有限制这些引用所指向的对象内部是否能变。这里,元组中第三个槽位始终指向同一个列表对象。我们只是修改了这个列表对象的内容,并没有改变元组中存储的引用。所以从元组的角度看,它依然是"不可变"的。

    总结

    特性不可变对象可变对象
    常见类型 int, float, str, tuple, frozenset list, dict, set, 自定义类实例
    修改操作 任何修改都会创建一个新对象 可以在原内存地址上直接修改内容
    内存地址 修改后内存地址会改变 修改后内存地址通常不变
    安全性 多个引用共享时安全,不会被意外修改 多个引用共享时需谨慎,一处修改处处可见
    作为字典键 可以 不可以
    函数传参 函数内修改不会影响外部变量 函数内修改会影响外部对象

    理解这个核心区别,是掌握Python内存模型和写出健壮代码的关键一步。

    赞(0)
    未经允许不得转载:171主机测评 » Python中可变对象与不可变对象
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址