GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • 卡码笔记-最强八股文
  • 计算机基础
  • Python
  • C++
  • Java
  • Go
  • ChatGPT充值
  • Claude充值
  • 国内使用Claude
  • 中转站推荐
  • 🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经

This view

  • Original page
  • New address

GeezerVison is showing notes.kamacoder.com — Python函数参数是值传递还是引用传递?对象共享传递与可变对象修改

# Python函数参数是值传递还是引用传递?

面试官问:"Python函数传参是值传递还是引用传递?"

这题最容易答成"Python一切都是引用传递",但这个说法并不准确。真正要讲清楚的是:函数拿到的是同一个对象的引用副本,参数重新绑定和修改可变对象不是一回事。

# 简要回答

Python采用的是对象共享传递(call by object reference / call by sharing),不是传统意义上的值传递或引用传递。

调用函数时,形参会绑定到实参指向的同一个对象。这带来两个行为:

重新绑定形参不会影响外部变量。函数内执行param = new_value,只是让局部名字param指向另一个对象,调用方的变量仍然指向原来的对象。

通过形参修改可变对象,会影响外部看到的同一个对象。函数内执行items.append(x),修改的是调用方和函数共享的那个列表对象,外部自然也能看到变化。

# 详细回答

# 函数调用时发生了什么

Python中,变量是名字,对象独立存在。调用函数时:

  1. 实参表达式求值,得到对象。
  2. 形参名字绑定到这个对象。
  3. 函数内通过形参名访问对象。
  4. 函数返回后,形参名字消失,但对象是否被修改取决于函数内的操作。

形参与实参绑定同一个对象,不是复制对象内容,也不是传递变量本身的地址。

函数参数对象绑定

# 重新绑定不会修改外部对象

def reset_list(items):
    items = []  # 重新绑定局部名字items

courses = ["Python", "Go"]
reset_list(courses)
print(courses)  # ['Python', 'Go']
1
2
3
4
5
6

函数内items = []创建了一个新列表,让局部名字items指向它。外部的courses仍然指向原来的列表,所以courses的内容没有变化。

# 修改可变对象会影响外部

def append_item(items):
    items.append("C++")  # 原地修改对象

courses = ["Python", "Go"]
append_item(courses)
print(courses)  # ['Python', 'Go', 'C++']
1
2
3
4
5
6

items.append("C++")调用列表对象的方法,原地修改了这个列表。因为items和courses共享同一个对象,外部看到的列表自然也被修改了。

# 不可变对象的行为

对于不可变对象(整数、字符串、元组),函数内尝试修改时,Python实际上创建了新对象:

def increment(num):
    num = num + 1  # 创建新整数对象并重新绑定
    return num

count = 10
result = increment(count)
print(count)   # 10,外部变量未变
print(result)  # 11
1
2
3
4
5
6
7
8

num = num + 1计算后生成新整数对象11,num绑定到新对象,count仍然指向10。

# 常见错误:以为传的是变量本身

def swap(a, b):
    a, b = b, a  # 只是交换局部名字的绑定

x, y = 1, 2
swap(x, y)
print(x, y)  # 1 2,外部变量顺序未变
1
2
3
4
5
6

a, b = b, a只是让函数内的局部名字a和b交换了指向,不会影响外部的x和y。

# 知识拓展

Q1:为什么不说"引用传递"?

传统的引用传递(如C++中的int &x)允许函数直接修改外部变量的绑定。Python函数内重新绑定形参不会影响外部变量,所以不是引用传递。更准确的术语是对象共享传递或传对象引用。

Q2:如何让函数修改外部的不可变对象?

不可变对象本身不能被修改,可以把它包在可变容器里:

def increment_wrapped(container):
    container[0] = container[0] + 1

num = [10]
increment_wrapped(num)
print(num[0])  # 11
1
2
3
4
5
6

或者直接返回新值:

def increment(num):
    return num + 1

count = increment(count)
1
2
3
4

Q3:id()能看出对象是否相同吗?

可以。id(obj)返回对象的身份标识(在CPython中是内存地址)。形参和实参的id()相同,说明它们指向同一个对象。

Q4:函数参数会复制对象内容吗?

不会。无论对象多大,传参只是复制引用(一个指针),不复制对象本身。这也是为什么传递大列表或大字典不会有性能问题。

Q5:如何避免函数意外修改可变参数?

函数内先复制一份:

def process_items(items):
    items = items.copy()  # 浅拷贝,不影响外部
    items.append("new")
    return items
1
2
3
4

或者明确在调用时传副本:

result = process_items(courses.copy())
1

← 列表、字典和集合推导式怎么写? 位置参数、关键字参数、*args和**kwargs怎么用? →

评论

验证登录状态...

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.