Python中的值传递和引用传递是怎样的?

Python采用“传对象引用”机制,即传递对象引用的副本。对于不可变对象(如整数、字符串),函数内部修改会创建新对象,不影响外部变量;对于可变对象(如列表、字典),函数内部的就地修改会影响外部对象,但重新绑定则不影响。因此,理解可变与不可变对象的行为差异是掌握Python参数传递的关键。

python中的值传递和引用传递是怎样的?

Python中的“值传递”和“引用传递”并不是像C++或Java那样泾渭分明的概念。实际上,Python采用的是一种被称为“传对象引用”(pass-by-object-reference)的机制。这意味着当你将一个参数传递给函数时,实际上是将一个指向该对象的引用(或者说,是该引用的一个副本)传递给了函数。函数内部的参数名会成为原始对象的一个新别名。这个核心点,是理解Python参数传递的关键。

解决方案

要深入理解Python的参数传递,我们得从它的核心机制——“传对象引用”说起。想象一下,Python里的所有东西都是对象,变量名就像是贴在这些对象上的标签。当你把一个变量传给函数时,你并不是把标签本身(变量名)传过去,也不是把标签指向的对象复制一份传过去(除非你显式地做了复制),而是给函数内部的参数也贴上一个一模一样的标签,让它也指向同一个对象。

这个机制带来的实际效果,就取决于你传递的这个对象是“可变”的(mutable)还是“不可变”的(immutable)。

不可变对象(如整数、浮点数、字符串、元组):当你将一个不可变对象传递给函数,并在函数内部尝试“修改”它时,实际上发生的是对函数内部那个新标签的“重新绑定”。也就是说,函数内部的参数名会指向一个新的对象,而外部的原始对象丝毫不受影响。

立即学习“Python免费学习笔记(深入)”;

def modify_immutable(num):    print(f"函数内部:原始num的ID是 {id(num)}")    num = num + 10  # 这里不是修改原始num,而是将num这个局部变量重新绑定到一个新对象    print(f"函数内部:修改后num的ID是 {id(num)}")    print(f"函数内部:num的值是 {num}")my_number = 5print(f"函数外部:调用前my_number的ID是 {id(my_number)}")modify_immutable(my_number)print(f"函数外部:调用后my_number的值是 {my_number}")print(f"函数外部:调用后my_number的ID是 {id(my_number)}")

你会发现,

my_number

id

在函数调用前后没有变,值也没有变。函数内部的

num

在被重新赋值后,它的

id

变了,说明它指向了一个全新的整数对象。

可变对象(如列表、字典、集合):当你将一个可变对象传递给函数,并在函数内部通过这个新标签对对象进行“就地修改”(in-place modification,比如列表的

append()

pop()

,字典的

update()

),那么这些修改会直接反映在函数外部的原始对象上,因为它们指向的是同一个对象。

def modify_mutable(my_list):    print(f"函数内部:原始my_list的ID是 {id(my_list)}")    my_list.append(4)  # 就地修改,原始列表会受影响    print(f"函数内部:修改后my_list的ID是 {id(my_list)}")    print(f"函数内部:my_list的值是 {my_list}")my_data = [1, 2, 3]print(f"函数外部:调用前my_data的ID是 {id(my_data)}")modify_mutable(my_data)print(f"函数外部:调用后my_data的值是 {my_data}")print(f"函数外部:调用后my_data的ID是 {id(my_data)}")

在这里,

my_data

id

在函数调用前后是相同的,而且它的值也被修改了。

但如果函数内部对可变对象参数进行“重新绑定”,那效果就和不可变对象一样了:

def rebind_mutable(my_list):    print(f"函数内部:原始my_list的ID是 {id(my_list)}")    my_list = [5, 6, 7]  # 重新绑定,my_list指向了一个新列表对象    print(f"函数内部:重新绑定后my_list的ID是 {id(my_list)}")    print(f"函数内部:my_list的值是 {my_list}")my_data_rebind = [1, 2, 3]print(f"函数外部:调用前my_data_rebind的ID是 {id(my_data_rebind)}")rebind_mutable(my_data_rebind)print(f"函数外部:调用后my_data_rebind的值是 {my_data_rebind}")print(f"函数外部:调用后my_data_rebind的ID是 {id(my_data_rebind)}")

这里,

my_data_rebind

id

和值在函数调用后都没有改变,因为函数内部的

my_list

只是被重新绑定到了一个全新的列表对象,并没有影响到外部的

my_data_rebind

所指向的原始对象。

