第一次接触Python函数传参时,很多人会被"值传递"和"引用传递"的概念绕晕。实际上Python采用的是"对象引用传递"机制——既不是单纯的传值也不是传统的传引用。理解这个机制需要先明确三个关键事实:
举个例子:
python复制def modify(num):
num += 10
print("函数内:", id(num), num) # 新对象
n = 5
print("调用前:", id(n), n) # 原始对象
modify(n)
print("调用后:", id(n), n) # 仍是原始对象
输出会显示函数内外num和n的id值变化,直观展示不可变对象在传参时的行为特征。
当传递不可变对象时,函数内对参数的修改会创建新对象:
python复制def try_change_str(s):
s = "新内容"
print("函数内:", s)
text = "原始内容"
try_change_str(text)
print("函数外:", text) # 仍输出"原始内容"
这是因为字符串不可变,任何修改都会创建新对象,而原始引用保持不变。
传递可变对象时,函数内修改会影响原始对象:
python复制def append_item(lst):
lst.append(4)
print("函数内:", lst)
my_list = [1, 2, 3]
append_item(my_list)
print("函数外:", my_list) # 输出[1, 2, 3, 4]
但重新赋值不会影响原变量:
python复制def reassign_list(lst):
lst = [7, 8, 9] # 创建新引用
print("函数内:", lst)
my_list = [1, 2, 3]
reassign_list(my_list)
print("函数外:", my_list) # 仍为[1, 2, 3]
默认参数在函数定义时求值,可能导致意外行为:
python复制def add_item(item, lst=[]): # 默认列表只创建一次
lst.append(item)
return lst
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] 而非预期的[2]
正确做法是:
python复制def add_item(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
可变参数本质上都是打包成元组和字典进行传递:
python复制def show_args(*args, **kwargs):
print(type(args)) # <class 'tuple'>
print(type(kwargs)) # <class 'dict'>
show_args(1, 2, a=3, b=4)
函数作为一等公民,传递的是函数对象引用:
python复制def apply(func, x):
return func(x)
def square(n):
return n * n
print(apply(square, 5)) # 25
类实例作为参数传递时,修改属性会影响原始对象:
python复制class Person:
def __init__(self, name):
self.name = name
def rename(p, new_name):
p.name = new_name
john = Person("John")
rename(john, "Jack")
print(john.name) # 输出"Jack"
利用默认参数特性实现记忆化:
python复制def factorial(n, _cache={0: 1, 1: 1}):
if n not in _cache:
_cache[n] = n * factorial(n-1)
return _cache[n]
通过返回self实现方法链:
python复制class Calculator:
def __init__(self, value=0):
self.value = value
def add(self, num):
self.value += num
return self # 返回实例本身
calc = Calculator()
result = calc.add(5).add(3).value # 8
使用*和**进行参数解包:
python复制def draw_point(x, y, color='black'):
print(f"在({x},{y})绘制{color}的点")
coords = (3, 4)
params = {'color': 'red'}
draw_point(*coords, **params)
python复制x = 10
def modify():
x = 20 # 创建局部变量
print("函数内:", x)
modify()
print("函数外:", x) # 仍是10
使用global关键字修改全局变量:
python复制x = 10
def modify():
global x
x = 20
modify()
print(x) # 20
python复制import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)
original[0][0] = 99
print(shallow[0][0]) # 99 (受影响)
print(deep[0][0]) # 1 (不受影响)
通过id()追踪对象引用变化:
python复制a = [1, 2, 3]
print(id(a)) # 原始列表ID
def modify(lst):
print("传入的ID:", id(lst))
lst.append(4)
print("修改后ID:", id(lst)) # 相同
modify(a)
print("最终ID:", id(a)) # 仍相同
python复制# 不推荐
def process_data(data):
result = []
for item in data:
result.append(str(item)) # 频繁创建新字符串
return result
# 推荐
def process_data(data):
return list(map(str, data)) # 批量转换
python复制def large_sequence():
for i in range(1000000):
yield i # 惰性计算
sum(large_sequence()) # 不一次性加载全部数据
python复制import timeit
def by_value(x):
return x + 1
def by_ref(lst):
lst.append(1)
print("不可变对象:", timeit.timeit('by_value(1)', globals=globals()))
print("可变对象:", timeit.timeit('by_ref([])', globals=globals()))
Django视图函数接收request对象:
python复制from django.http import HttpResponse
def greet(request): # request是可变对象
name = request.GET.get('name', 'Guest')
return HttpResponse(f"Hello, {name}!")
Flask使用线程局部变量实现请求上下文:
python复制from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def index():
user_agent = request.headers.get('User-Agent')
return f"你的浏览器是: {user_agent}"
事件驱动编程中的回调机制:
python复制def event_handler(callback):
data = "事件数据"
callback(data) # 传递数据给回调函数
def custom_callback(info):
print("收到:", info)
event_handler(custom_callback)
python复制from typing import List, Dict
def process_items(items: List[str],
config: Dict[str, int]) -> int:
return len(items) * config.get('factor', 1)
理解Python参数传递机制的关键在于:区分"变量"和"对象"的概念,明白函数接收的是对象引用的副本。这个认知能帮助开发者避免80%的参数传递相关问题。