参数传递机制:位置、关键字、默认与可变
Python 的函数参数,说白了就是给函数递东西的方式。这玩意看着简单,但坑其实不少。我在做代码审查时,发现好多注释不一致的问题,源头就是参数传递搞混了。
咱们今天把这四种参数机制彻底捋一遍。你想想看,函数调用时参数怎么“对号入座”?顺序、名字、默认值、可变数量,每一种都有它的脾气。
位置参数:顺序就是一切
先聊最基础的——位置参数。调用函数时,实参按顺序一一对应到形参。这个逻辑很直觉,但容易出事。
我遇到过一个小伙伴传参时顺序写反了,结果日期当成金额传进去,查了好久才找到。嗯,这就是位置参数的双刃剑——简单直接,但顺序错了就全错。
def create_user(name, age, city):
return f"{name}, {age}岁, 来自{city}"
# 位置参数调用
print(create_user("林月", 28, "深圳"))
# 输出: 林月, 28岁, 来自深圳
# 顺序错了会怎样?
print(create_user(28, "深圳", "林月"))
# 输出: 28, 深圳岁, 来自林月 --- 完全乱套了
关键字参数:明确的映射
关键字参数就聪明多了。调用时写上形参名 = 实参,顺序随便你怎么排。说白了,就是告诉函数:“这个值给这个参数,那个值给那个参数”。
我个人习惯在参数超过三个时,强制使用关键字参数。这样调用代码本身就成了文档,不用猜每个位置对应什么。
def train_model(data_path, model_type, epochs, learning_rate):
print(f"数据: {data_path}")
print(f"模型: {model_type}, 轮数: {epochs}, 学习率: {learning_rate}")
# 关键字参数 —— 顺序任意
train_model(
learning_rate=0.001,
model_type="随机森林",
data_path="./data/train.csv",
epochs=100
)
你看看,这个调用即使不写注释,别人也能看懂每个参数的含义。这就是关键字参数的价值。
默认参数:减少冗余
有的参数大部分情况下取值相同,每次都写很烦。默认参数就能解决这个问题。定义函数时直接给形参赋值,调用时如果不传,就使用默认值。
说白了就是:你给就按你的来,你不给我就按我的来。
def connect_db(host="localhost", port=5432, user="admin", password="123456"):
print(f"连接 {host}:{port} 用户 {user}")
# 使用默认参数 —— 只需传需要改的
connect_db() # 全部默认
connect_db(host="10.0.0.1") # 只改host
connect_db(host="10.0.0.2", port=3306) # 改host和port
# 踩坑示例:不要把可变对象做默认参数
def add_item(item, items=[]):
items.append(item)
return items
print(add_item("苹果")) # ['苹果']
print(add_item("香蕉")) # ['苹果', '香蕉'] —— 默认的列表被改了
# 正确的做法
def add_item_safe(item, items=None):
if items is None:
items = []
items.append(item)
return items
这就是我常跟团队说的:默认参数用 None,然后在函数内部初始化。别偷懒直接给空列表。
可变参数:应对不确定性
有些场景,你事先不知道要传多少个参数。比如日志记录、配置合并、装饰器包装。这时候就需要可变参数上场了。
Python 提供了两套:*args 和 **kwargs。一个收位置参数,打包成元组;一个收关键字参数,打包成字典。
def log_event(timestamp, *messages, **metadata):
print(f"[{timestamp}]")
for msg in messages:
print(f" - {msg}")
for key, value in metadata.items():
print(f" {key}: {value}")
# 调用:位置参数+可变位置+可变关键字
log_event(
"2024-06-15 10:30:00",
"用户登录成功",
"IP地址: 192.168.1.100",
"浏览器: Chrome",
user_id=10086,
level="info"
)
# 输出:
# [2024-06-15 10:30:00]
# - 用户登录成功
# - IP地址: 192.168.1.100
# - 浏览器: Chrome
# user_id: 10086
# level: info
*args 收位置参数变元组,**kwargs 收关键字参数变字典。这是最灵活的参数设计模式。你想想看,函数签名如果写死了参数个数,后面加需求就得改接口。用可变参数,向前兼容,优雅多了。
| 参数类型 | 定义方式 | 调用方式 | 典型场景 |
|---|---|---|---|
| 位置参数 | def f(a, b) | f(1, 2) | 简单、确定性调用 |
| 关键字参数 | def f(a, b) | f(a=1, b=2) | 提高可读性、参数多时 |
| 默认参数 | def f(a=1) | f() 或 f(2) | 配置项、可选参数 |
| 可变参数 | def f(*args, **kwargs) | f(1,2, x=3, y=4) | 扩展性、包装器、日志 |
好了,四种参数机制就讲到这里。下次写函数签名时,想想你这参数到底是不变的(位置)、需要明确的(关键字)、可有可无的(默认)还是不确定数量的(可变)。选对了,代码就清晰了。