所以,与其纠结于传统的“值传递”和“引用传递”哪个更贴切,不如直接理解Python的“传对象引用”模型,并区分可变对象和不可变对象在函数内部行为上的差异。这能帮你避免很多潜在的bug。

Python参数传递机制:为什么它不是简单的传值或传引用?

从我个人的经验来看,很多人初学Python时都会被这个问题困扰,因为它不像C++那样有指针和引用,也不像Java那样对原始类型和对象类型有明确区分。Python的这种“传对象引用”机制,其实是一种更高级的抽象。

传统意义上的“传值”(pass-by-value)意味着函数接收的是参数值的一个副本。函数对这个副本的任何修改都不会影响到原始值。比如在C语言里,你把一个

int

传给函数,函数内部改了这个

int

,外部的变量是不会变的。

而“传引用”(pass-by-reference)则意味着函数接收的是参数的内存地址(或者说,是对原始变量的一个直接引用)。函数内部对这个引用的操作,会直接作用到原始变量上。C++的引用和指针可以实现这种效果。

Python的“传对象引用”介于两者之间,但又有所不同。它传递的是对象引用的一个副本。这个副本和原始引用都指向内存中的同一个对象。

如果这个对象是不可变的,那么你不能“改变”它,只能让引用指向另一个新的对象。当你在函数内部对参数进行赋值操作时,你实际上是让函数内部的局部引用指向了一个新对象,这不影响外部的原始引用。如果这个对象是可变的,你可以通过这个副本引用直接修改对象的内容(比如往列表里添加元素)。这些修改会影响到外部通过原始引用访问到的对象。

这种设计哲学,我认为体现了Python的“一切皆对象”原则。变量名只是对象的标签,而函数参数则是这些标签的临时副本,同样指向同一个对象。理解这一点,就能拨开迷雾,看清Python参数传递的本质。

可变对象与不可变对象在函数参数传递中的行为差异

正是这种可变性(mutability)的差异,导致了我们经常遇到的“奇怪”行为。对我来说,这不仅仅是语法规则,更是一种编程思维的考量。什么时候我需要一个函数修改传入的数据,什么时候我希望它保持数据的纯洁性?Python的机制直接影响了我的设计选择。

不可变对象

特性:一旦创建,其值就不能改变。每次“修改”都会创建一个新对象。传递行为:函数内部对参数的赋值操作,只会重新绑定函数内部的局部变量名,使其指向一个新的对象。外部的原始变量保持不变。这使得处理不可变对象时,你通常不需要担心函数会意外地修改你的原始数据。例子

int

,

float

,

str

,

tuple

,

frozenset

实际应用:当你需要一个函数对数字进行计算,或对字符串进行处理并返回结果时,你通常会返回一个新的值,而不是修改传入的原始值。这符合函数式编程中“纯函数”的理念,即不产生副作用。

可变对象

特性:创建后,其内容可以被修改。传递行为:函数内部可以直接通过参数修改对象的内部状态(例如,

list.append()

,

dict.update()

)。这些修改会直接反映到函数外部的原始对象上,因为它们共享同一个内存地址。然而,如果函数内部对参数进行重新赋值(

param = new_object

),则只会重新绑定函数内部的局部变量,不会影响外部。例子

list

,

dict

,

set

实际应用:这既是便利,也是陷阱。便利在于,你可以通过函数直接更新一个大的数据结构,避免不必要的复制和返回。陷阱在于,如果你不小心,可能会在函数内部对传入的列表或字典进行了不希望的修改,导致难以追踪的bug。比如,一个函数本意是读取配置,结果却不小心改了全局配置字典,那就麻烦了。

我的经验是,当你处理可变对象时,尤其要小心。我常常会问自己:这个函数是应该修改传入的数据,还是应该返回一个修改后的新数据?如果答案是后者,我就会采取一些防御性编程措施。

如何避免或控制函数参数传递带来的意外副作用?

作为一名开发者,我深知这种副作用的潜在危害。调试一个因为函数意外修改了外部数据而产生的bug,往往比编写功能本身要耗时得多。所以,我总结了一些实践方法来应对。

明确函数意图:在设计函数时,首先要明确它的职责。这个函数是用来修改传入数据的(in-place modification),还是仅仅读取数据并返回一个新结果?在函数文档字符串(docstring)中清晰地说明这一点,对团队协作和未来的维护至关重要。

防御性复制可变对象:如果你传入的是一个可变对象,但你不希望函数修改原始数据,那么在函数内部或调用函数时,显式地创建一份副本。

