前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文读懂 Python 值传递和引用传递

一文读懂 Python 值传递和引用传递

原创
作者头像
Lorin 洛林
发布2024-02-26 21:05:51
85993
代码可运行
发布2024-02-26 21:05:51
举报
文章被收录于专栏:Python 技术小屋Python 技术小屋
运行总次数:3
代码可运行

版本

  • Python 3.9

前言

  • 在编程语言中,值传递(pass by value)和引用传递(pass by reference)是两个重要的概念。它们涉及到变量在函数调用中的传递方式,对于理解函数调用和参数传递的机制至关重要。在本文中,我们将深入探讨 Python 中的值传递和引用传递,并通过代码示例进行说明。

形参和实参

  • 我们先了解一点前置知识,形参和实参,先说概念:形参出现在函数定义中,在整个函数体内都可使用,离开函数体则不可使用。实参出现在主调函数中,进入被调函数后,不能使用。
代码语言:python
代码运行次数:2
复制
def func(param):
    # 这里 param 为形参
    print(param)


if __name__ == "__main__":
    # 这里的 a 就是实参
    a = 1
    func(a)

值传递和引用传递

  • 我们先了解一下值传递和引用传递的概念:值传递是指在调用方式时,将实参的值拷贝一份给形参,对形参的修改不影响实参。引用传递也叫地址传递,指在调用方法时将实参的地址传递给形参,对形参的修改将影响实参的值,即传递的是实参的内存地址。

Python 变量存储

  • 对于python而言,python的一切变量都是对象,变量的存储采用了引用语义的方式,存储的只是一个变量的值所在的内存地址,而不是这个变量的只本身。
Python 变量存储
Python 变量存储
  • 代码实测:
代码语言:python
代码运行次数:1
复制
a = 1
print(f"变量a的地址:{id(a)}")

b = 1
print(f"变量b的地址:{id(b)}")

b = 2
print(f"变量b的地址:{id(b)}")
  • 上面实例输出如下:
代码语言:txt
复制
变量a的地址:2483649669424
变量b的地址:2483649669424
变量b的地址:2483649669456
  • 从实际用例我们可以看出,a、b = 1 时,这里底层为了性能考虑指向相同的内存,当 b 发生改变时,发生写时复制,b 指向了新的内存地址。

值语义和引用语义

值语义

  • 值语义是指将变量赋值为另一个变量时,会复制变量的值,而不是引用原始值所在的内存地址。如 Java 的基本数据类型。

引用语义

  • 引用语义是指将变量赋值为另一个变量时,实际上是将变量指向同一个对象的内存地址,而不是复制对象的值。如 Java 的引用数据类型、Python 值存储。

探讨 Python 值传递和引用传递

  • 了解完上面的一些基本概念后,我们从可变(mutable)和不可变(immutable)两种类型来探讨 Python 值传递和引用传递:

不可变(immutable)类型

  • 不可变变量的值一旦创建,就不能被修改。如果你尝试修改一个不可变对象的值,Python 将会创建一个新的对象。Python 中的不可变对象包括整数(int)、浮点数(float)、字符串(str)、元组(tuple)等。
  • 先简单看一个下面的例子:
代码语言:python
代码运行次数:0
复制
def modify_value(x):
    print(f"变量x修改前地址:{id(x)}")
    x = x + 10
    print(f"变量x修改后地址:{id(x)}")
    print("函数内部修改后的值为:", x)


# 调用函数
value = 5
print(f"变量value地址:{id(value)}")
modify_value(value)
print("函数外部原始值为:", value)
  • 在这个示例中,我们定义了一个函数 modify_value,它接受一个参数 x。在函数内部,我们对 x 的值进行修改,并打印出修改后的值。然后我们调用函数,传递了一个值为 5 的参数 value。运行以上代码,将会输出:
代码语言:txt
复制
变量value地址:1886976960944
变量x修改前地址:1886976960944
变量x修改后地址:1886976961264
函数内部修改后的值为: 15
函数外部原始值为: 5
  • 可以看到,尽管在函数内部修改了形式参数 x 的值,但并没有影响到函数外部实际参数 value 的值,而发生了写时复制。

