Python 的函数传参传递的到底是不是引用?

C++ 中的引用

在C++中,函数定义时,参数的传递可以定义成引用形式,比如下面这样:最后结果打印出来是10

void f(int &a) {
    a = 10;
}

int main()
{
    int a = 1;
    f(a);
    printf("%d\n", a);
    return 0;
}

这就是引用的好处,当我们想要在函数里面修改a的值的时候,我们就可以使用引用,还有一种办法就是将参数定义成指针的形式,比如下面这样:同样可以得到结果是10

void f(int *a) {
    *a = 10;
}

int main()
{
    int a = 1;
    f(&a);
    printf("%d\n", a);
    return 0;
}

但是如果我们不使用指针的形式,参数直接是int类型的话,函数传递进来的实参其其实是一份拷贝,修改这份拷贝,原来的实参是不会发生变化的,比如下面这样: 最后的结果是1

void f(int a) {
    a = 10;
}

int main()
{
    int a = 1;
    f(a);
    printf("%d\n", a);
    return 0;
}

这就是c++中引用的好处,为什么会有引用,虽然用指针也能实现同样的效果,但是我觉得是因为指针的操作太复杂,引用的话,直接在参数上加个&就搞定了,引用帮助我们省了很多事,同时使用引用的话,还有一个好处就是,可以节省内存空间,并且减少开辟新内存需要花费的时间,如果参数我们用的不是引用,系统需要拷贝一份值,这个过程需要消耗时间和内存空间。

 

python中的引用

上面讨论了c++中的引用,那么python中的参数传递到底是引用还是值的拷贝?

下面看个例子

def f(a, b):
    print('a-id in f() = ', id(a))
    print('b-id in f() = ', id(b))
    a = 10
    print("当a被赋予一个新的值的时候")
    print('a-id in f() = ', id(a))
    a = b
    print('a-id in f() = ', id(a))
    print('a = ', a)

a = 1
b = 2
print("函数外面")
print('id-a = ', id(a))
print('id-b = ', id(b))
print("进入到函数里面")
f(a, b)
print("函数结束后")
print('a = ',a)
print('a-id out of f() = ', id(a))

# 结果
# 函数外面
# id-a =  4346719136
# id-b =  4346719168
# 进入到函数里面
# a-id in f() =  4346719136
# b-id in f() =  4346719168
# 当a被赋予一个新的值的时候
# a-id in f() =  4346719424
# a-id in f() =  4346719168
# a =  2
# 函数结束后
# a =  1
# a-id out of f() =  4346719136

在这个例子中我们看到了python传递的确实是引用,因为a在函数外面和函数里面的id是一样的,但是下面代码的执行结果就不按照我们想的那样走了,如果跟c++一样的引用的话,我们修改a的值,函数外面a的值也会发生变化,可是我们通过代码的运行结果可以知道,尽管在函数里面我们对a进行了赋值操作,进行a=10,a=b这两个操作,但是当函数运行结束后,函数外a的值没有发生变化,id也没有发生变化,是不是感觉很奇怪。我们往函数里面看,当我们给a赋值10,a的id发生了变化,当我们把b赋值给a的时候,a的id又发生了变化,变成了b的id。

从上面的例子中我们看到,虽然python传递的参数都是以引用的方式,但是引用的形式跟c++还是有区别的。其实python传递进去的参数都是以引用的形式,但是当参数是不可变类型,比如字符,数字,元祖,tuple的时候,当我们去对它进行赋值操作的时候,python会重新创建一个空间,这个空间的地址重新赋值给当前变量,也就是说在函数里面我们一开始a=1的时候,a指向的是在内存空间中1这个数据的存放地址,当给a=10的时候,python又在内存中开辟了一块空间这块空间放数据10,而数据10的地址就又给了a,所以这时候a的id发生了变化。

上面我们看了参数是不可变的情况,那么当参数是可变的呢,比如list, 类?我们看下面的例子:

def f(a, b):
    print('a-id in f() = ', id(a))
    print('b-id in f() = ', id(b))

    a.append(10)
    print("\n当往a里面追加一个元素的时候")
    print('a-id in f() = ', id(a))
    print('a = ', a)

    print('\n当把b赋值给a')
    a = b
    print('a-id in f() = ', id(a))
    print('a = ', a)

a = [1,2]
b = [4,5]
print("函数外面")
print('id-a = ', id(a))
print('id-b = ', id(b))
print("\n进入到函数里面")
f(a, b)
print("\n函数结束后")
print('a = ',a)
print('a-id out of f() = ', id(a))

# 结果
# 函数外面
# id-a =  4478374368
# id-b =  4478376928
# 
# 进入到函数里面
# a-id in f() =  4478374368
# b-id in f() =  4478376928
# 
# 当往a里面追加一个元素的时候
# a-id in f() =  4478374368
# a =  [1, 2, 10]
# 
# 当把b赋值给a
# a-id in f() =  4478376928
# a =  [4, 5]
# 
# 函数结束后
# a =  [1, 2, 10]
# a-id out of f() =  4478374368

从这个例子中我们看到函数当传递进去的参数是可变对象的时候,参数依然是引用的形式,但是当我们当往a里面添加一个元素后,a的id没有发生变化,最后在函数外面a也发生了变化,变成了[1,2,10]。但是当a=b的时候,a并没有变成[4,5], a的id变成了b的id。

总结一下,可能描述的并不是python内部的具体实现,还望看到python底层实现的可以指导一下,下面只是自己的一些总结,帮助理解:

python中一个变量赋值给另一个变量,比如a=2,a=b,b=1, 这时候python重新创建了一个标签,这个标签指向了b指向的内存空间,a和b都是标签,这时候a和b都是指向内存空间中存放1的地址,通过a和b都能够找到1这个数据,但是内存中存放2的那部分空间依然存在。python传递的参数都是引用,可变对象与不可变对象有区别。另外,无论是传递的参数是可变对象还是不可变对象,对象之间的赋值,会销毁原来的标签,创建一个新的标签,这个标签指向赋值对象所指的内存空间,比如a=b, python新生成了一个标签,这个标签的指向b所指的内存空间。

这里突发奇想,python虽然参数传递都是引用的形式,但是更进一步其实是引用的拷贝?如何上面的那个例子,函数外面有个a,函数里面也有个a,在函数里面a=b后,a本身这个名字没有发生变化,只是a的内容发生了变化,函数外面的a指向的内容并没有发生变化。这里存在了两个a,一开始他们俩指向的内存地址是一样的,但是经过a=b后,他俩指向的内存地址发生了变化。就好像是你打印了两张纸,纸a和纸b,两张纸是一模一样的,纸a是函数外面那个参数,纸b是函数里面的那个参数,这两张纸上面都写了一个同一个内存地址,后面你修改了纸b上面的内容,通过纸b能找到的内存地址发生了变化,但是纸a没有发生变化。

 

c++ 下面这样写可以:

void f(int &a, int &b) {
    a = b;
}

int main()
{
    int a = 1, b=2;
    f(a, b);
    printf("%d\n", a);
    return 0;
}

// 结果是2

但是python这样写,得到的还是原来的值

def f(a, b):
    a = b

a = [1,2]
b = [4,5]
print(a)

# 结果
# [1,2]

 

Python里面所有变量存放的都是指向内存空间的地址

比如 a=1,b=a,这里a存放了内存中存放数据1的地址,把a赋值给b,并不是把a的地址存放在了b里面,而是把b里面存放的数据的地址给了a,下面用图展示一下:

 

下面这段代码也展示了这种情况:

def f(a, b):
    a = b
    b = 10
    print('a = ', a)
    print('b = ', b)

a = 1
b = 2
f(a, b)

# 结果
# a =  2
# b =  10

 

 

最后,希望这篇博客能帮助到你,另外博主并没有深入到python底层去看python的实现方式,这些只是博主自己通过测试代码以及查看其它博主的博客总结出来的东西,如果有错误,还希望评论区帮忙指正,谢谢~

 

附上参考博客的链接:

知乎

廖雪峰python教程,也是博主python的启蒙教程

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值