1. 函数基础练习题:从零到精通的实战指南
函数作为编程中最基础也最重要的概念之一,掌握它就像学会骑自行车一样——一旦理解就永远不会忘记。但很多初学者在函数练习时常常陷入"看代码能懂,自己写就懵"的困境。今天我们就通过一系列精心设计的练习题,带你真正吃透函数的本质。
提示:建议在阅读时打开你的代码编辑器,边看边动手实践。函数的学习没有捷径,只有通过大量练习才能形成肌肉记忆。
2. 函数基础概念回顾与热身
2.1 为什么函数如此重要?
想象你是一家餐厅的老板。如果没有函数,就像每次有客人点餐时,你都要从头开始准备食材、烹饪、摆盘。而有了函数,你可以把"制作汉堡"这个过程封装起来,只需要告诉厨房"做一个汉堡"(调用函数),而不必关心具体怎么做。这就是函数的威力——封装重复逻辑,提高代码复用性。
2.2 函数的三大核心要素
每个完整的函数都包含以下关键部分:
- 函数定义:使用
def关键字声明 - 参数列表:函数接收的输入(可以是零个或多个)
- 函数体:实现具体功能的代码块
- 返回值:通过
return返回结果(可选)
python复制# 典型函数结构示例
def greet(name): # name是参数
message = f"Hello, {name}!" # 函数体
return message # 返回值
3. 必做基础练习题精解
3.1 温度转换器
题目:编写一个函数将摄氏温度转换为华氏温度。转换公式:F = C × 9/5 + 32
python复制def celsius_to_fahrenheit(celsius):
fahrenheit = celsius * 9/5 + 32
return round(fahrenheit, 2) # 保留两位小数
# 测试用例
print(celsius_to_fahrenheit(0)) # 应输出32.0
print(celsius_to_fahrenheit(100)) # 应输出212.0
常见错误:
- 忘记写return语句(函数会返回None)
- 公式写错顺序(如先加32再乘9/5)
- 没有处理小数位数导致输出过长
3.2 偶数判断器
题目:编写一个函数判断数字是否为偶数,返回True/False
python复制def is_even(number):
return number % 2 == 0 # 直接返回布尔表达式结果
# 更完整的写法(考虑异常情况)
def is_even_robust(number):
if not isinstance(number, int):
raise TypeError("输入必须是整数")
return number % 2 == 0
进阶思考:
- 如何处理非整数输入?(如字符串、浮点数)
- 负数是否也能正确判断?
- 为什么直接返回布尔表达式比用if-else更简洁?
4. 中级函数练习题实战
4.1 字符串反转函数
题目:不借助[::-1]等高级特性,手动实现字符串反转
python复制def reverse_string(text):
reversed_text = ""
for char in text:
reversed_text = char + reversed_text # 关键步骤!
return reversed_text
# 测试
print(reverse_string("hello")) # 输出"olleh"
原理解析:
这个实现展示了函数如何封装复杂逻辑。循环中每次将新字符加到已有字符串前面,就像堆叠积木一样从底部开始重建。
4.2 质数判断函数
题目:编写函数判断一个数是否为质数
python复制def is_prime(n):
if n <= 1:
return False
for i in range(2, int(n**0.5)+1):
if n % i == 0:
return False
return True
# 优化版本(跳过偶数检查)
def is_prime_optimized(n):
if n <= 1:
return False
if n == 2:
return True
if n % 2 == 0:
return False
for i in range(3, int(n**0.5)+1, 2): # 只检查奇数
if n % i == 0:
return False
return True
性能对比:
- 基础版本检查2到√n的所有数
- 优化版本跳过偶数检查,减少约50%计算量
- 对于大数(如10^6+3),优化版本速度明显更快
5. 高级函数技巧与应用
5.1 默认参数与关键字参数
题目:创建个人信息格式化函数,带有默认参数
python复制def format_profile(name, age, city="未知", country="中国"):
return f"{name}({age}),来自{country}{city}"
# 多种调用方式
print(format_profile("张三", 25)) # 使用默认城市和国家
print(format_profile("李四", 30, city="北京"))
print(format_profile(country="美国", name="王五", age=40, city="纽约"))
关键知识点:
- 默认参数必须放在非默认参数后面
- 关键字参数允许任意顺序传参
- 默认参数在函数定义时求值(小心可变默认参数!)
5.2 可变参数:*args和**kwargs
题目:实现通用计算器函数,支持任意数量参数
python复制def calculator(op, *args):
if not args:
return 0
if op == '+':
return sum(args)
elif op == '*':
result = 1
for num in args:
result *= num
return result
# 可以继续扩展其他运算符...
# 使用示例
print(calculator('+', 1, 2, 3, 4)) # 输出10
print(calculator('*', 2, 3, 5)) # 输出30
实际应用场景:
- 日志记录函数(不定量参数)
- 数据库查询构造器
- 装饰器开发
6. 函数最佳实践与调试技巧
6.1 如何设计好的函数?
-
单一职责原则:一个函数只做一件事
- 反例:
process_data_and_save_to_file() - 正例:
clean_data()+save_to_file()
- 反例:
-
合理的参数设计:
- 不超过5个参数(太多考虑用对象封装)
- 布尔参数慎用(容易造成理解困难)
-
清晰的命名:
- 动词开头:
get_...,calculate_...,validate_... - 避免模糊名称:
handle(),process()
- 动词开头:
6.2 常见函数错误排查
问题1:函数修改了传入的列表
python复制def append_number(nums, n):
nums.append(n)
numbers = [1, 2, 3]
append_number(numbers, 4)
print(numbers) # 输出[1, 2, 3, 4] 原列表被修改!
解决方案:
- 文档中明确说明函数是否会修改参数
- 必要时创建副本:
nums = list(nums)
问题2:可变默认参数的陷阱
python复制def add_item(item, items=[]): # 默认参数在定义时创建
items.append(item)
return items
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] 不是预期的[2]!
正确写法:
python复制def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
7. 综合实战项目:简易通讯录系统
让我们用函数构建一个完整的通讯录管理系统:
python复制contacts = []
def add_contact(name, phone, email=""):
contacts.append({
'name': name,
'phone': phone,
'email': email
})
print(f"已添加联系人:{name}")
def find_contact(name):
for contact in contacts:
if contact['name'] == name:
return contact
return None
def list_contacts():
print("\n通讯录列表:")
for idx, contact in enumerate(contacts, 1):
print(f"{idx}. {contact['name']}: {contact['phone']}")
# 使用示例
add_contact("张三", "13800138000")
add_contact("李四", "13900139000", "lisi@example.com")
list_contacts()
found = find_contact("张三")
if found:
print(f"找到联系人:{found}")
扩展建议:
- 添加删除联系人功能
- 实现联系人编辑功能
- 增加按电话号码搜索
- 添加数据持久化(保存到文件)
8. 函数式编程初探
虽然Python不是纯函数式语言,但支持一些函数式特性:
8.1 lambda匿名函数
python复制square = lambda x: x ** 2
print(square(5)) # 输出25
# 常用于排序等场景
names = ["Alice", "Bob", "Charlie"]
names.sort(key=lambda name: len(name))
print(names) # 按长度排序:['Bob', 'Alice', 'Charlie']
8.2 map/filter/reduce
python复制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))
from functools import reduce
# reduce: 累积计算
sum_all = reduce(lambda x, y: x + y, numbers)
适用场景:
- 数据转换流水线
- 集合元素批量处理
- 简洁的条件过滤
9. 类型提示与函数文档
9.1 类型注解(Python 3.5+)
python复制def calculate_tip(bill_amount: float, tip_percent: float = 0.15) -> float:
"""计算小费金额
Args:
bill_amount: 账单金额
tip_percent: 小费百分比(默认15%)
Returns:
小费金额
"""
return bill_amount * tip_percent
9.2 良好的文档字符串实践
python复制def find_max(numbers):
"""找出列表中的最大值
示例:
>>> find_max([3, 1, 4, 1, 5])
5
参数:
numbers: 数字列表
返回:
列表中的最大值,如果列表为空则返回None
"""
if not numbers:
return None
return max(numbers)
文档字符串标准:
- Google风格(如上例)
- reStructuredText风格
- NumPy风格
10. 单元测试与函数验证
为你的函数编写测试是专业开发的重要习惯:
python复制import unittest
class TestMathFunctions(unittest.TestCase):
def test_celsius_to_fahrenheit(self):
self.assertEqual(celsius_to_fahrenheit(0), 32)
self.assertEqual(celsius_to_fahrenheit(100), 212)
self.assertAlmostEqual(celsius_to_fahrenheit(37), 98.6, places=1)
def test_is_prime(self):
self.assertTrue(is_prime(2))
self.assertTrue(is_prime(17))
self.assertFalse(is_prime(1))
self.assertFalse(is_prime(9))
if __name__ == '__main__':
unittest.main()
测试要点:
- 覆盖边界条件(如0、负数、空输入)
- 包含典型用例和异常用例
- 测试函数组合使用的情况
- 定期运行测试(最好自动化)
11. 性能优化技巧
11.1 缓存装饰器示例
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))
# 第二次直接从缓存读取
print(fibonacci(50))
11.2 避免不必要的计算
python复制# 不推荐:每次调用都重新计算
def get_report(date):
data = query_database() # 耗时操作
return process_data(data)
# 推荐:缓存数据
cached_data = None
def get_report_optimized(date):
global cached_data
if cached_data is None:
cached_data = query_database()
return process_data(cached_data)
12. 函数设计模式
12.1 工厂函数模式
python复制def create_greeter(greeting):
def greeter(name):
return f"{greeting}, {name}!"
return greeter
say_hello = create_greeter("Hello")
say_hi = create_greeter("Hi")
print(say_hello("Alice")) # "Hello, Alice!"
print(say_hi("Bob")) # "Hi, Bob!"
12.2 策略模式
python复制def calculate_total(strategy, items):
return strategy(items)
def normal_pricing(items):
return sum(item['price'] for item in items)
def discount_pricing(items):
total = normal_pricing(items)
return total * 0.9 if total > 100 else total
items = [{'name': 'book', 'price': 50}, {'name': 'pen', 'price': 10}]
print(calculate_total(normal_pricing, items)) # 60
print(calculate_total(discount_pricing, items)) # 54 (打了9折)
13. 闭包与装饰器进阶
13.1 计时装饰器实现
python复制import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 执行时间: {end-start:.4f}秒")
return result
return wrapper
@timer
def slow_function():
time.sleep(2)
slow_function() # 输出执行时间
13.2 带参数的装饰器
python复制def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(num_times=3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice") # 打印三次问候
14. 异步函数入门
Python的async/await语法:
python复制import asyncio
async def fetch_data():
print("开始获取数据")
await asyncio.sleep(2) # 模拟IO操作
print("数据获取完成")
return {"data": 123}
async def main():
task = asyncio.create_task(fetch_data())
print("可以同时做其他事情")
data = await task
print(f"获取到的数据: {data}")
asyncio.run(main())
关键概念:
async def定义异步函数await暂停当前协程,直到awaitable完成asyncio.create_task()调度协程执行- 事件循环管理所有异步任务
15. 函数调试技巧
15.1 使用pdb调试器
python复制import pdb
def complex_calculation(a, b):
pdb.set_trace() # 设置断点
result = a * b
result += 100
return result / (a + b)
print(complex_calculation(10, 20))
常用pdb命令:
n(ext):执行下一行s(tep):进入函数调用c(ontinue):继续执行直到下一个断点l(ist):查看当前代码上下文p(rint):打印变量值
15.2 日志调试法
python复制import logging
logging.basicConfig(level=logging.DEBUG)
def process_data(data):
logging.debug(f"开始处理数据,长度: {len(data)}")
try:
result = sum(data) / len(data)
logging.info(f"计算完成,结果: {result}")
return result
except Exception as e:
logging.error(f"处理数据时出错: {str(e)}")
raise
process_data([1, 2, 3])
16. 函数版本兼容性设计
16.1 参数兼容处理
python复制def draw_chart(data, color="blue", **kwargs):
# 老版本只使用color参数
# 新版本可以通过kwargs接收额外参数而不破坏旧代码
if "line_style" in kwargs:
print(f"绘制{color}色{kwargs['line_style']}线型图表")
else:
print(f"绘制{color}色实线图表")
# 新旧调用方式都兼容
draw_chart([1,2,3], color="red")
draw_chart([1,2,3], color="green", line_style="虚线")
16.2 弃用警告
python复制import warnings
def old_function():
warnings.warn(
"old_function已弃用,请使用new_function代替",
DeprecationWarning,
stacklevel=2
)
# 旧实现...
17. 函数安全注意事项
17.1 避免eval注入风险
python复制# 危险!不要这样做
def unsafe_calculate(expression):
return eval(expression)
# 安全替代方案
import ast
import operator
def safe_calculate(expression):
allowed_operators = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv
}
node = ast.parse(expression, mode='eval')
def evaluate(node):
if isinstance(node, ast.Num):
return node.n
elif isinstance(node, ast.BinOp):
left = evaluate(node.left)
right = evaluate(node.right)
return allowed_operators[type(node.op)](left, right)
else:
raise ValueError("不支持的表达式")
return evaluate(node.body)
17.2 输入验证
python复制def process_age(age):
if not isinstance(age, int):
raise TypeError("年龄必须是整数")
if age < 0 or age > 120:
raise ValueError("年龄必须在0-120之间")
return f"你的年龄是{age}岁"
18. 函数与面向对象结合
18.1 类方法装饰器
python复制class Circle:
def __init__(self, radius):
self.radius = radius
@classmethod
def from_diameter(cls, diameter):
return cls(diameter / 2)
@property
def area(self):
return 3.14 * self.radius ** 2
# 使用类方法创建实例
circle = Circle.from_diameter(10)
print(circle.radius) # 5.0
print(circle.area) # 78.5
18.2 静态方法
python复制class StringUtils:
@staticmethod
def is_palindrome(text):
text = text.lower().replace(" ", "")
return text == text[::-1]
print(StringUtils.is_palindrome("Madam")) # True
19. 函数性能分析
19.1 使用timeit测量
python复制import timeit
def test_func():
return sum(range(10000))
time = timeit.timeit(test_func, number=1000)
print(f"平均执行时间: {time/1000:.6f}秒")
19.2 cProfile分析
python复制import cProfile
def slow_function():
total = 0
for i in range(10000):
for j in range(100):
total += i * j
return total
cProfile.run('slow_function()')
20. 函数设计思维训练
20.1 函数拆分练习
原始代码:
python复制def process_data(data):
# 验证数据
if not isinstance(data, list):
raise TypeError("需要列表输入")
if len(data) == 0:
raise ValueError("数据不能为空")
# 计算统计量
total = sum(data)
average = total / len(data)
maximum = max(data)
minimum = min(data)
# 格式化输出
report = f"""
数据统计报告:
条目数: {len(data)}
总和: {total}
平均值: {average:.2f}
最大值: {maximum}
最小值: {minimum}
"""
return report
重构后:
python复制def validate_data(data):
if not isinstance(data, list):
raise TypeError("需要列表输入")
if len(data) == 0:
raise ValueError("数据不能为空")
def calculate_stats(data):
return {
'count': len(data),
'total': sum(data),
'average': sum(data) / len(data),
'max': max(data),
'min': min(data)
}
def format_report(stats):
return f"""
数据统计报告:
条目数: {stats['count']}
总和: {stats['total']}
平均值: {stats['average']:.2f}
最大值: {stats['max']}
最小值: {stats['min']}
"""
def process_data_refactored(data):
validate_data(data)
stats = calculate_stats(data)
return format_report(stats)
重构优点:
- 每个函数只做一件事
- 更容易单独测试每个组件
- 代码复用性更高
- 修改统计计算不影响验证和格式化逻辑
20.2 函数组合练习
题目:用现有函数组合出新功能
python复制# 已有函数
def get_employees():
return ["Alice", "Bob", "Charlie"]
def get_salary(name):
salaries = {"Alice": 5000, "Bob": 6000, "Charlie": 4500}
return salaries.get(name, 0)
def format_currency(amount):
return f"${amount:,.2f}"
# 组合实现:打印员工薪资报表
def print_salary_report():
for employee in get_employees():
salary = get_salary(employee)
print(f"{employee}: {format_currency(salary)}")
print_salary_report()
设计要点:
- 利用已有函数构建新功能
- 避免重复造轮子
- 保持函数职责单一
