1. Python函数基础:从概念到实战
Python函数就像厨房里的多功能料理机——你把食材(参数)放进去,选择功能(函数体),它就能输出加工好的成品(返回值)。这种"黑箱"设计让代码变得模块化、可复用,是构建复杂程序的基础单元。
1.1 函数的核心价值
为什么每个Python开发者都应该精通函数?来看个真实案例:假设我们需要在电商系统中频繁比较两个价格,没有函数时的代码会是这样:
python复制price_a = 299
price_b = 399
discount_price = price_a if price_a < price_b else price_b
# 200行代码后...
product_x = 1500
product_y = 1800
final_price = product_x if product_x < product_y else product_y
而使用函数后:
python复制def get_min_price(a, b):
"""返回两个价格中的较低价"""
return a if a < b else b
# 随处可调用
discount_price = get_min_price(299, 399)
final_price = get_min_price(1500, 1800)
经验之谈:好的函数名应该像这个例子一样,用动词短语明确表达功能,get_min_price比简单的compare更直观。
1.2 函数定义深度解析
定义函数的完整语法其实包含很多隐藏细节:
python复制def function_name(
positional_arg,
default_arg=value,
*args,
**kwargs
) -> return_type:
"""Docstring describing the function"""
# 函数体
return expression
关键组件说明:
- 位置参数:必须按顺序传递
- 默认参数:调用时可省略,如
default_arg - 可变参数:
*args接收元组,**kwargs接收字典 - 类型注解:
-> return_type提示返回类型(Python 3.5+) - 文档字符串:用三引号编写,help()函数可查看
1.3 参数传递的底层机制
Python的参数传递既不是传值也不是传引用,而是"传递对象引用"。理解这点能避免很多坑:
python复制def update_list(items):
items.append(4) # 修改可变对象会影响原数据
my_list = [1, 2, 3]
update_list(my_list)
print(my_list) # 输出[1, 2, 3, 4]
def reassign(items):
items = [4, 5, 6] # 重新赋值不会影响原变量
reassign(my_list)
print(my_list) # 仍输出[1, 2, 3, 4]
避坑指南:要避免意外修改外部数据,对于可变参数,可以在函数内先做拷贝:
items = list(items)
2. 函数高级特性实战
2.1 递归函数的正确打开方式
递归虽然优雅,但使用不当会导致堆栈溢出。以经典的斐波那契数列为例,我们来看三种实现方式:
python复制# 朴素递归(性能灾难)
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2) # 存在大量重复计算
# 记忆化递归(性能优化)
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
# 迭代解法(最佳实践)
def fib(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
实测性能对比(计算fib(35)):
| 方法 | 执行时间 | 函数调用次数 |
|---|---|---|
| 朴素递归 | 4.3秒 | 29,860,703次 |
| 记忆化递归 | 0.0001秒 | 36次 |
| 迭代 | 0.00001秒 | 35次 |
2.2 lambda的适用场景
匿名函数最适合用在需要临时函数的地方,比如排序:
python复制users = [
{'name': 'Alice', 'age': 25},
{'name': 'Bob', 'age': 30},
{'name': 'Charlie', 'age': 20}
]
# 按年龄排序
users.sort(key=lambda x: x['age'])
# 等价于
def get_age(user):
return user['age']
users.sort(key=get_age)
但遇到复杂逻辑时,应该使用常规函数:
python复制# 不推荐(可读性差)
filter(lambda x: x > 10 and x % 2 == 0 and '4' not in str(x), numbers)
# 推荐
def is_valid_number(x):
return (
x > 10 and
x % 2 == 0 and
'4' not in str(x)
)
filter(is_valid_number, numbers)
3. 闭包与装饰器进阶
3.1 闭包的变量捕获机制
闭包会记住外层函数的变量,即使外层函数已经执行完毕:
python复制def multiplier(factor):
def inner(number):
return number * factor
return inner
double = multiplier(2)
triple = multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
这里有个关键细节:闭包捕获的是变量本身,而不是变量的值。这意味着如果外层变量是可变对象,闭包内修改会影响所有引用:
python复制def counter():
count = 0
def inner():
nonlocal count
count += 1
return count
return inner
c = counter()
print(c(), c(), c()) # 输出1, 2, 3
3.2 装饰器的工业级应用
装饰器在Web开发、日志记录等领域有广泛应用。下面是一个带参数的装饰器示例:
python复制import time
from functools import wraps
def retry(max_attempts=3, delay=1):
def decorator(func):
@wraps(func) # 保留原函数元信息
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
if attempts == max_attempts:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=5, delay=2)
def call_unstable_api():
# 模拟不稳定的API调用
if random.random() < 0.7:
raise ValueError("API call failed")
return "Success"
这个装饰器实现了:
- 可配置的重试次数和延迟
- 异常捕获和自动重试
- 最终失败时抛出原始异常
- 保留原函数的__name__等属性
4. 函数最佳实践与性能优化
4.1 函数设计原则
遵循SOLID原则中的单一职责原则(SRP):
- 一个函数只做一件事
- 函数应该短小(通常不超过20行)
- 参数尽量少(不超过3个为宜)
反面教材:
python复制def process_data(data, output_format='json', validate=True, log=False):
# 做了数据清洗、验证、格式转换、日志记录等太多事情
...
改进方案:
python复制def clean_data(data):
...
def validate_data(data):
...
def convert_format(data, format):
...
# 组合使用
data = clean_data(raw_data)
if validate_data(data):
result = convert_format(data, 'json')
4.2 性能优化技巧
-
局部变量访问更快:
python复制# 较慢 def calculate(): return math.sqrt(math.pi) # 较快 def calculate(): sqrt = math.sqrt pi = math.pi return sqrt(pi) -
避免不必要的函数调用:
python复制# 每次循环都调用len() for i in range(len(items)): ... # 预先计算长度 n = len(items) for i in range(n): ... -
使用生成器处理大数据:
python复制def read_large_file(file): with open(file) as f: for line in f: yield line.strip() # 内存友好 for line in read_large_file('huge.log'): process(line)
5. 类型提示与静态检查
Python 3.5+引入了类型提示,配合mypy工具可以在编码阶段发现许多类型错误:
python复制from typing import List, Dict, Optional
def process_items(
items: List[str],
counts: Dict[str, int],
limit: Optional[int] = None
) -> float:
"""处理商品数据并返回平均价值
Args:
items: 商品名称列表
counts: 商品名称到库存的映射
limit: 可选的最大处理数量
Returns:
处理商品的平均价值
"""
total = 0.0
for item in items[:limit]:
total += counts.get(item, 0)
return total / len(items)
类型提示的好处:
- 提高代码可读性
- IDE能提供更好的自动补全
- 静态检查可以发现潜在错误
- 便于生成API文档
6. 函数调试与测试
6.1 使用pdb调试
python复制def complex_calculation(a, b):
import pdb; pdb.set_trace() # 设置断点
result = 0
for i in range(a):
for j in range(b):
result += i * j
return result
常用pdb命令:
n(ext): 执行下一行s(tep): 进入函数调用c(ontinue): 继续执行到下一个断点l(ist): 显示当前代码p(rint): 打印变量值
6.2 单元测试示例
python复制import unittest
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
class TestMathFunctions(unittest.TestCase):
def test_divide_normal(self):
self.assertAlmostEqual(divide(10, 3), 3.333, places=3)
def test_divide_by_zero(self):
with self.assertRaises(ValueError):
divide(10, 0)
if __name__ == '__main__':
unittest.main()
测试要点:
- 覆盖正常情况和边界情况
- 验证返回值和异常
- 测试应该独立、可重复
- 使用assertAlmostEqual处理浮点数比较
7. 函数式编程技巧
Python虽然不是纯函数式语言,但支持许多函数式编程特性:
7.1 map/filter/reduce
python复制from functools import reduce
numbers = [1, 2, 3, 4, 5]
# 映射
squares = list(map(lambda x: x**2, numbers))
# 过滤
evens = list(filter(lambda x: x % 2 == 0, numbers))
# 归约
sum_total = reduce(lambda x, y: x + y, numbers)
7.2 偏函数(Partial)
python复制from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5)) # 25
print(cube(5)) # 125
7.3 生成器表达式
python复制# 列表推导(立即计算)
squares = [x**2 for x in range(1000000)] # 占用大量内存
# 生成器表达式(惰性计算)
squares_gen = (x**2 for x in range(1000000)) # 内存友好
# 可以组合使用
sum_of_squares = sum(x**2 for x in range(1000000))
8. 异步函数与协程
Python 3.5+引入了async/await语法支持协程:
python复制import asyncio
async def fetch_data(url):
print(f"开始获取 {url}")
await asyncio.sleep(2) # 模拟IO操作
print(f"完成获取 {url}")
return f"{url}的数据"
async def main():
# 并行执行多个任务
task1 = asyncio.create_task(fetch_data("url1"))
task2 = asyncio.create_task(fetch_data("url2"))
# 等待所有任务完成
results = await asyncio.gather(task1, task2)
print(results)
asyncio.run(main())
关键点:
async def定义异步函数await暂停当前协程,直到awaitable对象完成asyncio.create_task启动后台任务asyncio.gather等待多个协程完成
9. 函数签名与内省
Python函数是对象,可以动态检查其属性:
python复制def greet(name: str, times: int = 1) -> str:
"""生成问候语"""
return "\n".join([f"Hello {name}!"] * times)
# 获取函数签名
import inspect
sig = inspect.signature(greet)
print(sig.parameters)
# OrderedDict([
# ('name', <Parameter "name: str">),
# ('times', <Parameter "times: int = 1">)
# ])
print(sig.return_annotation) # <class 'str'>
# 动态调用
params = {'name': 'Alice', 'times': 3}
bound_args = sig.bind(**params)
print(greet(**bound_args.arguments))
这个特性在Web框架的路由、参数验证等场景非常有用。
10. 函数缓存与性能优化
对于计算密集型函数,可以使用缓存避免重复计算:
python复制from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
# 第一次计算会实际执行
print(fibonacci(50)) # 12586269025
# 后续调用直接返回缓存结果
print(fibonacci(50)) # 立即返回
缓存策略选择:
lru_cache: 适合大多数场景,自动淘汰最近最少使用的缓存- 自定义缓存:对于特殊需求可以实现
__hash__方法 - 外部缓存:如Redis,适合分布式系统
性能实测:计算fibonacci(40)无缓存需要约30秒,有缓存仅需0.0001秒