可变(mutable)类型

  • 可变变量的值可以在原地修改,而不会创建一个新的对象。Python 中的可变对象包括列表(list)、字典(dict)、集合(set)等。
  • 我们以 list 类型为例:

案例一

代码语言:python
代码运行次数:0
复制
def modify_list(list):
    print(f"变量list地址:{id(list)}")
    list[2] = 4
    print(f"变量list修改地址:{id(list)}")
    print("函数内部修改后的列表为:", list)


# 调用函数
my_list = [1, 2, 3]
print(f"变量my_list地址:{id(my_list)}")
modify_list(my_list)
print("函数外部原始列表为:", my_list)
  • 输出如下:
代码语言:txt
复制
变量my_list地址:2115249727936
变量list地址:2115249727936
变量list修改地址:2115249727936
函数内部修改后的列表为: [1, 2, 4]
函数外部原始列表为: [1, 2, 4]

案例二

代码语言:python
代码运行次数:0
复制
def modify_list(list):
    print(f"变量list地址:{id(list)}")
    list = [6, 6, 6]
    print(f"变量list修改地址:{id(list)}")
    print("函数内部修改后的列表为:", list)


# 调用函数
my_list = [1, 2, 3]
print(f"变量my_list地址:{id(my_list)}")
modify_list(my_list)
print("函数外部原始列表为:", my_list)
  • 输出如下:
代码语言:txt
复制
变量my_list地址:2141908331136
变量list地址:2141908331136
变量list修改地址:2141908181248
函数内部修改后的列表为: [6, 6, 6]
函数外部原始列表为: [1, 2, 3]
  • 看完上面的两个案例你是否有些许疑惑,案例一修改了函数外的原始值,案例二未修改函数外的原始值,下面我们用图解来解释一下上面发生了什么:
案例一
案例一
案例二
案例二
  • 从图解中我们可以清晰的看到,在案例一和案例二中函数传递了 my_list 地址的拷贝值,案例一中持有数组的内存地址,因此成功修改了原数组元素,案例二中 list 的内存地址修改为新的数组内存地址,并没有修改原数组的值。
  • 通过对可变(mutable)和不可变(immutable)两种类型的函数传递的分析,我们可以知道由于 Python 中一切皆对象的特性,实际传递给函数的都是内存地址的拷贝,从表现上来说,我们可以说 Python 中都是值传递,了解过 Java 的同学会发现这里和 Java 的引用类型原理一致。

拓展:不可变类型真的不可变?

  • 上面我们提到了可变类型和不可变类型,不可变类型真的是不可变的?我们来看下面的案例:
代码语言:python
代码运行次数:0
复制
arr = (1, 2, [4, 4])

print(f"元组修改前:{arr}")
arr[2][0] = 2
print(f"元组修改后:{arr}")
  • 输出结果:
代码语言:txt
复制
元组修改前:(1, 2, [4, 4])
元组修改后:(1, 2, [2, 4])
  • 上面的案例中不可变类型出现了变化,这个是 bug ? 其实并不是,不可变类型的不可变指的是组成的元素是不可变的,在上面的案例 arr 元组中存储的是对应的内存地址,而不可变指的是内存地址和指向无法改变,但如果内存地址指向的是可变类型,比如数组,那么元素内部是可变的。

总结

  • 本文以值传递、引用传递的基本概念、以及 Python 变量存储为基础,从可变(mutable)和不可变(immutable)两种类型来分析 Python 值传递和引用传递的真相,通过充足的案例分析我们发现,Python变量 和 Java 引用类型类似,只存在值传递。

个人简介

? 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.

? 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。

? 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。

? 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。

? 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

? 保持关注我的博客,让我们共同追求技术卓越。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 版本
  • 前言
  • 形参和实参
  • 值传递和引用传递
  • Python 变量存储
    • 值语义和引用语义
      • 值语义
      • 引用语义
  • 探讨 Python 值传递和引用传递
    • 不可变(immutable)类型
      • 可变(mutable)类型
        • 案例一
        • 案例二
    • 拓展:不可变类型真的不可变?
    • 总结
    • 个人简介
    相关产品与服务
    云数据库 MySQL
    腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
    http://www.vxiaotou.com