🐍 函数基础 — 代码的乐高积木
🕐 预计用时:2-3 小时 | 🎯 目标:掌握 def、参数类型、return 和文档字符串
📖 今日目录
1. 为什么要用函数?
函数就是"打包好的代码"——写一次,用无数次。
想象你每天早上要做三件事:泡咖啡、烤面包、煎鸡蛋。没有函数的话,每天都要重复写一遍步骤。有了函数,你只需要说一声"做早餐",三件事自动完成。
# ❌ 没有函数:重复代码print("=" * 30)print("欢迎来到商店A")print("=" * 30)# ... 100行代码后 ...print("=" * 30)print("欢迎来到商店B")print("=" * 30)# ✅ 有函数:复用代码def print_welcome(name): print("=" * 30) print(f"欢迎来到{name}") print("=" * 30)print_welcome("商店A")print_welcome("商店B")
2. 定义和调用函数
📖 基本语法
# 定义函数def greet(): print("你好!") print("欢迎光临!")# 调用函数greet() # 你好! 欢迎光临!greet() # 可以调用无数次
函数定义的四个要素:
| | |
|---|
def | | def greet(): |
| | greet |
| | (name) |
| | print("hi") |
# 定义 vs 调用def say_hello(name): # ← 定义(不执行代码) print(f"Hello, {name}!")say_hello("张三") # ← 调用(才执行代码)say_hello("李四") # ← 再次调用
💡 常见新手错误:定义了函数但忘了调用。def greet(): print("hi") ← 这行什么都不会打印!必须写 greet() 才会执行。
3. 参数:位置参数
位置参数是最基本的参数类型——按顺序传入。
# 一个参数def greet(name): print(f"你好,{name}!")greet("张三") # 你好,张三!greet("李四") # 你好,李四!# 多个参数def introduce(name, age, city): print(f"我叫{name},{age}岁,来自{city}")introduce("张三", 25, "北京") # 顺序很重要!introduce("北京", 25, "张三") # ❌ 顺序错了,意思全变
🔢 参数数量检查
def add(a, b): return a + badd(1, 2) # ✅ 正确# add(1) # ❌ TypeError: 缺少参数# add(1, 2, 3) # ❌ TypeError: 参数太多
4. 参数:默认值
给参数一个"备胎"值——调用时不传就用默认值。
# 有默认值的参数def greet(name, greeting="你好"): print(f"{greeting},{name}!")greet("张三") # 你好,张三!(用默认值)greet("张三", "早上好") # 早上好,张三!(用传入的值)# 多个默认值def create_user(name, age=18, city="未知", role="学生"): print(f"用户: {name}, 年龄: {age}, 城市: {city}, 角色: {role}")create_user("张三") # 全部用默认值create_user("李四", 25) # 只改年龄create_user("王五", city="上海") # 跳过 age,改城市(关键字参数)create_user("赵六", 30, "广州", "老师") # 全部自定义
⚠️ 重要规则:有默认值的参数必须放在没有默认值的参数后面!
def greet(name, greeting="你好"): # ✅ 正确def greet(greeting="你好", name): # ❌ SyntaxError!
原因:Python 从左到右匹配参数,如果默认值在前面,它不知道哪个值传给谁。
⚠️ 默认值的陷阱:可变对象
# ❌ 危险:默认值用可变对象(列表、字典)def add_item(item, items=[]): items.append(item) return itemsprint(add_item("苹果")) # ['苹果']print(add_item("香蕉")) # ['苹果', '香蕉'] ← 咩?!print(add_item("橘子")) # ['苹果', '香蕉', '橘子'] ← 更离谱!# 原因:默认列表只创建一次,所有调用共享同一个列表对象
# ✅ 正确写法:用 None 作为默认值def add_item(item, items=None): if items is None: items = [] # 每次调用都创建新列表 items.append(item) return itemsprint(add_item("苹果")) # ['苹果']print(add_item("香蕉")) # ['香蕉'] ← 正确了!
💡 黄金法则:函数的默认值永远不要用可变对象(列表、字典、集合)。用 None 代替,在函数体内再创建。
5. 参数:关键字参数
关键字参数让你可以"指名道姓"地传值,不用管顺序。
def create_profile(name, age, city, hobby): print(f"姓名: {name}, 年龄: {age}, 城市: {city}, 爱好: {hobby}")# 位置参数:按顺序create_profile("张三", 25, "北京", "编程")# 关键字参数:按名字create_profile(hobby="编程", name="张三", city="北京", age=25)# 混合使用:位置参数在前,关键字参数在后create_profile("张三", 25, hobby="编程", city="北京")
💡 什么时候用关键字参数?1. 参数多、容易搞混顺序时2. 调用不常见的函数时(提高可读性)3. 有默认值、只想改其中几个时
6. return 返回值
print 是给人看的,return 是给程序用的。
# 没有 return 的函数,返回 Nonedef greet(name): print(f"你好,{name}")result = greet("张三") # 打印: 你好,张三print(result) # None ← 函数没有 return,返回 None# 有 return 的函数,返回值def add(a, b): return a + bresult = add(3, 5)print(result) # 8 ← return 的值被拿到
📦 return 的用法
# 返回单个值def square(n): return n * nprint(square(5)) # 25# 返回多个值(实际返回一个元组)def min_max(numbers): return min(numbers), max(numbers)lo, hi = min_max([3, 1, 4, 1, 5, 9])print(f"最小: {lo}, 最大: {hi}") # 最小: 1, 最大: 9# 返回不同类型的值def divide(a, b): if b == 0: return None # 除以零返回 None return a / b # 正常返回结果print(divide(10, 3)) # 3.333...print(divide(10, 0)) # None# 提前返回(early return)def check_age(age): if age < 0: return "年龄不能为负数" # 提前退出 if age < 18: return "未成年" if age < 65: return "成年人" return "老年人"print(check_age(25)) # 成年人print(check_age(-5)) # 年龄不能为负数
💡 print vs return 区别:
7. 文档字符串(docstring)
docstring 是函数的"说明书"——告诉别人(和未来的自己)这个函数干什么。
def calculate_bmi(weight, height): """ 计算 BMI(身体质量指数) 参数: weight (float): 体重,单位:千克 height (float): 身高,单位:米 返回: float: BMI 值 示例: >>> calculate_bmi(70, 1.75) 22.86 """ bmi = weight / (height ** 2) return round(bmi, 2)# 查看文档help(calculate_bmi)print(calculate_bmi.__doc__)# 使用result = calculate_bmi(70, 1.75)print(f"BMI: {result}") # BMI: 22.86
📝 docstring 的格式
# 单行 docstring(简单函数)def double(n): """返回 n 的两倍。""" return n * 2# 多行 docstring(复杂函数)def process_data(data, threshold=0.5, mode="filter"): """ 处理数据并返回结果。 根据阈值和模式对数据进行过滤或转换。 Args: data: 输入数据列表 threshold: 阈值,默认 0.5 mode: 处理模式,'filter' 或 'transform' Returns: list: 处理后的数据 Raises: ValueError: 当 mode 不是 'filter' 或 'transform' 时 """ if mode not in ("filter", "transform"): raise ValueError(f"无效模式: {mode}") if mode == "filter": return [x for x in data if x > threshold] else: return [x * threshold for x in data]
💡 好的 docstring 三要素:1. 一句话描述:这个函数做什么2. 参数说明:每个参数是什么意思3. 返回值:返回什么养成写 docstring 的习惯,三个月后的你会感谢现在的自己。
8. 函数的返回值进阶
🔄 返回多个值
# Python 可以一次返回多个值(本质是返回元组)def get_name_age(): return "张三", 25result = get_name_age()print(result) # ('张三', 25) ← 元组print(type(result)) # <class 'tuple'># 解包接收name, age = get_name_age()print(name) # 张三print(age) # 25
🎯 返回布尔值的函数
# 习惯:以 is/has/can 开头def is_even(n): """判断是否偶数""" return n % 2 == 0def has_vowel(text): """判断是否包含元音""" vowels = "aeiouAEIOU" return any(c in vowels for c in text)def can_vote(age): """判断是否可以投票""" return age >= 18print(is_even(4)) # Trueprint(has_vowel("bcdf")) # Falseprint(can_vote(20)) # True# 直接用在条件判断中if is_even(10): print("是偶数")
🔄 函数作为参数
# 函数可以当参数传给另一个函数def apply(func, value): return func(value)def double(x): return x * 2def square(x): return x ** 2print(apply(double, 5)) # 10print(apply(square, 5)) # 25
9. 实战练习
🎯 练习 1:温度转换工具
def celsius_to_fahrenheit(celsius): """ 摄氏度转华氏度 公式: F = C × 9/5 + 32 """ return celsius * 9 / 5 + 32def fahrenheit_to_celsius(fahrenheit): """ 华氏度转摄氏度 公式: C = (F - 32) × 5/9 """ return (fahrenheit - 32) * 5 / 9def temperature_advice(celsius): """根据温度给出穿衣建议""" if celsius < 0: return "🥶 极寒!穿羽绒服、戴帽子手套" elif celsius < 10: return "🧥 很冷!穿厚外套" elif celsius < 20: return "👔 凉爽!穿长袖" elif celsius < 30: return "👕 温暖!穿短袖" else: return "🥵 炎热!注意防晒多喝水"# 测试temps_c = [0, 15, 25, 37, -10]for c in temps_c: f = celsius_to_fahrenheit(c) advice = temperature_advice(c) print(f" {c:5.1f}°C = {f:5.1f}°F | {advice}")
🎯 练习 2:密码强度检查器
def check_password_strength(password): """ 检查密码强度 评分标准: - 长度 >= 8: +1分 - 包含大写字母: +1分 - 包含小写字母: +1分 - 包含数字: +1分 - 包含特殊字符: +1分 返回: (分数, 等级, 详细信息) """ score = 0 details = [] # 检查长度 if len(password) >= 8: score += 1 details.append("✅ 长度足够") else: details.append("❌ 长度不足8位") # 检查大写字母 if any(c.isupper() for c in password): score += 1 details.append("✅ 包含大写字母") else: details.append("❌ 缺少大写字母") # 检查小写字母 if any(c.islower() for c in password): score += 1 details.append("✅ 包含小写字母") else: details.append("❌ 缺少小写字母") # 检查数字 if any(c.isdigit() for c in password): score += 1 details.append("✅ 包含数字") else: details.append("❌ 缺少数字") # 检查特殊字符 special = "!@#$%^&*()_+-=[]{}|;:',.<>?" if any(c in special for c in password): score += 1 details.append("✅ 包含特殊字符") else: details.append("❌ 缺少特殊字符") # 评级 levels = {0: "极弱", 1: "弱", 2: "一般", 3: "中等", 4: "强", 5: "极强"} level = levels[score] return score, level, details# 测试passwords = ["123456", "abcdef", "Abc12345", "MyP@ss2024!"]for pwd in passwords: score, level, details = check_password_strength(pwd) stars = "⭐" * score print(f"\n密码: {pwd}") print(f" 评分: {score}/5 {stars}") print(f" 等级: {level}") for d in details: print(f" {d}")
🎯 练习 3:数学工具箱
def factorial(n): """计算阶乘 n!""" if n < 0: return None result = 1 for i in range(1, n + 1): result *= i return resultdef fibonacci(n): """生成前 n 个斐波那契数""" if n <= 0: return [] if n == 1: return [0] fib = [0, 1] for i in range(2, n): fib.append(fib[i-1] + fib[i-2]) return fibdef is_prime(n): """判断是否为素数""" if n < 2: return False for i in range(2, int(n ** 0.5) + 1): if n % i == 0: return False return Truedef primes_in_range(start, end): """返回范围内的所有素数""" return [n for n in range(start, end + 1) if is_prime(n)]# 测试print(f"5! = {factorial(5)}") # 120print(f"斐波那契前10个: {fibonacci(10)}") # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]print(f"17是素数? {is_prime(17)}") # Trueprint(f"1-50的素数: {primes_in_range(1, 50)}") # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
10. 今日小结
| |
|---|
| def 函数名(参数): |
| |
| def f(x, y=10) |
| f(name="张三", age=25) |
| 返回值给调用者;不写 return 返回 None |
| return a, b |
| """描述""" |
| is_ |
🧠 记忆口诀:def 定义函数名,括号里面放参数。位置参数按序传,默认参数放后边。return 给值别忘写,None 返回没有它。docstring 写说明,三个月后不抓瞎。
🔮 预告: Day 14 函数进阶 — *args/**kwargs、变量作用域(global/nonlocal)、lambda 匿名函数。从"会写函数"到"写好函数"的关键一步!