在函数内部复制

def process_data(data_list):    local_list = list(data_list)  # 创建列表副本    # 或者 local_list = data_list[:]    local_list.append('processed')    return local_listmy_original_list = ['raw']new_list = process_data(my_original_list)print(my_original_list) # ['raw'] - 原始列表未变print(new_list)        # ['raw', 'processed']

在调用时复制

def process_data_no_copy_inside(data_list):    data_list.append('processed') # 直接修改传入的列表    return data_listmy_original_list = ['raw']# 传入副本processed_list = process_data_no_copy_inside(list(my_original_list))print(my_original_list) # ['raw']print(processed_list)   # ['raw', 'processed']

选择哪种方式取决于你的设计偏好。如果函数总是需要一个独立的副本,那么在函数内部复制更合理;如果偶尔需要,在调用时复制更灵活。

返回新对象而非就地修改:对于许多操作,尤其是涉及到数据转换或过滤时,我更倾向于让函数返回一个全新的、修改后的对象,而不是直接修改传入的对象。这使得函数更“纯粹”,更容易理解和测试。

def filter_even_numbers(numbers):    return [num for num in numbers if num % 2 == 0]original_numbers = [1, 2, 3, 4, 5]even_numbers = filter_even_numbers(original_numbers)print(original_numbers) # [1, 2, 3, 4, 5] - 原始列表未变print(even_numbers)    # [2, 4]

警惕默认可变参数:这是一个Python新手常踩的坑。在函数定义中,如果使用可变对象作为默认参数,那么这个默认对象只会在函数定义时创建一次,后续每次调用函数且不传入该参数时,都会使用同一个对象。

def add_item(item, item_list=[]): # 错误示范!item_list是可变默认参数    item_list.append(item)    return item_listprint(add_item(1)) # [1]print(add_item(2)) # [1, 2] - 意料之外!print(add_item(3, [])) # [3] - 传入新列表时正常

正确的做法是使用

None

作为默认值,然后在函数内部检查并创建新的可变对象:

def add_item_correct(item, item_list=None):    if item_list is None:        item_list = [] # 每次调用都创建一个新的列表    item_list.append(item)    return item_listprint(add_item_correct(1)) # [1]print(add_item_correct(2)) # [2] - 正常了

类型提示(Type Hinting):虽然不能直接阻止副作用,但良好的类型提示可以帮助开发者更好地理解函数预期的输入和输出,以及它是否可能修改传入的数据结构。例如,

def process(data: list) -> list:

可能会暗示返回一个新列表,而

def update(data: list) -> None:

则可能暗示就地修改。

这些方法并非孤立,而是相互配合,共同构建健壮、可维护的代码。在Python的世界里,理解“传对象引用”的细微之处,是迈向高级编程的重要一步。

以上就是Python中的值传递和引用传递是怎样的?的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1370388.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
SQLAlchemy模型跨文件关系管理指南
上一篇 2025年12月14日 10:30:08
大规模数据抓取时的性能优化与去重
下一篇 2025年12月14日 10:30:19

