python 传值 传引用 可变对象 不可变对象 的区别与联系

本文深入探讨Python中可变与不可变对象的概念及其对函数调用的影响,详细解释了对象的地址变化及如何实现不同类型的对象传值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

可变对象 与 不可变对象

我们知道在python中一切皆对象。在python世界中可以把对象大体分成两大类:

  • 不可变对象:数字(int,float, double)、字符串、元组(tuple)、function等
  • 可变对象:字典(dict)、列表(list)、集合(set)、程序自定义的对象

所谓不可变对象就是对象的内容不能改变。给人感觉有点像c++的const修饰的对象。但是我们可以对一个不可变对象进行赋值(写操作),这时候这个不可变的对象就指向了一个新的地址空间,指向的内容也更新成新赋值的内容

可变对象就是对对象本身的修改(写操作)是直接作用于对象自身的,不会产生新的对象

通过内置函数id()来获取变量的地址,下面举例说明:

num = 1
print "num addr:",id(num)
num += 2
print "num addr:",id(num)

num addr: 74024648
num addr: 74024624

执行num += 2给不可变对象num赋值的时候会产生一个新的地址空间,并用新的内容填充这块地址空间,正如上面的输出所示,此时的num的地址已经改变

下面再看一个

listnum = [1,23,2]
print "list addr:",id(listnum)
listnum.append(11)
print "list addr:",id(listnum)
print listnum

list addr: 79300608
list addr: 79300608
[1, 23, 2, 11]
list是可变对象,修改一个可换对象可直接作用于这个可变对象,所以最终的list就插入了一个元素

传值还是传引用

有了前面的可变对象与不可变对象的认识,我们在就很容易理解传值还是传引用的问题。但是先说个结论:python中不存在所谓的传值的函数调用,一切都是传递引用
我们知道函数传递的参数是进行从实参到形参的赋值操作进行的。在python中我们知道,不管是可变对象还是不可变对象,所有的赋值操作并没有产生实质性的东西,新赋值的对象和源对象的地址始终是一样的,也就是说两者在赋值操作完成后指向的是同一块内存。我们可以看下面的例子:
num = 1
num_bk = num
print "num addr:%d   num_bk addr:%d" %(id(num),id(num_bk))

listnum = [1,23,2]
listnum_bk = listnum
print "listnum addr:%d   listnum_bk addr:%d"%(id(listnum),id(listnum_bk))


num addr:77104840   num_bk addr:77104840
listnum addr:79104000   listnum_bk addr:79104000

可以看出不管是可变对象还是不可变对象,赋值后两个对象的地址是相同的。也证实了上面的结论。所以问题的关键就变成了我们对新赋值的对象进行修改是否会影响到原对象的值。这个问题似乎回到了上面的对可变对象和不可变对象的写操作的问题。

通过上面的讲解我们知道:

  • 对可变对象进行写操作是直接作用于写对象本身。
  • 对不可变对象进行写操作会产生一个新的地址空间,并用新的内容填充这块地址空间

到此我们应该很容易知道:

  • 当我们传递一个不可变对象的参数到函数中(相当于通过赋值给函数的形参),如果在函数中对这个对象进行修改(写操作)是不会改变原始对象的值,因为会产生一个新的对象,这样的调用给人感觉是传值的,但其实原理不一样。
  • 当我们传递一个可变对象到函数中,如果函数中对这个对象进行修改(写操作),这个是会影响到原来的对象的 看下面具体的例子:
def UpdateNum(num):
    print "begin update num addr:",id(num)
    num += 2
    print "after uodate num addr:",id(num)

num = 1
print "begin call function num add:",id(num)
UpdateNum(num)
print "after call function num add:",id(num)


begin call function num add: 77104840
begin update num addr: 77104840
after uodate num addr: 77104816
after call function num add: 77104840

如何实现可变对象的传值和不可变对象的传引用

这个问题这样表述可能有点问题,但是意思可能都明白。就是想实现,传入的是可变对象时,函数内部的修改不会影响到原来的对象;传入是不可变对象,函数内部的修改能够影响到原来的对象

对于后者的解决方案比较简单,就是函数通过返回值回传这个新的对象,调用方用原来的对象来接收这个新的对象就达到了这个效果。但是要实现可变对象的传值问题就要借助于复制的内置函数了。这个介绍两个拷贝函数:copy(浅拷贝)、deepcopy(深拷贝)


