python函数传参是传值还是传引用?

本文详细探讨了Python中函数参数传递的机制,通过实例解释了值传递和引用传递的区别。在Python中,所有数据都是对象,参数传递实际上是对象的引用。对于不可变对象如整数,函数内部的操作不会影响原始对象;而对于可变对象如列表,函数内部的修改会影响到原始对象。文章还通过代码展示了如何判断和操作这些对象。
摘要由CSDN通过智能技术生成

1. 引子

在这里插入图片描述

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode()
        p = dummy
        while list1 != None and list2 != None:
            if list1.val < list2.val:
                p.next = list1
                list1 = list1.next
                p = p.next
            elif list2.val <= list1.val:
                p.next = list2
                list2 = list2.next
                p = p.next
        if list1 != None:
            p.next = list1
        if list2 != None:
            p.next = list2
        return dummy.next 

今天在刷一道leetcode,很简单用一个双指针和dummy node 的trick就可以很快的解决。但是做完之后突然细思恐极,这个list1,和list2在函数里被我各种 .next 操作会不会变了。赶紧查一下python的函数是怎么传值和传引用的

2. 理解概念

值传递(passl-by-value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。

引用传递(pass-by-reference)过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

其实区别就是值传递相当于考试的时候,场外的学霸看到了你的题,然后打印一份一样的,帮你做完再把答案发给你。

引用传递相当于学霸知道了你在考场坐在哪,然后直接替考。

3. python中使用

Python程序中存储的所有数据都是对象,每一个对象有一个身份,一个类型和一个值。
看变量的实际作用,执行a = 8 这行代码时,就会创建一个值为8,类型是int的对象。
变量名是对这个"一个值为8的int对象"的引用。

  1. 可以通过id() 来取得对象的身份
    这个内置函数,它的参数是a这个变量名,这个函数返回的值是这个变量a引用的那个"一个值为8的int对象"的内存地址。
a = 8
print(id(a), id(8))
# 140711661084640 140711661084640
  1. 可以通过type()来取得a引用对象的数据类型
type(a)
# int
  1. 当变量出现在表达式中,它会被它引用的对象的值替代。
  2. Python函数传递参数到底是传值还是引用?

传值、引用这个是c/c++中的概念,Python中一切都是对象,实参向形参传递的是对象的引用值。就像Python赋值的意思。

如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值--相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象--相当于通过“传值’来传递对象。

举个例子

  1. 传值
def test(c):
    print('test before + :', id(c))
    c += 2
    print('test after + :', id(c))
    return c
if __name__ == '__main__':
    a = 2
    print('main before invoke test:', id(a))
    a_return = test(a)
    print('main after invoke test: ',id(a))
    print('return id:', id(a_return))
    
# main before invoke test: 140711661084448
# test before + : 140711661084448
# test after + : 140711661084512
# main after invoke test:  140711661084448
# return id: 140711661084512
  1. 传引用
    在下面这个例子里没有修改c, 而只是修改了 c[0]其实是c.get(0),而这时候对c[0] 的修改就会直接再内存上修改
def test(c):
    print('test before + :', id(c),id(c[0]))
    c[0] += 10
    print('test after + :', id(c),id(c[0]))
    return c
if __name__ == '__main__':
    a = [0,0,0]
    print('main before invoke test:', id(a),id(a[0]))
    a_return = test(a)
    print('main after invoke test: ',id(a),id(a[0]))
    print('return id:', id(a_return),id(a_return[0]))
# main before invoke test: 1911175809216 140711661084384
# test before + : 1911175809216 140711661084384
# test after + : 1911175809216 140711661084704
# main after invoke test:  1911175809216 140711661084704
# return id: 1911175809216 140711661084704

如果我们接着直接修改 c 试一下, 发现和传值是一样的,上文c+=2和这里c = c+[1]中左侧的c都是另一个id的引用了,相当于又开辟了一块新的内存,所以有人总说c = c+[1]很慢

def test(c):
    print('test before + :', id(c),id(c[0]))
    c[0] += 10
    c = c+[1] #注意
    print('test after + :', id(c),id(c[0]))
    return c
if __name__ == '__main__':
    a = [0,0,0]
    print('main before invoke test:', id(a),id(a[0]))
    a_return = test(a)
    print('main after invoke test: ',id(a),id(a[0]))
    print('return id:', id(a_return),id(a_return[0]))
# main before invoke test: 1911209370432 140711661084384
# test before + : 1911209370432 140711661084384
# test after + : 1911209103488 140711661084704 #注意
# main after invoke test:  1911209370432 140711661084704
# return id: 1911209103488 140711661084704 #注意

当然了我们一般不用c = c+[1],我们一般会用从c+=[1],或者c.append(1), 这样会发生什么呢,发现相当于在原来的c上修改并没有用新的c,所以有人说这样要快一点

def test(c):
    print('test before + :', id(c),id(c[0]))
    c[0] += 10
    c+=[1]
    print('test after + :', id(c),id(c[0]))
    return c
if __name__ == '__main__':
    a = [0,0,0]
    print('main before invoke test:', id(a),id(a[0]))
    a_return = test(a)
    print('main after invoke test: ',id(a),id(a[0]))
    print('return id:', id(a_return),id(a_return[0]))
# main before invoke test: 1911209010432 140711661084384
# test before + : 1911209010432 140711661084384
# test after + : 1911209010432 140711661084704
# main after invoke test:  1911209010432 140711661084704
# return id: 1911209010432 140711661084704

至于引子里的例子,list1和list2都相当于传值,类似于复制一份,然后处理复印件,(传引用的第二段代码,或者传值的代码)。然而如果对list1.val进行修改,当跳出函数时,这个修改仍然存在,相当于传引用(传引用的第一段代码)。

本文借鉴了

https://www.cnblogs.com/loleina/p/5276918.html
https://zhuanlan.zhihu.com/p/32828289

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值