Python 第十二天:*args、**kwargs 与默认参数
目录
1. 函数参数基础回顾
位置参数
位置参数是最基本的参数类型,必须按照定义顺序传递:
def greet(name, age, city):
"""三个位置参数"""
return f"我是{name},今年{age}岁,来自{city}"
# 必须按顺序传递
print(greet("张三", 25, "北京"))
# 输出: 我是张三,今年25岁,来自北京
# 错误示例:顺序不对
print(greet("北京", "张三", 25)) # 结果可能不符合预期
关键字参数
使用参数名传递,可以不按顺序:
def greet(name, age, city):
return f"我是{name},今年{age}岁,来自{city}"
# 使用关键字参数,顺序可以任意
print(greet(age=25, city="北京", name="张三"))
# 输出: 我是张三,今年25岁,来自北京
# 混合使用:位置参数在前,关键字参数在后
print(greet("张三", city="北京", age=25))
# 输出: 我是张三,今年25岁,来自北京
2. 默认参数详解
基本语法
在函数定义时给参数指定默认值,使其成为可选参数:
def greet(name, greeting="你好", punctuation="!"):
"""默认参数示例"""
return f"{greeting},{name}{punctuation}"
# 使用默认 greeting 和 punctuation
print(greet("张三")) # 你好,张三!
print(greet("李四", "早上好")) # 早上好,李四!
print(greet("王五", "晚上好", "。")) # 晚上好,王五。
默认参数的重要性
# 没有默认参数:每次都要提供所有参数
def connect_db(host, port, user, password, database):
"""连接数据库(无默认参数)"""
return f"连接 {host}:{port}/{database} 用户={user}"
# 必须提供所有参数
conn1 = connect_db("localhost", 3306, "root", "123456", "mydb")
# 有默认参数:简化调用
def connect_db_default(host, port=3306, user="root", password="", database="test"):
"""连接数据库(带默认参数)"""
return f"连接 {host}:{port}/{database} 用户={user}"
# 使用默认值
conn2 = connect_db_default("localhost")
conn3 = connect_db_default("localhost", user="admin")
conn4 = connect_db_default("localhost", database="production")
默认参数的常见陷阱
陷阱1:使用可变对象作为默认值
这是 Python 中最常见的错误之一!
# 错误示例:可变默认值被共享
def add_item_bad(item, items=[]): # 永远不要这样做!
items.append(item)
return items
# 每次调用共享同一个列表
print(add_item_bad("apple")) # ['apple']
print(add_item_bad("banana")) # ['apple', 'banana']
print(add_item_bad("cherry")) # ['apple', 'banana', 'cherry']
# 正确示例:使用 None 作为默认值
def add_item_good(item, items=None): # 正确做法
if items is None:
items = []
items.append(item)
return items
# 每次调用创建新列表
print(add_item_good("apple")) # ['apple']
print(add_item_good("banana")) # ['banana']
print(add_item_good("cherry")) # ['cherry']
陷阱2:默认参数在函数定义时求值
import datetime
# 错误示例:时间在函数定义时固定
def log_bad(msg, when=datetime.datetime.now()):
return f"{when}: {msg}"
# 多次调用会得到相同的时间
print(log_bad("第一次"))
# 假设输出: 2024-01-01 10:00:00.123456: 第一次
print(log_bad("第二次"))
# 输出: 2024-01-01 10:00:00.123456: 第二次(时间相同!)
# 正确示例:使用 None 并在函数内判断
def log_good(msg, when=None):
if when is None:
when = datetime.datetime.now()
return f"{when}: {msg}"
# 每次调用得到当前时间
print(log_good("第一次"))
print(log_good("第二次"))
默认参数的最佳实践
# 最佳实践1:使用不可变类型作为默认值
def func1(path, mode="r"):
"""字符串是不可变类型,可以安全使用"""
pass
# 最佳实践2:使用 None 作为可变默认值的默认值
def func2(data=None):
if data is None:
data = []
data.append(1)
return data
# 最佳实践3:对于需要保持状态的函数,显式传递
def process_items(initial_items):
"""显式传递初始列表"""
items = initial_items.copy() # 创建副本
items.append("new")
return items
my_items = ["a", "b"]
result = process_items(my_items)
print(my_items) # ['a', 'b'] - 原列表未被修改
3. *args 可变位置参数
基本语法
*args 允许函数接受任意数量的位置参数,这些参数被收集到一个元组中:
def sum_all(*args):
"""求和函数,接受任意数量的参数"""
print(f"接收到的参数类型: {type(args)}")
print(f"参数内容: {args}")
return sum(args)
# 调用方式
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
print(sum_all()) # 0
*args 的工作原理
def debug_args(*args):
"""展示 args 的本质"""
print("=" * 40)
print(f"参数个数: {len(args)}")
for i, arg in enumerate(args):
print(f" 参数{i}: {arg} (类型: {type(arg).__name__})")
debug_args(1, "hello", 3.14, True, [1, 2, 3])
# 输出:
# ========================================
# 参数个数: 5
# 参数0: 1 (类型: int)
# 参数1: hello (类型: str)
# 参数2: 3.14 (类型: float)
# 参数3: True (类型: bool)
# 参数4: [1, 2, 3] (类型: list)
使用 *args 实现灵活函数
# 示例1:计算任意数字的算术平均
def arithmetic_mean(*numbers):
"""计算算术平均数"""
if not numbers:
return None
return sum(numbers) / len(numbers)
print(arithmetic_mean(10, 20, 30)) # 20.0
print(arithmetic_mean(5, 10, 15, 20, 25)) # 15.0
# 示例2:找出最大值
def find_max(*numbers):
"""找出一组数的最大值"""
if not numbers:
return None
max_val = numbers[0]
for num in numbers[1:]:
if num > max_val:
max_val = num
return max_val
print(find_max(3, 7, 2, 9, 1)) # 9
# 示例3:字符串连接
def join_strings(*strings, separator=" "):
"""用分隔符连接字符串"""
return separator.join(strings)
print(join_strings("Hello", "World", "Python"))
print(join_strings("apple", "banana", "cherry", separator=", "))
*args 与普通参数组合
def func(prefix, *args, suffix="!"):
"""前缀 + 可变参数 + 后缀"""
result = prefix
for arg in args:
result += str(arg)
result += suffix
return result
print(func("数字:", 1, 2, 3, 4, 5)) # 数字:12345!
print(func("单词:", "a", "b", "c", suffix="?")) # 单词:abc?
4. **kwargs 可变关键字参数
基本语法
**kwargs 允许函数接受任意数量的关键字参数,这些参数被收集到一个字典中:
def print_info(**kwargs):
"""打印所有关键字参数"""
print(f"接收到的参数类型: {type(kwargs)}")
print(f"参数内容: {kwargs}")
for key, value in kwargs.items():
print(f" {key} = {value}")
# 调用方式
print_info(name="张三", age=25, city="北京")
print_info(a=1, b=2, c=3)
**kwargs 的工作原理
def debug_kwargs(**kwargs):
"""展示 kwargs 的本质"""
print("=" * 40)
print(f"参数个数: {len(kwargs)}")
for key, value in kwargs.items():
print(f" {key}: {value} (类型: {type(value).__name__})")
debug_kwargs(name="Alice", score=95, is_active=True, hobbies=["reading", "coding"])
# 输出:
# ========================================
# 参数个数: 4
# name: Alice (类型: str)
# score: 95 (类型: int)
# is_active: True (类型: bool)
# hobbies: ['reading', 'coding'] (类型: list)
使用 **kwargs 实现灵活配置
# 示例1:创建用户配置
def create_user(name, **profile):
"""创建用户,可选添加任意配置项"""
user = {"name": name}
user.update(profile)
return user
user1 = create_user("张三", age=25, city="北京", profession="工程师")
user2 = create_user("李四", email="li@example.com", phone="1234567890")
print(user1)
print(user2)
# 示例2:格式化日志消息
def log(level, message, **options):
"""日志函数,支持自定义格式"""
timestamp = options.get("timestamp", "2024-01-01 12:00:00")
prefix = options.get("prefix", "")
full_message = f"[{timestamp}] [{level}]"
if prefix:
full_message += f" [{prefix}]"
full_message += f" {message}"
return full_message
print(log("INFO", "服务启动成功"))
print(log("ERROR", "连接失败", timestamp="2024-06-15 10:30:00", prefix="DB"))
**kwargs 与普通参数组合
def func(required_arg, *args, **kwargs):
"""参数组合示例"""
print(f"必需参数: {required_arg}")
if args:
print(f"位置参数组: {args}")
if kwargs:
print(f"关键字参数组: {kwargs}")
func("test") # 必需参数: test
func("test", 1, 2, 3) # 必需参数: test, 位置参数组: (1, 2, 3)
func("test", 1, 2, 3, name="Alice", age=25) # 全部参数
5. 参数组合使用
完整参数顺序
Python 函数参数的标准顺序:
def func(positional_only, /, positional_or_keyword, *args, keyword_only, **kwargs):- positional_only: 仅位置参数(/ 之前)
- positional_or_keyword: 位置或关键字参数
- *args: 可变位置参数
- keyword_only: 仅关键字参数
- **kwargs: 可变关键字参数
def complete_example(pos_only, /, standard, *args, kw_only, **kwargs):
"""
完整参数示例
- pos_only: 只能用位置传递
- standard: 位置或关键字传递
- args: 收集额外位置参数
- kw_only: 必须用关键字传递
- kwargs: 收集额外关键字参数
"""
print(f"pos_only: {pos_only}")
print(f"standard: {standard}")
print(f"args: {args}")
print(f"kw_only: {kw_only}")
print(f"kwargs: {kwargs}")
# 调用
complete_example(1, 2, 3, 4, kw_only=5, extra="value")
# 输出:
# pos_only: 1
# standard: 2
# args: (3, 4)
# kw_only: 5
# kwargs: {'extra': 'value'}
常见组合模式
# 模式1:*args + **kwargs(最灵活)
def flexible(*args, **kwargs):
print(f"args: {args}")
print(f"kwargs: {kwargs}")
flexible(1, 2, 3, name="test", value=100)
# 模式2:默认参数 + *args + **kwargs
def with_defaults(a, b=10, *args, **kwargs):
print(f"a={a}, b={b}")
print(f"args={args}")
print(f"kwargs={kwargs}")
with_defaults(1, 2, 3, 4, x=10, y=20)
# a=1, b=2, args=(3, 4), kwargs={'x': 10, 'y': 20}
# 模式3:默认值 + *args
def func3(a, b=20, *args):
print(f"a={a}, b={b}, args={args}")
func3(1) # a=1, b=20, args=()
func3(1, 2) # a=1, b=2, args=()
func3(1, 2, 3) # a=1, b=2, args=(3,)
实际应用示例
class Calculator:
"""计算器类,演示参数组合"""
def calculate(self, operation, *numbers, precision=2, **options):
"""执行计算操作"""
self._validate_numbers(*numbers)
if operation == "sum":
result = sum(numbers)
elif operation == "avg":
result = sum(numbers) / len(numbers) if numbers else 0
elif operation == "max":
result = max(numbers) if numbers else None
elif operation == "min":
result = min(numbers) if numbers else None
else:
raise ValueError(f"未知操作: {operation}")
# 应用选项
if options.get("round", True):
result = round(result, precision)
if options.get("negative"):
result = -result
return result
def _validate_numbers(self, *numbers):
"""验证输入"""
for num in numbers:
if not isinstance(num, (int, float)):
raise TypeError(f"所有参数必须是数字,得到: {type(num)}")
# 使用
calc = Calculator()
print(calc.calculate("sum", 1, 2, 3, 4, 5)) # 15
print(calc.calculate("avg", 10, 20, 30)) # 20.0
print(calc.calculate("max", 3, 7, 2, 9, 1)) # 9
print(calc.calculate("avg", 1, 2, 3, precision=1)) # 2.0
print(calc.calculate("sum", 1, 2, 3, negative=True)) # -6
6. 参数解包与拆包
在函数调用时使用 *
将列表或元组拆包为位置参数:
def add(a, b, c):
"""三个参数求和"""
return a + b + c
# 普通调用
print(add(1, 2, 3)) # 6
# 使用 * 解包
numbers = [1, 2, 3]
print(add(*numbers)) # 6
# 解包与直接传参混用
print(add(*[1, 2], 3)) # 6
在函数调用时使用 **
将字典拆包为关键字参数:
def create_user(name, age, city):
"""创建用户"""
return {"name": name, "age": age, "city": city}
# 普通调用
print(create_user("张三", 25, "北京"))
# 使用 ** 解包字典
config = {"name": "李四", "age": 30, "city": "上海"}
print(create_user(**config))
# 部分解包
print(create_user("王五", **{"age": 28, "city": "广州"}))
在函数定义时使用 *(强制收集)
def func(*args):
"""收集所有位置参数"""
print(f"收集到: {args}")
# 调用时解包
func(*[1, 2, 3]) # 收集到: (1, 2, 3)
func(*[1, 2, 3], *[4, 5]) # 收集到: (1, 2, 3, 4, 5)
在函数定义时使用 **(强制收集)
def func(**kwargs):
"""收集所有关键字参数"""
print(f"收集到: {kwargs}")
# 调用时解包
func(**{"a": 1, "b": 2}) # 收集到: {'a': 1, 'b': 2}
func(**{"x": 10}, **{"y": 20}) # 收集到: {'x': 10, 'y': 20}
综合示例
def configure(title, width=800, height=600, **options):
"""配置窗口"""
config = {
"title": title,
"width": width,
"height": height
}
config.update(options)
return config
# 各种调用方式
print(configure("主窗口")) # 基本调用
print(configure("设置", 1024, 768)) # 指定宽高
print(configure("游戏", height=1080, fullscreen=True, vsync=False)) # 关键字参数
print(configure(**{"title": "对话框", "width": 600})) # 字典解包
7. 深入理解 * 的作用
* 的两个角色
# 角色1:在函数定义时 - 收集参数
def collect(*args):
"""收集位置参数"""
print(args)
# 角色2:在函数调用时 - 展开参数
def show(a, b, c):
print(f"a={a}, b={b}, c={c}")
numbers = [1, 2, 3]
show(*numbers) # a=1, b=2, c=3
* 用在赋值中
# 解包赋值
a, *b, c = [1, 2, 3, 4, 5]
print(f"a={a}, b={b}, c={c}") # a=1, b=[2, 3, 4], c=5
# 忽略不需要的值
head, *_, tail = [1, 2, 3, 4, 5]
print(f"head={head}, tail={tail}") # head=1, tail=5
** 的两个角色
# 角色1:在函数定义时 - 收集关键字参数
def collect(**kwargs):
print(kwargs)
# 角色2:在函数调用时 - 展开字典
def show(name, age):
print(f"name={name}, age={age}")
info = {"name": "张三", "age": 25}
show(**info) # name=张三, age=25
组合使用的完整示例
def demo(pos1, pos2, /, standard, *args, kw_only, **kwargs):
"""完整演示"""
print(f"pos1={pos1}, pos2={pos2}")
print(f"standard={standard}")
print(f"args={args}")
print(f"kw_only={kw_only}")
print(f"kwargs={kwargs}")
# 各种调用方式
print("=" * 50)
print("方式1: 基本调用")
demo(1, 2, 3, kw_only=4)
print("=" * 50)
print("方式2: 使用 * 解包")
demo(*[1, 2], 3, kw_only=4, **{"extra": 5})
print("=" * 50)
print("方式3: 使用 ** 解包")
demo(1, 2, standard=3, kw_only=4, extra1=5, extra2=6)
8. 强制关键字参数
Python 3 的仅关键字参数
在 * 之后定义的参数必须使用关键字传递:
def func(a, b, *, c, d):
"""
a, b: 可以用位置或关键字传递
c, d: 必须用关键字传递
"""
return f"a={a}, b={b}, c={c}, d={d}"
# 正确调用
print(func(1, 2, c=3, d=4))
# 错误调用 - c, d 不能用位置传递
# print(func(1, 2, 3, 4)) # TypeError
仅关键字参数的应用场景
# 场景1:防止参数顺序混淆
def create_user(name, /, *, age, email):
"""
name: 仅位置(用户名)
age, email: 仅关键字(必须指定参数名)
"""
return {"name": name, "age": age, "email": email}
# name 可以直接传,其他必须用关键字
print(create_user("张三", age=25, email="zhang@example.com"))
print(create_user("李四", email="li@example.com", age=30))
# 场景2:增强代码可读性
def send_message(message, /, *, to, from_address, subject="", priority="normal"):
"""
强制使用关键字参数,代码更清晰
"""
return {
"message": message,
"to": to,
"from": from_address,
"subject": subject,
"priority": priority
}
msg = send_message("你好", to="user@example.com", from_address="system@company.com")
/ 和 * 的组合使用
def complex_func(
pos_only, # 位置限定: 必须用位置传参
/, # 斜杠:左边仅位置
pos_or_kw, # 普通参数: 位置或关键字
*, # 星号:右边仅关键字
kw_only # 关键字限定: 必须用关键字传参
):
return f"{pos_only}, {pos_or_kw}, {kw_only}"
# 测试
print(complex_func(1, 2, kw_only=3)) # 正确
# print(complex_func(1, pos_or_kw=2, kw_only=3)) # 错误: pos_or_kw 不能用关键字
# print(complex_func(pos_only=1, 2, kw_only=3)) # 错误: pos_only 必须用位置
9. 参数注解与类型提示
基本类型注解
def greet(name: str, age: int) -> str:
"""带类型注解的函数"""
return f"你好,{name}!你 {age} 岁了。"
print(greet("张三", 25))
*args 和 **kwargs 的类型注解
from typing import Tuple, Dict, Any
def func(*args: int, **kwargs: str) -> Tuple[tuple, dict]:
"""带类型的 *args 和 **kwargs"""
return args, kwargs
# 调用
result = func(1, 2, 3, name="Alice", city="Beijing")
print(result) # ((1, 2, 3), {'name': 'Alice', 'city': 'Beijing'})
Optional 和 Union 类型
from typing import Optional, Union, List
def process(
data: List[int],
multiplier: Optional[float] = None,
**options: Union[str, int]
) -> Dict[str, Any]:
"""复杂类型注解示例"""
result = sum(data)
if multiplier is not None:
result *= multiplier
return {
"sum": result,
"count": len(data),
"options": options
}
print(process([1, 2, 3], multiplier=2.0, name="test"))
TypedDict(Python 3.8+)
from typing import TypedDict, List
class UserProfile(TypedDict):
name: str
age: int
email: str
hobbies: List[str]
def create_profile(**kwargs: str) -> UserProfile:
"""TypedDict 用法"""
profile = UserProfile(
name=kwargs.get("name", ""),
age=int(kwargs.get("age", 0)),
email=kwargs.get("email", ""),
hobbies=kwargs.get("hobbies", [])
)
return profile
10. 实战练习
练习1:通用格式化函数
def format_table(
*columns,
delimiter: str = " | ",
header: bool = True,
align: str = "left",
padding: int = 1
) -> str:
"""
将多个列表格式化为表格字符串
Args:
*columns: 任意数量的列表,每个列表作为一列
delimiter: 列分隔符
header: 是否显示列标题
align: 对齐方式 ('left', 'right', 'center')
padding: 单元格内边距
Returns:
格式化的表格字符串
"""
if not columns:
return ""
# 获取每列的最大宽度
num_rows = max(len(col) for col in columns)
col_widths = []
for col in columns:
col_widths.append(max(len(str(item)) for item in col) if col else 0)
# 构建表格
rows = []
padding_str = " " * padding
def format_cell(content, width):
content = str(content)
pad = padding_str
if align == "left":
return f"{pad}{content:<{width}}{pad}"
elif align == "right":
return f"{pad}{content:>{width}}{pad}"
else: # center
return f"{pad}{content:^{width}}{pad}"
# 添加表头行
if header:
header_row = delimiter.join(
format_cell(f"列{i+1}", col_widths[i])
for i in range(len(columns))
)
rows.append(header_row)
# 分隔线
separator = delimiter.join("-" * (w + 2 * padding) for w in col_widths)
rows.append(separator)
# 添加数据行
for row_idx in range(num_rows):
row_values = []
for col_idx, col in enumerate(columns):
value = col[row_idx] if row_idx < len(col) else ""
row_values.append(format_cell(value, col_widths[col_idx]))
rows.append(delimiter.join(row_values))
return "\n".join(rows)
# 测试
names = ["Alice", "Bob", "Charlie", "David"]
ages = [25, 30, 35, 28]
cities = ["Beijing", "Shanghai", "Guangzhou", "Shenzhen"]
print(format_table(names, ages, cities))
print("\n" + "=" * 50 + "\n")
print(format_table(names, ages, cities, header=False))
print("\n" + "=" * 50 + "\n")
print(format_table(names, ages, cities, align="right", padding=2))
练习2:函数装饰器(使用 *args, **kwargs)
from functools import wraps
from typing import Callable, Any
import time
def timing(func: Callable) -> Callable:
"""测量函数执行时间的装饰器"""
@wraps(func)
def wrapper(*args, **kwargs) -> Any:
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"函数 {func.__name__} 执行时间: {end - start:.4f} 秒")
return result
return wrapper
def validate_args(**validators):
"""验证参数类型的装饰器工厂"""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs):
# 获取函数签名
import inspect
sig = inspect.signature(func)
bound = sig.bind(*args, **kwargs)
bound.apply_defaults()
# 验证参数
for name, validator in validators.items():
if name in bound.arguments:
value = bound.arguments[name]
if not validator(value):
raise ValueError(f"参数 {name}={value} 验证失败")
return func(*args, **kwargs)
return wrapper
return decorator
@timing
@validate_args(age=lambda x: 0 < x < 150, name=lambda x: isinstance(x, str) and len(x) > 0)
def create_person(name: str, age: int, city: str = "北京") -> dict:
"""创建人物信息"""
return {"name": name, "age": age, "city": city}
# 测试
print(create_person("张三", 25))
print(create_person("李四", age=30))
# create_person("", 25) # 会抛出异常
# create_person("王五", 200) # 会抛出异常
练习3:链式函数调用
class QueryBuilder:
"""SQL 查询构建器,演示参数传递"""
def __init__(self, table: str):
self.table = table
self._fields = ["*"]
self._conditions = []
self._order_by = None
self._limit_val = None
def select(self, *fields: str) -> "QueryBuilder":
"""选择字段"""
if fields:
self._fields = list(fields)
return self
def where(self, condition: str, *params) -> "QueryBuilder":
"""添加 WHERE 条件"""
self._conditions.append((condition, params))
return self
def order_by(self, field: str, direction: str = "ASC") -> "QueryBuilder":
"""排序"""
self._order_by = (field, direction.upper())
return self
def limit(self, count: int, offset: int = 0) -> "QueryBuilder":
"""限制结果数"""
self._limit_val = (count, offset)
return self
def build(self) -> tuple:
"""构建 SQL 和参数"""
# SELECT
sql = f"SELECT {', '.join(self._fields)} FROM {self.table}"
# WHERE
params = []
if self._conditions:
where_parts = []
for cond, cond_params in self._conditions:
where_parts.append(cond)
params.extend(cond_params)
sql += " WHERE " + " AND ".join(where_parts)
# ORDER BY
if self._order_by:
sql += f" ORDER BY {self._order_by[0]} {self._order_by[1]}"
# LIMIT
if self._limit_val:
sql += f" LIMIT ? OFFSET ?"
params.extend(self._limit_val)
return sql, tuple(params)
def __repr__(self):
sql, params = self.build()
return f"SQL: {sql}\nParams: {params}"
# 使用
query = (
QueryBuilder("users")
.select("id", "name", "email")
.where("age > ?", 18)
.where("city = ?", "北京")
.order_by("name", "ASC")
.limit(10, 0)
)
print(query)
总结
今天我们深入学习了 Python 函数参数的高级特性:
- 默认参数:为参数提供默认值,但避免使用可变对象作为默认值
- *args:收集任意数量的位置参数为元组
- **kwargs:收集任意数量的关键字参数为字典
- 参数组合:多种参数类型可以组合使用,有固定顺序
- 解包与拆包:使用
*和**在调用时展开参数 - 仅关键字参数:使用
*分隔符强制某些参数必须用关键字传递 - 类型注解:使用
: type和-> type提供类型提示
掌握这些知识可以编写出更加灵活和强大的 Python 函数。