1. 函数基础:从零开始理解Python函数
第一次接触Python函数时,我完全不明白为什么要把代码打包成一个个"黑盒子"。直到有次需要重复计算不同半径的圆面积,写了十几行重复代码后,才真正体会到函数的价值。函数就像厨房里的多功能料理机——你把食材(参数)放进去,选择功能(函数逻辑),就能得到加工好的成品(返回值)。
1.1 函数定义的基本语法
Python中使用def关键字定义函数,基本结构如下:
def 函数名(参数1, 参数2=默认值): """文档字符串(可选)""" 函数体 return 返回值 # 可选新手最容易犯的语法错误是忘记冒号和缩进。记得有次我调试半小时的报错,最后发现只是少了个冒号。正确的缩进是Python的灵魂,函数体内的所有代码必须保持相同缩进层级(通常4个空格)。
1.2 参数传递的三种方式
- 位置参数:最常见的传参方式,按顺序对应
def greet(name, greeting): print(f"{greeting}, {name}!") greet("小明", "你好") # 输出:你好, 小明!- 关键字参数:明确指定参数名,顺序可变
greet(greeting="Hi", name="Alice") # 输出:Hi, Alice!- 默认参数:定义时指定默认值
def greet(name, greeting="Hello"): print(f"{greeting}, {name}!") greet("Bob") # 输出:Hello, Bob!注意:默认参数必须放在非默认参数之后,否则会引发语法错误。这是新手常踩的坑。
2. 函数进阶:理解变量作用域与返回值
2.1 变量作用域:LEGB规则
Python查找变量的顺序遵循LEGB规则:
- Local(局部作用域)
- Enclosing(闭包函数外的函数)
- Global(全局作用域)
- Built-in(内置作用域)
x = "global" # 全局变量 def outer(): x = "enclosing" def inner(): x = "local" # 局部变量 print(x) # 输出:local inner() print(x) # 输出:enclosing outer() print(x) # 输出:global2.2 返回多个值的秘密
Python函数实际上是通过返回元组来实现多值返回的:
def get_circle_info(radius): area = 3.14 * radius ** 2 circumference = 2 * 3.14 * radius return area, circumference # 实际上是返回元组 result = get_circle_info(5) print(type(result)) # 输出:<class 'tuple'>解包接收返回值更优雅:
area, circumference = get_circle_info(5)3. 高阶函数特性:让代码更Pythonic
3.1 lambda表达式:匿名函数的妙用
lambda适合简单的一次性函数,比如排序时的key函数:
students = [("小明", 88), ("小红", 95), ("小刚", 78)] students.sort(key=lambda x: x[1], reverse=True) print(students) # 输出:[('小红', 95), ('小明', 88), ('小刚', 78)]3.2 闭包:函数记住外部变量的能力
闭包就像给函数装了个"记忆芯片":
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)) # 输出:153.3 装饰器:不修改原函数添加功能
装饰器是Python的语法糖,本质是高阶函数:
def timer(func): def wrapper(*args, **kwargs): import time start = time.time() result = func(*args, **kwargs) end = time.time() print(f"{func.__name__}执行耗时:{end-start:.4f}秒") return result return wrapper @timer def long_running_func(n): return sum(i*i for i in range(n)) long_running_func(1000000)4. 常见问题与调试技巧
4.1 新手常犯的5个错误
- 修改可变默认参数:
def append_to(element, target=[]): # 危险! target.append(element) return target正确做法:
def append_to(element, target=None): if target is None: target = [] target.append(element) return target- 混淆return和print:
def add(a, b): print(a + b) # 这不是返回值! result = add(2, 3) # result是None- 忽略函数文档:养成写docstring的习惯
def calculate_area(radius): """计算圆的面积 参数: radius (float): 圆的半径 返回: float: 圆的面积 """ return 3.14 * radius ** 24.2 调试函数的神器:pdb
在函数中插入断点:
import pdb def complex_calculation(x, y): pdb.set_trace() # 调试断点 result = x**2 + y**2 return result ** 0.5常用pdb命令:
n(ext):执行下一行s(tep):进入函数c(ontinue):继续执行l(ist):查看当前代码p(rint):打印变量值
5. 实战演练:构建一个温度转换器
让我们综合运用所学知识,开发一个完整的温度转换程序:
def temperature_converter(value, input_scale, output_scale): """温度单位转换器 参数: value (float): 温度值 input_scale (str): 输入单位 ("C", "F", "K") output_scale (str): 输出单位 ("C", "F", "K") 返回: float: 转换后的温度值 异常: ValueError: 当输入无效单位时抛出 """ scales = {"C", "F", "K"} if input_scale not in scales or output_scale not in scales: raise ValueError("无效的温度单位,请使用C/F/K") # 先统一转为摄氏度 if input_scale == "F": celsius = (value - 32) * 5/9 elif input_scale == "K": celsius = value - 273.15 else: celsius = value # 从摄氏度转为目标单位 if output_scale == "F": return celsius * 9/5 + 32 elif output_scale == "K": return celsius + 273.15 else: return celsius # 测试用例 print(temperature_converter(100, "C", "F")) # 212.0 print(temperature_converter(32, "F", "C")) # 0.0 print(temperature_converter(0, "C", "K")) # 273.15这个案例涵盖了:
- 清晰的函数定义
- 参数验证
- 条件逻辑
- 单位转换算法
- 文档字符串
- 异常处理
6. 函数最佳实践与性能考量
6.1 保持函数单一职责
好函数应该像瑞士军刀的一个工具——只做好一件事。我曾经写过一个函数同时处理数据清洗、转换和保存,结果调试起来简直是噩梦。后来拆分成三个小函数,每个函数不到20行代码,可读性和可维护性大幅提升。
6.2 参数设计的艺术
- 限制参数数量(最好不超过5个)
- 相关参数可以组合成对象或字典
- 布尔型参数通常意味着函数做了太多事
- 考虑使用
*args和**kwargs增加灵活性
def process_data(data, *, normalize=True, verbose=False): """处理数据 参数: data: 输入数据 normalize (bool): 是否标准化数据 verbose (bool): 是否打印详细信息 """ pass6.3 性能优化技巧
- 局部变量访问更快:
def slow_func(): return math.sqrt(x) + math.sin(x) # 每次都要查找math def fast_func(): sqrt, sin = math.sqrt, math.sin # 局部变量缓存 return sqrt(x) + sin(x)- 避免在循环中定义函数:
# 不好 for i in range(10000): def square(x): return x*x square(i) # 好 def square(x): return x*x for i in range(10000): square(i)- 使用生成器处理大数据:
def read_large_file(file_path): with open(file_path) as f: for line in f: yield process_line(line)7. 从函数到面向对象:理解方法与函数的关系
当你在类中定义函数时,它就变成了方法。方法第一个参数通常是self,指向实例对象:
class Circle: def __init__(self, radius): self.radius = radius def area(self): return 3.14 * self.radius ** 2 @staticmethod def info(): # 静态方法不需要self print("这是一个圆形类")静态方法(@staticmethod)和类方法(@classmethod)是特殊的函数:
- 静态方法:与类和实例都无关,只是放在类里的函数
- 类方法:第一个参数是类本身(cls),常用于替代构造函数
class Pizza: def __init__(self, ingredients): self.ingredients = ingredients @classmethod def margherita(cls): return cls(["番茄", "马苏里拉奶酪", "罗勒"]) @classmethod def prosciutto(cls): return cls(["番茄", "马苏里拉奶酪", "火腿"])8. 函数式编程在Python中的应用
虽然Python不是纯函数式语言,但支持许多函数式编程特性:
8.1 map/filter/reduce三剑客
numbers = [1, 2, 3, 4, 5] # map: 对每个元素应用函数 squares = list(map(lambda x: x**2, numbers)) # filter: 过滤元素 evens = list(filter(lambda x: x%2 == 0, numbers)) # reduce: 累积计算 from functools import reduce product = reduce(lambda x, y: x*y, numbers)8.2 列表推导式 vs map/filter
列表推导式通常更Pythonic:
# 使用map/filter result = list(map(lambda x: x.upper(), filter(lambda x: len(x)>3, names))) # 使用列表推导式 result = [name.upper() for name in names if len(name) > 3]8.3 偏函数:固定部分参数
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)) # 1259. 类型提示:让函数接口更清晰
Python 3.5+支持类型提示,虽然不是强制性的,但能大大提高代码可读性:
from typing import List, Tuple, Optional def process_items( items: List[str], max_length: int = 100 ) -> Tuple[bool, Optional[str]]: """处理字符串列表 参数: items: 待处理的字符串列表 max_length: 最大允许长度 返回: 元组:(是否成功, 错误信息或None) """ if any(len(item) > max_length for item in items): return False, "存在超长字符串" return True, None使用mypy进行静态类型检查:
pip install mypy mypy your_script.py10. 异步函数:理解async/await
Python 3.5+引入了原生协程支持:
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")) data1 = await task1 data2 = await task2 print(f"获取到数据:{data1}, {data2}") asyncio.run(main())关键点:
async def定义异步函数await暂停当前协程,直到awaitable对象完成- 异步函数被调用时会返回协程对象,需要await或交给事件循环执行
11. 函数测试:确保代码质量
11.1 使用doctest进行文档测试
def factorial(n): """计算阶乘 >>> factorial(5) 120 >>> factorial(0) 1 >>> factorial(-1) Traceback (most recent call last): ... ValueError: n必须是非负整数 """ if n < 0: raise ValueError("n必须是非负整数") return 1 if n == 0 else n * factorial(n-1) if __name__ == "__main__": import doctest doctest.testmod()11.2 使用unittest进行单元测试
import unittest class TestMathFunctions(unittest.TestCase): def test_add(self): self.assertEqual(add(2, 3), 5) self.assertEqual(add(-1, 1), 0) def test_divide(self): self.assertAlmostEqual(divide(1, 3), 0.333333, places=6) with self.assertRaises(ValueError): divide(1, 0) if __name__ == "__main__": unittest.main()11.3 使用pytest更简洁地测试
# test_math.py import pytest def test_add(): assert add(2, 3) == 5 assert add(-1, 1) == 0 def test_divide(): assert divide(1, 3) == pytest.approx(0.333333, rel=1e-6) with pytest.raises(ValueError): divide(1, 0)运行测试:
pytest test_math.py -v12. 函数调试与性能分析
12.1 使用logging记录函数行为
import logging logging.basicConfig(level=logging.DEBUG) def complex_calculation(x, y): logging.debug(f"开始计算,x={x}, y={y}") try: result = x**2 + y**2 logging.info(f"计算结果:{result}") return result ** 0.5 except Exception as e: logging.error(f"计算出错:{str(e)}") raise12.2 使用cProfile分析函数性能
import cProfile def slow_function(): return sum(i*i for i in range(1000000)) cProfile.run('slow_function()', sort='cumulative')输出示例:
4 function calls in 0.100 seconds Ordered by: cumulative time ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.100 0.100 <string>:1(<module>) 1 0.100 0.100 0.100 0.100 test.py:2(slow_function) 1 0.000 0.000 0.000 0.000 {built-in method builtins.sum} 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}12.3 使用timeit测量小段代码
from timeit import timeit def test_list_comprehension(): return [x*x for x in range(1000)] def test_map_lambda(): return list(map(lambda x: x*x, range(1000))) print("列表推导式:", timeit(test_list_comprehension, number=1000)) print("map+lambda:", timeit(test_map_lambda, number=1000))13. 函数设计模式实战
13.1 策略模式:用函数实现
def pay_credit(amount): print(f"信用卡支付 {amount} 元") def pay_alipay(amount): print(f"支付宝支付 {amount} 元") def pay_wechat(amount): print(f"微信支付 {amount} 元") class Order: def __init__(self, payment_strategy=pay_alipay): self.payment_strategy = payment_strategy def process_payment(self, amount): self.payment_strategy(amount) order = Order(pay_wechat) order.process_payment(100) # 微信支付 100 元13.2 工厂模式:返回函数的函数
def create_greeter(greeting): def greeter(name): print(f"{greeting}, {name}!") return greeter say_hello = create_greeter("Hello") say_hi = create_greeter("Hi") say_hello("World") # Hello, World! say_hi("Python") # Hi, Python!13.3 装饰器实现单例模式
def singleton(cls): instances = {} def wrapper(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper @singleton class Logger: pass logger1 = Logger() logger2 = Logger() print(logger1 is logger2) # True14. 函数与元编程
14.1 动态创建函数
def create_arithmetic_function(op): if op == 'add': return lambda x, y: x + y elif op == 'sub': return lambda x, y: x - y else: raise ValueError("未知操作") adder = create_arithmetic_function('add') print(adder(2, 3)) # 514.2 函数自省:查看函数属性
def example(a, b=1, *args, **kwargs): """示例函数""" pass print(example.__name__) # example print(example.__doc__) # 示例函数 print(example.__code__.co_varnames) # ('a', 'b', 'args', 'kwargs') print(example.__defaults__) # (1,)14.3 修改函数行为
def original(): print("原始函数") def decorated(): print("装饰前") original() print("装饰后") original = decorated original() # 输出: # 装饰前 # 原始函数 # 装饰后15. 函数安全与最佳实践
15.1 参数验证装饰器
def validate_input(*validations): def decorator(func): def wrapper(*args, **kwargs): for i, (arg, validation) in enumerate(zip(args, validations)): if not validation(arg): raise ValueError(f"参数{i}验证失败") return func(*args, **kwargs) return wrapper return decorator @validate_input(lambda x: x > 0, lambda x: isinstance(x, str)) def process_data(num, text): return text * num print(process_data(3, "Hi")) # HiHiHi15.2 防止递归爆栈
递归函数需要设置合理的终止条件和最大深度:
import sys def safe_recursion(func): def wrapper(*args, **kwargs): wrapper.depth += 1 if wrapper.depth > sys.getrecursionlimit() // 2: raise RecursionError("超过最大递归深度") try: return func(*args, **kwargs) finally: wrapper.depth -= 1 wrapper.depth = 0 return wrapper @safe_recursion def factorial(n): return 1 if n == 0 else n * factorial(n-1)15.3 函数权限控制
def admin_required(func): def wrapper(user, *args, **kwargs): if not user.is_admin: raise PermissionError("需要管理员权限") return func(user, *args, **kwargs) return wrapper @admin_required def delete_user(admin, username): print(f"用户 {username} 已被 {admin.name} 删除")