3.3 参数进阶与解包
目标
- 读懂
*args(收集多余位置参数)与**kwargs(收集多余关键字参数)。 - 读懂签名中的
*(其后仅限关键字)与/(其前仅限位置)。 - 掌握调用时的
*序列与**字典解包——minimal_agent.py核心一行的语法。
核心概念
定义时:收集参数
| 写法 | 收集什么 | 得到的类型 |
|---|---|---|
*args | 多余的位置参数 | tuple |
**kwargs | 多余的关键字参数 | dict |
名字 args、kwargs 只是约定,关键是 * 和 **。
Python · 可运行
python
def show(first, *args, **kwargs):
print("first =", first)
print("args =", args)
print("kwargs =", kwargs)
show(1, 2, 3, model="deepseek-flash", max_tokens=1024)仅限关键字 * 与仅限位置 /
Python · 可运行
python
def create(*, model, max_tokens, temperature=1.0):
# * 之后的参数只能用关键字传
return model, max_tokens, temperature
print(create(model="m", max_tokens=10))
def divide(a, b, /):
# / 之前的参数只能按位置传(3.8 起,PEP 570)
return a / b
print(divide(6, 3))完整顺序:def f(仅位置, /, 普通, *, 仅关键字);*args 可占据 * 的位置。
SDK 广泛使用仅限关键字参数,迫使调用方写出参数名,避免把 max_tokens 和 temperature 传反:
Python · 可运行演示报错
python
def create(*, model, max_tokens):
return model, max_tokens
create("deepseek-flash", 1024) # 不允许按位置传调用时:解包参数
调用时,* 把序列展开为多个位置参数,** 把字典展开为多个关键字参数(键必须是字符串,且与参数名对应):
Python · 可运行
python
def get_weather(city, unit="C"):
return f"{city}:22°{unit}"
args = ["上海"]
kwargs = {"city": "北京", "unit": "F"}
print(get_weather(*args)) # 等价 get_weather("上海")
print(get_weather(**kwargs)) # 等价 get_weather(city="北京", unit="F")
print(get_weather(**{"city": "深圳"}))组合:转发参数
*args, **kwargs 常用于“原样转发”给另一个函数(装饰器、包装器中极常见):
Python · 可运行
python
def call_logged(func, *args, **kwargs):
print(f"调用 {func.__name__} args={args} kwargs={kwargs}")
return func(*args, **kwargs)
def get_weather(city, unit="C"):
return f"{city}:22°{unit}"
print(call_logged(get_weather, "上海", unit="C"))易错点
- 定义时的
*与调用时的*方向相反:定义时“收集”,调用时“展开”。 **解包的键与参数名不匹配:抛TypeError: … got an unexpected keyword argument。这正是模型返回的工具参数与函数签名不一致时会遇到的错误。- 同一参数传两次:
f("上海", **{"city": "北京"})抛TypeError: … got multiple values for argument 'city'。
Python · 可运行演示报错
python
def get_weather(city):
return city
model_input = {"city": "上海", "date": "today"} # 多了函数不认识的参数
get_weather(**model_input)对照
| 概念 | Python | JS | Java |
|---|---|---|---|
| 收集剩余位置参数 | def f(*args) | function f(...args) | void f(String... args) |
| 收集关键字参数 | def f(**kwargs) | 用对象参数 f(opts) | 无(Map 或 Builder) |
| 展开序列 | f(*xs) | f(...xs) | 传数组给可变参数 |
| 展开字典为命名参数 | f(**d) | 无(只能传整个对象) | 无 |
| 强制命名参数 | def f(*, a) | 无 | 无 |
读代码
minimal_agent.py 的核心一行:
python
output = TOOL_FUNCTIONS[block.name](**block.input)从左到右拆成三步:
| 步骤 | 表达式 | 结果 |
|---|---|---|
| 1 | block.name | "get_weather"(模型要调用的工具名) |
| 2 | TOOL_FUNCTIONS["get_weather"] | 函数对象 get_weather |
| 3 | get_weather(**{"city": "上海"}) | 等价 get_weather(city="上海") |
block.input 是模型按 tools 中 input_schema 生成的参数字典,所以它的键必须与 Python 函数的参数名一致——这就是 input_schema.properties 里写 "city"、函数签名也写 city 的原因。
SDK 方法签名通常长这样(简化示意):
python
def create(self, *, max_tokens: int, messages: list, model: str, tools: list | None = None, extra_headers: dict | None = None) -> Message: ... # 简化示意* 之后全是仅限关键字参数,所以调用时必须写 model=...、max_tokens=...。
练习
练习 1:执行工具调用
实现 run_tool(tool_functions, name, tool_input):从字典中找到函数,用 ** 把 tool_input 展开调用,返回结果。
练习
def run_tool(tool_functions, name, tool_input):
...
快捷键:⌘/Ctrl + Enter 提交检查
练习 2:收集参数
实现 build_request(model, *messages, **options):返回字典 {"model": model, "messages": [所有位置消息], **options}(选项合并到顶层)。
练习
def build_request(model, *messages, **options):
return {"model": model}
快捷键:⌘/Ctrl + Enter 提交检查
练习 3:仅限关键字参数
把 create 改成“model 和 max_tokens 只能用关键字传入”,函数体不变。
练习
def create(model, max_tokens):
return {"model": model, "max_tokens": max_tokens}
快捷键:⌘/Ctrl + Enter 提交检查
小结
- 定义时:
*args收集为元组,**kwargs收集为字典;*后仅限关键字,/前仅限位置。 - 调用时:
*序列展开为位置参数,**字典展开为关键字参数。 TOOL_FUNCTIONS[block.name](**block.input):查表得到函数,再用字典展开调用。