Python函数定义与参数
文章目录
- Python函数定义与参数
-
- 前言
- 一、函数的定义与调用
-
- [1.1 基本语法](#1.1 基本语法)
- [1.2 带返回值的函数](#1.2 带返回值的函数)
- 二、参数类型详解
-
- [2.1 位置参数(Positional Arguments)](#2.1 位置参数(Positional Arguments))
- [2.2 默认参数(Default Arguments)](#2.2 默认参数(Default Arguments))
- [2.3 关键字参数(Keyword Arguments)](#2.3 关键字参数(Keyword Arguments))
- [2.4 可变参数(*args)](#2.4 可变参数(*args))
- [2.5 关键字可变参数(**kwargs)](#2.5 关键字可变参数(**kwargs))
- [2.6 参数顺序规则](#2.6 参数顺序规则)
- [三、强制关键字参数(Python 3.8+)](#三、强制关键字参数(Python 3.8+))
- 四、参数解包
- 五、lambda匿名函数
-
- [5.1 典型使用场景](#5.1 典型使用场景)
- [5.2 lambda的局限性](#5.2 lambda的局限性)
- [六、函数注解(Type Hints)](#六、函数注解(Type Hints))
- 七、函数作为一等公民
- 八、综合实战:命令行参数解析器
- 总结
- [✅ 亮点总结](#✅ 亮点总结)
- 适用场景
- 扩展方向
前言
函数是编程中最基本的抽象单元------它将一段逻辑封装起来,赋予一个有意义的名字,从而实现代码的复用和模块化。Python的函数设计简洁而不失强大,支持位置参数、默认参数、可变参数(*args)、关键字可变参数(**kwargs)等多种参数类型,配合lambda匿名函数和函数式编程风格,能让你用优雅的方式解决复杂问题。
但Python函数也有几个经典陷阱,是所有中级面试几乎必考的内容:默认参数不能使用可变对象(如空列表),否则会产生"记忆"效应;*args 和 **kwargs 的参数顺序有严格规定;lambda虽然方便但可读性有限。本文将全面梳理这些要点,帮你从"会写函数"进阶到"写好函数",让代码兼具灵活性和健壮性。
一、函数的定义与调用
1.1 基本语法
python
def 函数名(参数列表):
"""文档字符串------描述函数功能"""
# 函数体
return 返回值 # 可省略,默认返回None
最简单的示例:
python
def greet():
"""打印问候语"""
print("Hello, World!")
# 调用函数
greet() # Hello, World!
# 查看文档字符串
print(greet.__doc__) # 打印问候语
1.2 带返回值的函数
python
def add(a, b):
"""返回两个数的和"""
return a + b
result = add(3, 5)
print(result) # 8
# 返回多个值(实际上是返回元组)
def min_max(data):
return min(data), max(data)
mn, mx = min_max([3, 1, 4, 1, 5, 9])
print(mn, mx) # 1 9
# 没有return语句的函数返回None
def do_nothing():
pass
print(do_nothing()) # None
二、参数类型详解
Python函数的参数系统非常灵活,支持多种参数类型。
2.1 位置参数(Positional Arguments)
最常见的参数形式,调用时按位置一一对应:
python
def describe_person(name, age, city):
print(f"{name},{age}岁,来自{city}")
describe_person("Alice", 25, "上海") # 必须按位置传入
# 输出:Alice,25岁,来自上海
2.2 默认参数(Default Arguments)
给参数设置默认值,调用时可以省略:
python
def greet(name, greeting="你好"):
print(f"{greeting},{name}!")
greet("Alice") # 你好,Alice!
greet("Bob", greeting="早上好") # 早上好,Bob!
# 默认参数的陷阱:默认值在函数定义时计算,只计算一次!
def add_item(item, container=[]):
container.append(item)
return container
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] ------ 这可能是意料之外的!
print(add_item(3)) # [1, 2, 3]
# 正确做法:使用None作为哨兵值
def add_item_safe(item, container=None):
if container is None:
container = []
container.append(item)
return container
print(add_item_safe(1)) # [1]
print(add_item_safe(2)) # [2] ------ 这才是期望的行为
print(add_item_safe(3)) # [3]
黄金法则:默认参数的值绝不要使用可变对象(列表、字典、集合等),应该用None并在函数体内初始化。
2.3 关键字参数(Keyword Arguments)
调用时通过参数名指定值,无需关心顺序:
python
def create_user(name, age, email, phone="N/A"):
return {
"name": name,
"age": age,
"email": email,
"phone": phone
}
# 关键字参数可以打乱顺序
user = create_user(age=25, email="a@test.com", name="Alice")
print(user) # {'name': 'Alice', 'age': 25, 'email': 'a@test.com', 'phone': 'N/A'}
# 混合使用:位置参数在前,关键字参数在后
user2 = create_user("Bob", age=30, email="b@test.com")
2.4 可变参数(*args)
使用 *args 接收任意数量的位置参数(打包为元组):
python
def sum_all(*args):
"""计算任意数量数字的和"""
total = 0
for num in args:
total += num
return total
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
print(sum_all()) # 0(没有参数时为空元组)
# *args 在参数列表中的位置(必须在位置参数之后,关键字参数之前)
def log(level, *messages):
for msg in messages:
print(f"[{level}] {msg}")
log("INFO", "连接成功", "数据加载完成", "服务就绪")
2.5 关键字可变参数(**kwargs)
使用 **kwargs 接收任意数量的关键字参数(打包为字典):
python
def build_html_tag(tag, **attributes):
"""构建HTML标签"""
attr_str = " ".join(f'{k}="{v}"' for k, v in attributes.items())
return f"<{tag} {attr_str}>" if attr_str else f"<{tag}>"
print(build_html_tag("input", type="text", id="username", placeholder="请输入用户名"))
# <input type="text" id="username" placeholder="请输入用户名">
print(build_html_tag("br"))
# <br>
2.6 参数顺序规则
Python函数参数的完整顺序是:
def func(位置参数, *args, 关键字限定位置参数, 默认关键字参数, **kwargs)
示例:
python
def complex_func(a, b, *args, c=10, d=20, **kwargs):
print(f"a={a}, b={b}")
print(f"args={args}")
print(f"c={c}, d={d}")
print(f"kwargs={kwargs}")
complex_func(1, 2, 3, 4, 5, c=100, e=30, f=40)
# a=1, b=2
# args=(3, 4, 5)
# c=100, d=20
# kwargs={'e': 30, 'f': 40}
三、强制关键字参数(Python 3.8+)
在 * 之后的参数只能通过关键字传入:
python
def configure(host, port, *, use_ssl=False, timeout=30):
"""*后面的参数必须使用关键字传入"""
return {
"host": host,
"port": port,
"use_ssl": use_ssl,
"timeout": timeout
}
# 正确的调用
config = configure("localhost", 8000, use_ssl=True, timeout=60)
# configure("localhost", 8000, True, 60) # TypeError! 不能按位置传入
四、参数解包
在调用函数时,可以使用 * 和 ** 将序列和字典解包为参数:
python
def multiply(a, b, c):
return a * b * c
# 列表/元组解包为位置参数
nums = [2, 3, 4]
print(multiply(*nums)) # 24(等价于 multiply(2, 3, 4))
# 字典解包为关键字参数
params = {"a": 2, "b": 3, "c": 5}
print(multiply(**params)) # 30
# 混合使用
def build_url(base, **params):
query = "&".join(f"{k}={v}" for k, v in params.items())
return f"{base}?{query}"
defaults = {"page": 1, "size": 20}
url = build_url("https://api.example.com/items", **defaults)
print(url) # https://api.example.com/items?page=1&size=20
五、lambda匿名函数
lambda 是创建小型匿名函数的方式,语法为 lambda 参数: 表达式:
python
# 传统函数
def square(x):
return x ** 2
# lambda等价形式
square_lambda = lambda x: x ** 2
print(square(5)) # 25
print(square_lambda(5)) # 25
5.1 典型使用场景
python
# 场景1:排序的key参数
students = [
("Alice", 88),
("Bob", 92),
("Charlie", 79)
]
students.sort(key=lambda s: s[1], reverse=True)
print(students) # [('Bob', 92), ('Alice', 88), ('Charlie', 79)]
# 场景2:map/filter
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens) # [2, 4, 6, 8, 10]
squared = list(map(lambda x: x**2, nums))
print(squared) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# 场景3:作为回调函数
import tkinter as tk
# root = tk.Tk()
# button = tk.Button(root, text="点击", command=lambda: print("被点击了"))
5.2 lambda的局限性
lambda只能包含单个表达式,不能包含语句或复杂逻辑。很多初学者试图在lambda中写多行逻辑或赋值语句,这是不可行的:
python
# lambda不宜用于复杂逻辑
# bad_lambda = lambda x: if x > 0: ... # 语法错误
# 正确做法:简单用lambda,复杂用def
def classify_number(n):
if n > 0:
return "正数"
elif n < 0:
return "负数"
else:
return "零"
在实际开发中,lambda最合适的用途是作为 sorted() 的 key 参数和 filter()/map() 的短小回调。如果一行写不下,或者逻辑超过两个条件分支,直接用 def 定义具名函数------可读性远重要于少写两行代码。另一个需要注意的陷阱是:在循环中创建lambda时,变量捕获的是引用而非值 ,会导致所有lambda共享最终迭代值。遇到这种情况,可以用 functools.partial 或者给lambda加默认参数来"固定"当前值。
六、函数注解(Type Hints)
Python 3.5+ 支持类型注解,提高代码可读性和IDE支持:
python
def calculate_bmi(weight: float, height: float) -> float:
"""计算BMI指数
Args:
weight: 体重(千克)
height: 身高(米)
Returns:
BMI值
"""
return weight / (height ** 2)
bmi = calculate_bmi(70, 1.75)
print(f"BMI: {bmi:.1f}") # BMI: 22.9
# 复杂类型注解
from typing import List, Dict, Tuple, Optional, Union
def process_scores(
scores: List[float],
weights: Optional[Dict[str, float]] = None
) -> Tuple[float, float, float]:
"""处理成绩,返回平均分、最高分、最低分"""
if scores:
return sum(scores) / len(scores), max(scores), min(scores)
return 0.0, 0.0, 0.0
Python的类型注解不会在运行时强制检查(需配合mypy等静态检查工具),但它对代码可维护性很有帮助。
七、函数作为一等公民
在Python中,函数是一等公民------可以赋值给变量、作为参数传递、作为返回值:
python
# 函数赋值给变量
def greet_chinese(name):
return f"你好,{name}"
def greet_english(name):
return f"Hello, {name}"
# 函数字典(策略模式)
greeters = {
"zh": greet_chinese,
"en": greet_english
}
lang = "zh"
print(greeters[lang]("Alice")) # 你好,Alice
# 函数作为参数(高阶函数)
def apply_twice(func, value):
return func(func(value))
def inc(x):
return x + 1
print(apply_twice(inc, 5)) # 7(先5+1=6,再6+1=7)
# 函数作为返回值(工厂函数)
def make_multiplier(factor):
def multiplier(x):
return x * factor
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
八、综合实战:命令行参数解析器
python
import argparse
from typing import Callable, Dict, Any
def create_cli():
"""创建一个简单的命令行工具框架"""
commands: Dict[str, Callable] = {}
def register(name: str):
"""装饰器工厂:注册命令处理函数"""
def decorator(func: Callable):
commands[name] = func
return func
return decorator
def execute(command_name: str, **kwargs: Any):
"""执行指定的命令"""
if command_name not in commands:
print(f"未知命令:{command_name}")
print(f"可用命令:{', '.join(commands.keys())}")
return
handler = commands[command_name]
result = handler(**kwargs)
return result
def list_commands():
return list(commands.keys())
return register, execute, list_commands
# 使用框架
register_cmd, execute_cmd, list_cmds = create_cli()
@register_cmd("greet")
def handle_greet(name: str = "World", repeat: int = 1) -> str:
"""问候命令"""
for _ in range(repeat):
print(f"Hello, {name}!")
return f"已问候{repeat}次"
@register_cmd("calc")
def handle_calc(a: float, b: float, op: str = "+") -> float:
"""计算命令"""
ops = {"+": a + b, "-": a - b, "*": a * b, "/": a / b}
result = ops.get(op, None)
if result is None:
print(f"不支持的运算符:{op}")
return 0.0
print(f"{a} {op} {b} = {result}")
return result
# 执行命令
print(f"可用命令:{list_cmds()}")
execute_cmd("greet", name="Alice", repeat=3)
execute_cmd("calc", a=10, b=5, op="*")
总结
Python的函数机制博大精深,本文涵盖了核心要点:
- def定义:函数名、参数、文档字符串、返回值的基本结构
- 位置参数:按顺序传入,最基础的参数传递方式
- 默认参数 :提供可选值,但绝不能使用可变对象作为默认值
- *args:接收任意数量的位置参数,打包为元组
- **kwargs:接收任意数量的关键字参数,打包为字典
- 强制关键字参数 :
*后参数必须通过关键字传入,提高可读性 - 参数解包 :
*和**调用时展开序列和字典 - lambda:简洁的匿名函数,适用于排序键、map/filter等场景
- 类型注解:提高代码可读性,配合mypy做静态检查
- 函数是一等公民:可赋值、传参、返回,支撑高阶函数和装饰器模式
函数参数设计是一门艺术------好的参数设计让调用者直观易懂,避免了"这个参数是干嘛的"的困惑。下一篇文章我们将深入学习装饰器,探索Python最具魔力的语法特性之一。
✅ 亮点总结
- 默认参数避坑:绝不能使用可变对象(\[\]、{})作为默认值,理解"定义时求值"规则
- *args 与 **kwargs:接收任意数量参数,构建灵活通用的函数接口
- 强制关键字参数 :
*后的参数必须通过关键字传入,大幅提升调用可读性 - lambda 匿名函数:一行定义简单函数,配合 sorted/map/filter 使用简洁优雅
- 函数是一等公民:可赋值给变量、作为参数传递、作为返回值,支撑高阶函数和装饰器模式
- 类型注解 :
def add(a: int, b: int) -> int:提升代码可读性,配合 mypy 做静态检查
适用场景
- 开发通用工具函数库:利用 *args/**kwargs 设计灵活API,适配多种调用方式
- 回调函数与事件处理:lambda 和函数引用作为参数传递给GUI按钮点击、定时任务等
- API包装函数设计:封装外部接口时,用类型注解明确参数和返回值含义
扩展方向
- 学习
functools模块:partial(偏函数)、reduce、lru_cache 等实用工具 - 掌握生成器函数(yield)与迭代器协议,实现惰性数据流处理
- 推荐继续阅读下一篇:Python函数进阶之装饰器,探索Python最具魔力的语法特性