浅拷贝
浅拷贝不会拷贝子对象,只是拷贝了子对象的引用,下面举例说明:
listnum = [1,2,[11,22],3]
listcopy = copy.copy(listnum)

listcopy.append(4)
print "list value :", listnum
print "listcopy value:", listcopy

listnum[2].append(33)
print "list value :", listnum
print "listcopy value:", listcopy


list value : [1, 2, [11, 22], 3]
listcopy value: [1, 2, [11, 22], 3, 4]
list value : [1, 2, [11, 22, 33], 3]
listcopy value: [1, 2, [11, 22, 33], 3, 4]
可以看到我们子对象listnum[2]的修改会作用到复制的两个对象,而深拷贝就是解决这个问题的
深拷贝
相对于浅拷贝而言,深拷贝会连子对象的内存也会拷贝一份,对子对象的修改不会影响到源对象,还是刚才的例子
listnum = [1,2,[11,22],3]
listcopy = copy.deepcopy(listnum)

listcopy.append(4)
print "list value :", listnum
print "listcopy value:", listcopy

listnum[2].append(33)
print "list value :", listnum
print "listcopy value:", listcopy


list value : [1, 2, [11, 22], 3]
listcopy value: [1, 2, [11, 22], 3, 4]
list value : [1, 2, [11, 22, 33], 3]
listcopy value: [1, 2, [11, 22], 3, 4]

可以看到所有的修改都是独立的

熟悉c++的同学可能对深浅拷贝理解起来更容易。深浅拷贝的区别就是对指针成员对象进行的是仅仅指针的复制还是对指针所指示的内存空间进行复制。仅复制指针的话,由于两个指针同时指向同一块内存,所以修改是同步的






<think>我们正在讨论Python中的可变对象不可变对象。根据引用可变对象可以在不改变其内存地址的情况下修改内容,而不可变对象一旦创建,内容就不能修改,任何修改都会创建新对象。 实现原理方面,不可变对象(如整数、字符串、元组)在内存中创建后,其内容不可变。例如,字符串的replace方法会创建新字符串,而不是修改原字符串(引用[4][5])。可变对象(如列表、字典、集合)则可以在原内存地址上修改内容。 另外,引用[3]提到Python的参数递是“对象引用”,如果可变对象,函数内修改会影响原始对象;如果不可变对象,则函数内的修改不会影响原始对象,因为修改实际上是创建了新对象。 下面我们详细解释并举例说明。</think>### Python 可变不可变对象区别实现原理 #### **核心区别** | **特性** | **可变对象** (列表、字典、集合) | **不可变对象** (整数、字符串、元组) | |------------------|-----------------------------------|-----------------------------------| | **内存修改** | 可直接修改原内存地址的内容 | 修改会创建新对象,原对象不变 | | **身份(id)** | 修改后 id 不变 | 修改后 id 改变 | | **参数递** | 函数内修改会影响原始对象 | 函数内修改不影响原始对象 | | **典型操作** | `list.append()`, `dict.update()` | `str.replace()`, `int + 1` | #### **底层实现原理** 1. **不可变对象** - Python 在内存中为不可变对象分配固定空间,内容不可修改。 - 修改操作(如字符串拼接)会触发新对象创建: ```python a = "abc" # 内存地址 0x1000 b = a.replace("a", "A") # 创建新对象,地址变为 0x2000 ``` 此时 `a` 仍指向原内存地址(引用[4][5])。 2. **可变对象** - 对象内部保留动态扩展空间(如列表的预留缓冲区)。 - 修改操作直接更新原内存内容: ```python lst = [1, 2] # 地址 0x3000 lst.append(3) # 仍在 0x3000,内容变为 [1, 2, 3] ``` 3. **内存管理机制** - **小整数池**:Python 缓存常用整数(-5~256),避免重复创建(引用[1])。 - **字符串驻留**:短字符串或代码字面量会被复用,减少内存开销。 #### **参数递的本质** Python 采用 **"对象引用"** 方式: ```python def update(x): x += [4] # 修改可变对象,影响原始数据 def replace(s): s = "new" # 不可变对象,仅创建局部新对象 lst = [1]; s = "old" update(lst) # lst 变为 [1, 4] replace(s) # s 仍为 "old" ``` - 可变对象时,函数内修改会反映到原始对象(类似引用)。 - 不可变对象时,函数内操作创建新对象,原始对象不变(类似)(引用[3])。 #### **设计意义** - **不可变对象**:保证数据安全(如字典键必须是不可变类型)。 - **可变对象**:提高大对象操作效率(避免频繁内存分配)。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值