基础搭建-python基础
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
=====================================
Python 基础语法与算法综合案例 (Study)
=====================================
本文件系统涵盖 Python 核心知识点:
1. 装饰器模式 (Decorator Pattern) —— 函数增强技术
2. 数学计算与条件分支 —— 一元二次方程求根
3. 可变默认参数陷阱 —— Python 经典"坑"
4. 可变位置参数 *args —— 灵活参数接收
5. 循环嵌套与格式化输出 —— 9×9 乘法表
6. 递归算法 —— 汉诺塔 (经典分治思想)
7. 列表操作与动态规划 —— 杨辉三角形
学习建议:
- 先理解每个独立模块的原理
- 关注代码中的注释标注 [★重点] 和 [!注意]
- 尝试自己实现后再看参考答案
作者:bloxed
日期:2026-03-18
"""
import math
from functools import reduce
import time
# ============================================================
# 模块一:装饰器 (Decorator) —— 函数计时器
# ============================================================
# [★核心概念] 装饰器是一种设计模式,用于在不修改原函数代码的情况下,
# 为函数添加额外功能(如日志、缓存、权限验证、性能监控等)
# 本质是一个高阶函数:接受函数作为参数,返回一个新函数
def timeit(func):
"""
计时装饰器:测量被装饰函数的执行时间
工作原理:
┌─────────────────────────────────────────┐
│ @timeit │
│ def my_func(): │
│ ... │
│ │
│ 等价于: my_func = timeit(my_func) │
│ 调用 my_func() 实际调用的是 wrapper() │
└─────────────────────────────────────────┘
参数:
func: 被装饰的函数对象
返回:
wrapper: 包装后的新函数
[!注意] 使用 functools.wraps 可以保留原函数的元信息
(如 __name__, __doc__ 等),这里为了简洁省略
"""
def wrapper(*args, **kwargs):
"""内部包装函数,在执行前后分别记录时间"""
start_time = time.perf_counter() # perf_counter 比 time.time() 更精确
result = func(*args, **kwargs) # 执行原函数,保留返回值
end_time = time.perf_counter()
print(f"[timeit] {func.__name__} 执行耗时: {end_time - start_time:.4f} 秒")
return result # [★关键] 必须返回原函数结果,否则会丢失返回值
return wrapper
# ============================================================
# 模块二:一元二次方程求解
# ============================================================
# [数学背景] ax² + bx + c = 0 的解由求根公式给出:
# _______
# -b ± √b²-4ac
# x = ─────────────
# 2a
def quadratic(a, b, c):
"""
求解一元二次方程 ax² + bx + c = 0 的实数根
参数:
a: 二次项系数 (不能为0, 否则退化为一次方程)
b: 一次项系数
c: 常数项
返回:
无实数解 → None
一个解(重根) → float
两个不同解 → tuple(float, float)
[★知识] 判别式 Δ = b² - 4ac 决定了解的情况:
Δ > 0 → 两个不等实数根
Δ = 0 → 两个相等实数根 (重根)
Δ < 0 → 无实数根 (有共轭复数根)
示例:
>>> quadratic(1, -3, 2) # x²-3x+2=0 → x=1 或 x=2
(2.0, 1.0)
>>> quadratic(1, 2, 1) # x²+2x+1=0 → x=-1 (重根)
-1.0
"""
discriminant = b**2 - 4*a*c # 计算判别式 Δ
if discriminant < 0:
return None # 无实数解 (Δ < 0)
elif discriminant == 0:
return -b / (2*a) # 重根 (Δ = 0), x = -b/(2a)
else:
sqrt_disc = math.sqrt(discriminant) # 开方
# [★] 同时返回两个根: (-b+√Δ)/2a 和 (-b-√Δ)/2a
return ((-b + sqrt_disc) / (2*a), (-b - sqrt_disc) / (2*a))
# ============================================================
# 模块三:可变默认参数陷阱
# ============================================================
# [!!!经典面试题] Python 函数的可变对象默认值只会在定义时创建一次!
#
# 错误写法: def add_end(L=[]): ← [] 只在函数定义时创建一次
# 后续所有不传参的调用都会共享同一个列表对象!
#
# 正确写法: def add_end(L=None): ← None 是不可变的安全默认值
def add_end(L=None):
"""
向列表末尾追加 'END' 字符串
[!重要] 这是 Python 可变默认参数的经典反例/正例教学
错误演示 (不要这样写):
>>> def bad_add_end(L=[])
... L.append('END')
... return L
>>> bad_add_end() # ['END']
>>> bad_add_end() # ['END', 'END'] !! 第二次调用结果不对!
>>>
原因: 默认的 [] 是同一个对象,多次调用会累积
正确做法 (当前实现):
使用 None 作为默认值,每次调用时创建新的空列表
参数:
L: 列表,默认为None时自动创建新列表
追回:
追加了'END'的新列表
"""
if L is None: # [★] 用 None 作为哨兵值,每次都创建新列表
L = []
L.append('END')
return L
# ============================================================
# 模块四:可变位置参数 *args
# ============================================================
# [★核心概念] *numbers 会将所有传入的位置参数打包成一个元组 (tuple)
# 这让函数可以接受任意数量的参数
def calc(*numbers):
"""
计算任意数量数字的平方和: Σ(x²)
参数:
*numbers: 可变数量的数值参数
返回:
所有参数的平方之和
[★用法演示]:
>>> calc(1, 2, 3) # 1² + 2² + 3² = 14
14
>>> calc(10) # 10² = 100
100
>>> calc() # 无参数 → sum([]) = 0
0
>>> calc(*[1, 2, 3]) # 解包列表作为参数传入
14
相关语法:
*args → 接收任意位置参数,打包为元组
**kwargs → 接收任意关键字参数,打包为字典
"""
return sum(x**2 for x in numbers) # 生成器表达式,内存效率高
# ============================================================
# 模块五:9×9 乘法表 —— 格式化输出与循环嵌套
# ============================================================
@timeit # [★] 装饰器语法糖,等价于 multiplication_table = timeit(multiplication_table)
def multiplication_table():
"""
打印 9×9 乘法表 (中国小学经典练习)
输出格式:
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
...
1*9=9 ... 9*9=81
[★知识点]:
1. for-else/嵌套循环: 外层控制行(i),内层控制列(j≤i)
2. f-string 格式化: f"{j}*{i}={i*j}" — Python 3.6+
3. end=' ': print 默认换行,end='' 可改为其他结尾
4. @装饰器: 在不改变函数体的情况下附加功能
"""
for i in range(1, 10): # 外层循环:被乘数 1~9
for j in range(1, i + 1): # 内层循环:乘数 1~i (形成下三角结构)
print(f"{j}*{i}={i*j}", end=' ') # 不换行,用空格分隔
print() # 每行结束后换行
# 执行乘法表 (带计时)
print("=" * 50)
print("模块五: 9×9 乘法表")
print("=" * 50)
multiplication_table()
# ============================================================
# 模块六:汉诺塔 (Tower of Hanoi) —— 递归算法经典
# ============================================================
# [算法背景]
# 有三根柱子 A/B/C,A柱上有n个大小不同的盘子(大的在下小的在上)
# 目标:将所有盘子从A移动到C,规则:
# 1. 每次只能移动一个盘子
# 2. 大盘子不能放在小盘子上
#
# [★递归思想 - 分治法 Divide & Conquer]
# 要把 n 个盘子从 A→C (借助 B):
# Step 1: 把上面 n-1 个盘子 A→B (借助 C) [递归]
# Step 2: 把第 n 号盘子(最大的) A→C [直接完成]
# Step 3: 把 n-1 个盘子 B→C (借助 A) [递归]
#
# 时间复杂度: O(2^n) — n个盘子最少需要 2^n - 1 步
# 例如: 64个盘子 ≈ 1.84×10^19 步 (宇宙年龄级别!)
move_count = 0 # [!] 全局变量统计移动次数 (更好的方式是闭包或类属性)
def hanoi(n, source, target, auxiliary):
"""
汉诺塔递归求解
参数:
n: 当前要移动的盘子数量 (从大到小编号)
source: 来源柱子
target: 目标柱子
auxiliary: 辅助柱子
[递归终止条件]: n == 1 时直接移动 (最小子问题)
[递归关系式]: T(n) = 2*T(n-1) + 1, T(1) = 1
解得: T(n) = 2^n - 1
"""
global move_count # [!] 使用全局变量计数 (非最佳实践,但便于理解)
if n == 1:
# [基准情况 Base Case] 只剩一个盘子,直接移到目标
move_count += 1
print(f"第{move_count:2d}步: 盘子1 {source} → {target}")
else:
# [递归情况 Recursive Case] 分三步走
# Step 1: 先把上面的 n-1 个盘子移到辅助柱
hanoi(n - 1, source, auxiliary, target)
# Step 2: 把当前最大的盘子移到目标柱
move_count += 1
print(f"第{move_count:2d}步: 盘子{n} {source} → {target}")
# Step 3: 再把辅助柱上的 n-1 个盘子移到目标柱
hanoi(n - 1, auxiliary, target, source)
# 测试汉诺塔 (小规模演示)
print("\n" + "=" * 50)
print("模块六: 汉诺塔 (3个盘子)")
print("=" * 50)
move_count = 0 # 重置计数器
hanoi(3, 'A', 'C', 'B') # 3个盘子: A→C, B辅助, 共需 2³-1=7 步
print(f"\n总计移动 {move_count} 步 (理论值: 2^3-1 = {2**3-1})")
# ============================================================
# 模块七:杨辉三角形 (Pascal's Triangle) —— 组合数学与动态规划
# ============================================================
# [数学背景]
# 杨辉三角的第 n 行对应二项式 (a+b)^n 展开式的系数
# 每个数等于它上方两数之和: C(n,k) = C(n-1,k-1) + C(n-1,k)
#
# 结构示意 (前5行):
# 1 (n=0)
# 1 1 (n=1)
# 1 2 1 (n=2)
# 1 3 3 1 (n=3)
# 1 4 6 4 1 (n=4)
#
# [★性质]:
# 1. 第n行有 n+1 个元素
# 2. 每行首尾都是 1
# 3. 对称性: 第k个元素 = 第(n-k+1)个元素
# 4. 第n行元素之和 = 2^n
def pascal_triangle(n):
"""
生成杨辉三角形的前 n 行
参数:
n: 需要生成的行数
返回:
triangle: 二维列表,包含每一行的数据
[算法思路 - 动态规划]:
每行的值依赖上一行:
triangle[i][j] = triangle[i-1][j-1] + triangle[i-1][j]
(首尾元素恒为1,不需要计算)
时间复杂度: O(n²)
空间复杂度: O(n²) (存储完整三角形)
[进阶优化] 可以只用 O(n) 空间,只保留上一行即可
"""
triangle = []
for i in range(n): # i: 当前行号 (0-indexed)
row = [1] * (i + 1) # [★技巧] 先初始化全1,再更新中间值
for j in range(1, i): # j=0 和 j=i 已经是1,只需计算中间
# [★核心公式] 每个数等于上方两数之和
row[j] = triangle[i - 1][j - 1] + triangle[i - 1][j]
triangle.append(row)
print(row) # 打印当前行
return triangle
# 测试杨辉三角 (打印前10行)
print("\n" + "=" * 50)
print("模块七: 杨辉三角形 (前10行)")
print("=" * 50)
pascal_triangle(10)
# ============================================================
# 附加: 快速验证 quadratic 函数
# ============================================================
print("\n" + "=" * 50)
print("附加: 一元二次方程验证")
print("=" * 50)
test_cases = [
(1, -3, 2, "x^2-3x+2=0 (two real roots: 1, 2)"),
(1, 2, 1, "x^2+2x+1=0 (double root: -1)"),
(1, 1, 1, "x^2+x+1=0 (no real root)"),
]
for a, b, c, desc in test_cases:
result = quadratic(a, b, c)
print(f" {desc}")
if result is None:
print(f" Result: No real roots (discriminant < 0)")
elif isinstance(result, tuple):
print(f" Result: x1 = {result[0]}, x2 = {result[1]}")
else:
print(f" Result: x = {result} (double root)")
print()
print("=" * 50)
print("全部模块运行完毕!")
print("=" * 50)
781

被折叠的 条评论
为什么被折叠?



