参数传进去后到底变不变?

刚学Python那会儿,我最懵的就是函数参数传递的问题。传进去一个变量,函数里面一顿操作,出来一看,咦?怎么没变?有时候又变了?这Python到底是怎么传递参数的?

就以上问题这次相当于也是做一个总结吧!,保证看完再也不迷糊了!

先说结论:既不是传值也不是传引用!

很多教程会告诉你,Python参数传递是"传对象引用"。这么说没错,但太抽象了。实在点说的话就是:Python里,参数传递相当于把实参的"地址名片"复制一份给形参

关键是后面这点:当你修改参数时,具体行为取决于你操作的是可变对象还是不可变对象

什么是可变和不可变对象?

简单说:

  • 不可变对象:创建后不能修改内容,比如整数、浮点数、字符串、元组
  • 可变对象:创建后可以修改内容,比如列表、字典、集合

来看段代码就明白了:

python 复制代码
# 不可变对象示例
a = 10
print(id(a))  # 输出内存地址
a = a + 1
print(id(a))  # 地址变了!

# 可变对象示例
b = [1, 2, 3]
print(id(b))  # 输出内存地址
b.append(4)
print(id(b))  # 地址没变!

看到没?不可变对象修改后变成了新对象,而可变对象还是在原地修改。

函数参数传递

现在来看函数中的表现,这是最容易踩坑的地方!

情况一:不可变对象作为参数

python 复制代码
def change_number(num):
    print(f"函数内修改前: {id(num)}")
    num = 100  # 尝试修改
    print(f"函数内修改后: {id(num)}")
    
x = 10
print(f"函数调用前: {id(x)}")
change_number(x)
print(f"函数调用后: {x}")  # 输出10,没变!
print(f"函数调用后id: {id(x)}")  # 地址也没变

怎么回事?函数内明明修改了,为什么出来没变?

真相是:num = 100并不是修改了原来的对象,而是创建了一个新对象100,然后让num指向这个新对象。原来的x还是指向原来的对象,当然不变了!

情况二:可变对象作为参数

python 复制代码
def change_list(lst):
    print(f"函数内修改前: {id(lst)}")
    lst.append(4)  # 修改列表
    print(f"函数内修改后: {id(lst)}")
    
my_list = [1, 2, 3]
print(f"函数调用前: {id(my_list)}")
change_list(my_list)
print(f"函数调用后: {my_list}")  # 输出[1, 2, 3, 4],变了!
print(f"函数调用后id: {id(my_list)}")  # 地址没变

这里列表内容确实被修改了,因为列表是可变对象,append操作是在原对象上添加元素,没有创建新对象。

特殊情况:可变对象的重赋值

python 复制代码
def reassign_list(lst):
    print(f"函数内重赋值前: {id(lst)}")
    lst = [4, 5, 6]  # 重赋值
    print(f"函数内重赋值后: {id(lst)}")
    
my_list = [1, 2, 3]
print(f"函数调用前: {id(my_list)}")
reassign_list(my_list)
print(f"函数调用后: {my_list}")  # 输出[1, 2, 3],没变!

咦?同样是列表,为什么这里又没变了?

关键区别在于:append是修改原对象,而=重赋值是创建新对象并让形参指向它。这并不影响实参仍然指向原来的对象。

图解参数传递过程

想象一下,每个变量都是一个便利贴,上面写着对象的地址。

当你调用函数时,Python会复制一份便利贴传给函数。现在有两个便利贴都指向同一个对象。

  • 如果对象不可变:当你尝试修改时,Python会创建新对象,然后让你的便利贴指向新地址
  • 如果对象可变:你可以直接通过便利贴找到对象并修改它,所有指向这个对象的便利贴都能看到变化

编程中容易出现的问题

问题一:默认参数用可变对象

python 复制代码
def add_item(item, items=[]):  # 坑在这里!
    items.append(item)
    return items

print(add_item(1))  # [1]
print(add_item(2))  # [1, 2] 等等,为什么有1?

因为默认参数在函数定义时就被创建了,每次调用都用的是同一个列表对象!

正确做法:

python 复制代码
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

问题二:以为函数内修改了不可变对象

python 复制代码
def update_name(user):
    user["name"] = "李四"  # 这能生效,因为user是可变对象
    
def update_age(age):
    age = 30  # 这不会生效,因为age是不可变对象

person = {"name": "张三"}
update_name(person)  # 正确修改
print(person["name"])  # 李四

age = 25
update_age(age)
print(age)  # 还是25

总结一下

1. Python参数传递是传递对象的"地址名片"

2. 不可变对象(数字、字符串等)在函数内修改不会影响原对象

3. 可变对象(列表、字典等)在函数内修改会影响原对象

4. 但如果是重赋值(=操作),不管是可变还是不可变对象,都不会影响原对象

相关推荐
独泪了无痕5 小时前
Spring Data Redis 快速上手
spring boot·redis·后端
wangchunyu1147 小时前
JAVA CompletableFuture异步编程实战
java·开发语言·python
2501_944521599 小时前
Immer 跨端三方库仓颉适配全流程
python·eclipse·django
信誓旦旦的程序猿9 小时前
【Python量化实战宝典 #26】止损与回撤控制手册:移动止损、最大回撤(Python 可跑)
python·1024程序员节·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
m0_639906949 小时前
Linux 操作系统入门:目录结构、服务排查常用命令、Shell 脚本基础
linux·运维·后端
2501_944521599 小时前
Deepmerge 跨端三方库仓颉适配全流程
java·python·eclipse·github
Patrick在香港9 小时前
同一行 strftime,两台机器跑出两种日期
python·datetime·时区·locale·日期处理
2601_967987379 小时前
用Python+OpenCV+YOLO写个游戏脚本:从自动瞄准到自动采集的保姆级教程
python·opencv·yolo·目标检测·游戏自动化
meiying88389 小时前
快递单号批量查询Python脚本:从手动到自动化的完整实现
开发语言·python·自动化
人工智能导论实践课10 小时前
【GUI桌面开发3】PyCharm配置外部工具
ide·python·pycharm