相关推荐

  • VSCode如何通过调试变量监视列表批量追踪数据变化 VSCode变量监视列表批量追踪的新颖技巧​

    VSCode如何通过调试变量监视列表批量追踪数据变化 VSCode变量监视列表批量追踪的新颖技巧​VSCode如何通过调试变量监视列表批量追踪数据变化 VSCode变量监视列表批量追踪的新颖技巧​VSCode如何通过调试变量监视列表批量追踪数据变化 VSCode变量监视列表批量追踪的新颖技巧​VSCode如何通过调试变量监视列表批量追踪数据变化 VSCode变量监视列表批量追踪的新颖技巧​

    vscode中高效批量追踪数据变化的关键是将监视列表用作表达式求值器,而非仅添加单一变量;2. 可在监视列表中添加复杂对象路径(如user.profile.address.city)、计算表达式(如(a + b) * c)、函数调用(如calculatetotal(items))或条件判断(如myv…

    2026年9月22日 用户投稿
    000
  • 在Java中如何统计List中元素出现次数

    答案是使用Map或Stream API统计List元素频次最高效。通过HashMap手动遍历统计,或用Java 8的Stream结合groupingBy和counting()实现简洁计数,Collections.frequency适用于小数据量但性能较差,推荐Stream方式兼顾性能与可读性。 在J…

    2026年9月22日
    900
  • mysql安装完如何诊断 mysql慢查询分析与优化方法

    要解决 mysql 慢查询问题,首先要开启慢查询日志,其次使用 mysqldumpslow 分析日志,再通过 explain 查看执行计划,最后根据常见优化建议改进 sql 和索引。具体步骤如下:一、修改配置文件或动态开启慢查询日志,并设置阈值和路径;二、使用 mysqldumpslow 工具分析慢…

    2026年9月22日
    100
  • Java中如何区分逻辑错误和系统异常

    系统异常是程序运行中由JVM抛出的RuntimeException,如空指针、数组越界,会导致程序中断并打印堆栈;逻辑错误是程序语法正确但结果不符预期,如条件写反、循环次数错误,不会崩溃但行为异常。两者区别在于是否抛出异常、是否中断执行及调试方式不同,需通过防御性编程、单元测试和日志调试加以防范。 …

    2026年9月22日
    000
  • 抖音小店如何运营?普通人开店选品与推广的实用策略

    抖音小店如何运营?普通人开店选品与推广的实用策略抖音小店如何运营?普通人开店选品与推广的实用策略抖音小店如何运营?普通人开店选品与推广的实用策略抖音小店如何运营?普通人开店选品与推广的实用策略

    新手做抖音小店最现实的问题是没钱投广告和没专业团队,解决方法是抓住选品和推广两个核心环节。一、选品要找市场需求高且利润合理的商品,避开竞争激烈或太冷门的品类,结合多平台数据测试;二、前期重点用“商品卡”推广,通过短视频展示产品使用场景并挂链接引流,成本低且适合测试;三、适当尝试直播积累经验,但不依赖…

    2026年9月22日 用户投稿
    400
  • Spring Boot 应用中的单元测试、Mockito 和集成测试:最佳实践

    第一段引用上面的摘要: 本文旨在帮助初学者理解在 Spring Boot 应用中何时以及如何使用 JUnit、Mockito 和集成测试。我们将探讨这些测试框架在 Controller、Service 和 Repository 层中的应用,并提供示例说明何时使用 Mockito 模拟对象,以及何时使…

    2026年9月22日
    000
  • 如何查询命令所属包 yum provides反向查找

    如何查询命令所属包 yum provides反向查找如何查询命令所属包 yum provides反向查找如何查询命令所属包 yum provides反向查找如何查询命令所属包 yum provides反向查找

    使用 yum provides 可以查找某个命令或文件属于哪个软件包,解决“command not found”问题。1. 使用时建议带上完整路径,如 yum provides /usr/sbin/ifconfig;2. 支持通配符模糊查找,如 yum provides */python3;3. 若…

    2026年9月22日 用户投稿
    000
  • Karate框架中处理带方括号和日期范围的GET请求参数

    本文旨在解决Karate框架中构建包含复杂、带方括号(如filters[start_date])及日期范围的GET请求参数时遇到的URL编码问题。通过对比直接定义查询对象和使用param关键字的方法,详细阐述了如何正确地构造URL,确保参数格式符合预期,从而有效进行API测试。 1. 问题背景与挑战…

    2026年9月22日
    000
  • vivoS系列手机微信收款语音播报怎么设置?配置语音的详细方法

    开启微信收款语音播报需在微信“收付款”中启用“收款语音提醒”并授权麦克风权限;2. vivo手机需在设置中开启微信的自启动、后台运行、通知及麦克风权限以确保功能正常;3. 语音播报延迟或无声可能由网络、手机性能、微信版本、系统模式或第三方软件干扰导致;4. 除微信自带功能外,还可选用第三方收款App…

    2026年9月22日
    600
  • 谷歌浏览器窗口透明边框显示异常如何修复

    首先尝试修改快捷方式添加–disable-gpu –disable-software-rasterize参数,若可正常运行则关闭硬件加速,并重置chrome://flags实验功能及清除ShaderCache缓存文件。 谷歌浏览器出现窗口透明边框显示异常,通常和硬件加速或GP…

    2026年9月22日
    000
  • VSCode配合Quartus开发FPGA(环境设置教程,提高开发效率)

    使用VSCode配合Quartus开发FPGA可提升效率,核心是结合VSCode的代码编辑功能与Quartus的编译仿真能力。首先安装Quartus、VSCode及Python,再安装VHDL/Verilog插件和Makefile Tools等扩展。配置系统环境变量,将Quartus命令路径加入PA…

    2026年9月22日
    100
  • 如何在Dask中训练AI大模型?分布式数据处理的AI训练技巧

    如何在Dask中训练AI大模型?分布式数据处理的AI训练技巧如何在Dask中训练AI大模型?分布式数据处理的AI训练技巧如何在Dask中训练AI大模型?分布式数据处理的AI训练技巧如何在Dask中训练AI大模型?分布式数据处理的AI训练技巧

    Dask在处理超大规模数据集时的独特优势在于其Python原生的分布式计算能力,能无缝扩展Pandas和NumPy的工作流,突破单机内存限制,实现高效的数据预处理与模型训练。它通过惰性计算、分块处理和内存溢写机制,支持TB级数据的并行操作,相比Spark提供了更贴近Python数据科学生态的API和…

    2026年9月22日 用户投稿
    100
  • 抖音小店网页版怎么登录?抖音我的小店在哪里

    随着抖音电商平台的快速发展,越来越多的商家选择入驻该平台。作为商家运营的重要工具之一,抖音小店网页版为店铺管理带来了诸多便利。那么,如何正确登录抖音小店网页版?又该如何找到“我的小店”?下面将为您详细介绍。 一、为什么需要登录抖音小店网页版? 通过抖音小店网页版,商家可以高效地进行商品管理、订单处理…

    2026年9月22日
    000
  • php-gd怎么应用复古滤镜_php-gd图像怀旧色调处理

    使用PHP-GD库实现复古滤镜主要通过色调偏移和色彩调整模拟老照片效果。1. 色调偏黄褐色:先转灰度,再用imagefilter添加棕黄色调;2. 手动像素级调整:逐像素计算灰度并赋予暖色系值,降低饱和度;3. 增强质感:结合对比度降低与轻微模糊提升真实感;4. 示例流程包括加载图像、应用滤镜、输出…

    2026年9月22日
    200
  • VSCode调试FPGA的UART通信(串口数据分析,调试技巧)

    使用VSCode调试FPGA的UART通信,核心是通过其扩展生态集成串口监视与数据分析。首先确保FPGA的UART模块正常工作并输出调试信息,然后在VSCode中安装“Serial Monitor”等串口扩展,配置波特率、端口号以捕获数据。为解析十六进制或自定义协议数据,可结合Python脚本通过t…

    2026年9月22日
    000
  • win10无法修改默认应用_Win10设置中更改默认程序失败的解决方法

    首先通过“设置”应用重新分配默认程序,若无效则使用PowerShell移除预装应用障碍,最后可手动修改注册表重置文件关联,三步解决Windows 10默认程序无法保存问题。 如果您尝试在Windows 10的设置中更改文件类型的默认打开程序,但发现设置无法保存或立即恢复为原程序,则可能是由于系统策略…

    2026年9月22日
    500
  • 如何在mysql中监控用户操作日志

    MySQL默认不记录用户操作日志,但可通过启用通用查询日志记录所有SQL操作,或使用二进制日志追踪数据变更,也可部署审计插件实现细粒度监控,结合独立账号管理和日志轮转策略提升安全性与可追溯性。 MySQL 本身不默认记录用户的所有操作日志,但可以通过启用特定的日志功能来实现对用户行为的监控。以下是几…

    2026年9月22日
    100
  • 苹果手机为什么无法连接蓝牙耳机

    先检查蓝牙开关、耳机电量及配对模式,确认设备靠近且无干扰,重启设备后删除旧连接记录并重连,更新系统或重置网络设置,若均无效则可能存在硬件故障需联系官方支持。 苹果手机连不上蓝牙耳机,通常不是大问题,多是设置或环境的小疏忽。先别着急,按下面几步检查,基本能解决大部分情况。 基础状态确认 连接失败往往出…

    2026年9月22日
    000
  • Android自定义开关UI实现教程

    本文详细介绍了在Android应用中实现自定义开关UI的两种主要方法:一是通过集成第三方库如StickySwitch,快速实现美观且功能丰富的开关;二是通过结合Drawable XML和ToggleButton,实现高度定制化的开关外观。文章提供了详细的代码示例和配置说明,旨在帮助开发者灵活地创建符…

    2026年9月22日
    000
  • 宇宙级编辑器VSCode你真的会用吗?这些隐藏功能让效率翻倍​​

    VSCode的真正潜力在于深度使用命令面板、多光标编辑、用户代码片段、集成终端与任务、自定义快捷键及扩展生态,通过主动探索设置、状态栏功能、官方文档与社区资源,结合个性化主题与高效扩展,将其从基础编辑器升级为高度定制化、自动化、无缝集成的专属开发利器,显著提升编码效率与体验。 你可能以为自己会用VS…

    2026年9月22日
    000

发表回复

登录后才能评论
关注微信