← 返回 Python 7天入门专题

Day 4 · 函数

定义函数 · 参数 · 返回值 · 作用域 · lambda · 递归 | 建议时长:3-4小时
第一性原理:程序越写越长,你会发现同样的逻辑重复写了5遍。函数就是把重复逻辑"打包"成一个有名字的命令——以后只要喊名字就能执行整段逻辑。函数的本质是输入→处理→输出的封装。

1. 定义和调用函数

# 语法
def 函数名(参数1, 参数2):
    """文档字符串:说明这个函数做什么"""
    # 函数体
    return 返回值

# 示例:打招呼
def greet(name):
    """向指定的人打招呼"""
    message = f"你好,{name}!"
    return message

# 调用
result = greet("张三")
print(result)  # 你好,张三!

# 没有return的函数返回None
def say_hello():
    print("Hello!")

result = say_hello()   # 打印Hello!
print(result)        # None
函数的本质就是:输入(参数) → 黑盒(函数体) → 输出(return)。
就像一台绞肉机:放进去肉(参数),机器运转(函数体),出来肉馅(return值)。

2. 参数的四种形式

2.1 位置参数(最基本的)

def add(a, b):
    return a + b

print(add(3, 5))     # 8 — 3传给a,5传给b
# print(add(3))      ← 报错!参数不够

2.2 默认参数

def greet(name, greeting="你好"):
    return f"{greeting},{name}!"

print(greet("张三"))          # 你好,张三! (greeting用默认值)
print(greet("李四", "Hello"))  # Hello,李四!

# ⚠️ 默认参数必须在位置参数后面!
# def greet(greeting="你好", name):  ← 语法错误!

2.3 关键字参数

def describe(name, age, city):
    return f"{name},{age}岁,来自{city}"

# 用参数名指定,顺序可以变
print(describe(name="张三", city="北京", age=20))
print(describe("李四", city="上海", age=22))  # 混合用

2.4 可变参数 *args 和 **kwargs

# *args — 收集多余的"位置参数"为一个元组
def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3))        # 6
print(sum_all(1, 2, 3, 4, 5))   # 15

# **kwargs — 收集多余的"关键字参数"为一个字典
def make_profile(name, **kwargs):
    profile = {"name": name}
    profile.update(kwargs)
    return profile

print(make_profile("张三", age=20, city="北京", job="学生"))
# {'name': '张三', 'age': 20, 'city': '北京', 'job': '学生'}

# 组合使用
def func(a, b, *args, **kwargs):
    print(f"a={a}, b={b}, args={args}, kwargs={kwargs}")

func(1, 2, 3, 4, x=5, y=6)
# a=1, b=2, args=(3, 4), kwargs={'x': 5, 'y': 6}
参数顺序规则:位置参数 → 默认参数 → *args → **kwargs
即 def func(a, b=2, *args, **kwargs)

3. 返回值

# 1. 返回单个值
def square(x):
    return x ** 2

# 2. 返回多个值(本质是元组)
def min_max(lst):
    return min(lst), max(lst)

low, high = min_max([3, 1, 4, 1, 5])  # 解包

# 3. 提前返回(常见模式)
def is_even(n):
    if n % 2 == 0:
        return True
    return False

# 4. 无return等同于 return None
# 5. return后面不跟值也返回None

4. 作用域 — 变量的可见范围

第一性原理:变量不是哪里都能用的。函数内部的变量是"局部的",外面看不到;函数外面的变量是"全局的",里面能看到但不能直接改。这就是作用域。
# 全局变量
message = "我是全局的"

def func1():
    print(message)    # 能读取全局变量

def func2():
    message = "我是局部的"  # 这是新建局部变量,不是修改全局!
    print(message)

def func3():
    global message     # 声明要用全局变量
    message = "被修改了"    # 现在修改的是全局变量

func1()    # 我是全局的
func2()    # 我是局部的
print(message)  # 我是全局的 (func2没改全局)
func3()
print(message)  # 被修改了 (func3改了全局)
尽量少用 global!全局变量是bug之源。好的做法是通过参数传入、return返回。函数应该是"黑盒"——输入→处理→输出,不依赖外部状态。

4.1 LEGB规则(了解即可)

Python查找变量的顺序:L → E → G → B

字母含义示例
L (Local)函数内部函数体里的变量
E (Enclosing)外层函数嵌套函数的外层
G (Global)模块级文件顶层变量
B (Built-in)内置print, len, range...

5. 匿名函数 lambda

第一性原理:有时候需要一个很简单(一行)的函数,不想正式def一个。lambda就是"一次性迷你函数",适合简单场景。
# 语法:lambda 参数: 返回值
square = lambda x: x ** 2
print(square(5))    # 25

# 等价于
def square(x):
    return x ** 2

# lambda最常见的用途:作为参数传给高阶函数

# 1. 排序的key
students = [
    {"name": "张三", "score": 85},
    {"name": "李四", "score": 92},
    {"name": "王五", "score": 78},
]
students.sort(key=lambda s: s["score"], reverse=True)
# 按分数降序排列

# 2. map() — 对每个元素应用函数
nums = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, nums))
# [1, 4, 9, 16, 25]

# 3. filter() — 过滤
evens = list(filter(lambda x: x % 2 == 0, nums))
# [2, 4]

# 4. sorted() 自定义排序
words = ["banana", "apple", "cherry"]
print(sorted(words, key=lambda w: len(w)))  # 按长度排序

6. 递归 — 函数调用自己

第一性原理:有些问题天然是"自相似"的——大问题可以拆成同类型的小问题。递归就是函数调用自己来解决更小规模的同类问题。必须有终止条件,否则无限循环。
# 阶乘:5! = 5 × 4 × 3 × 2 × 1 = 120
def factorial(n):
    if n <= 1:           # 终止条件(必须有!)
        return 1
    return n * factorial(n - 1)  # 调用自己

print(factorial(5))   # 120
# 执行过程:5 * factorial(4)
#          → 4 * factorial(3)
#          → 3 * factorial(2)
#          → 2 * factorial(1)
#          → 1 (终止)

# 斐波那契数列
def fib(n):
    if n <= 2:
        return 1
    return fib(n-1) + fib(n-2)

print(fib(10))  # 55
递归三要素:
  1. 终止条件 — 什么时候停止(如 n <= 1)
  2. 递归调用 — 调用自己处理更小的问题
  3. 返回值 — 把子问题的结果组合成最终答案
递归效率不一定高(fib的朴素递归是指数级),但有些问题(树遍历、分治算法)用递归最自然。

7. 函数是一等公民

# 1. 函数可以赋值给变量
def shout(text):
    return text.upper()

yell = shout       # 赋值给变量
print(yell("hello"))  # HELLO

# 2. 函数可以作为参数
def apply_func(func, value):
    return func(value)

print(apply_func(shout, "hi"))   # HI
print(apply_func(len, "hello"))   # 5

# 3. 函数可以作为返回值(闭包)
def make_multiplier(n):
    def multiply(x):
        return x * n
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5))   # 10
print(triple(5))   # 15

8. 文档字符串与类型提示

# 文档字符串 — 用三引号说明函数用途
def calculate_bmi(weight, height):
    """
    计算BMI指数

    参数:
        weight: 体重(kg)
        height: 身高(m)

    返回:
        BMI值
    """
    return weight / (height ** 2)

# 类型提示(Python 3.5+)— 让代码更易读
def add(a: int, b: int) -> int:
    return a + b

def greet(name: str) -> str:
    return f"Hello, {name}"

# 类型提示不强制,只是提示。但IDE会检查,团队协作时很有用

9. 练习题(独立完成)

练习1:温度转换函数

写一个函数 celsius_to_fahrenheit(celsius),接收摄氏度,返回华氏度。再写一个反向函数。

参考答案
def celsius_to_fahrenheit(c):
    return c * 9 / 5 + 32

def fahrenheit_to_celsius(f):
    return (f - 32) * 5 / 9

print(celsius_to_fahrenheit(100))  # 212.0
print(fahrenheit_to_celsius(212))  # 100.0

练习2:密码强度检查器

写函数 check_password(pwd),返回"弱"/"中"/"强"。规则:长度<8弱;长度≥8且含数字和字母为中;长度≥12且含大写+小写+数字为强。

参考答案
def check_password(pwd):
    has_upper = any(c.isupper() for c in pwd)
    has_lower = any(c.islower() for c in pwd)
    has_digit = any(c.isdigit() for c in pwd)
    length = len(pwd)
    
    if length >= 12 and has_upper and has_lower and has_digit:
        return "强"
    elif length >= 8 and has_lower and has_digit:
        return "中"
    else:
        return "弱"

print(check_password("123"))          # 弱
print(check_password("abc12345"))     # 中
print(check_password("Abcdef123456")) # 强

练习3:统计函数

写函数 analyze_scores(scores),返回一个字典包含:平均分、最高分、最低分、及格率、优秀率。

参考答案
def analyze_scores(scores):
    return {
        "平均分": sum(scores) / len(scores),
        "最高分": max(scores),
        "最低分": min(scores),
        "及格率": sum(1 for s in scores if s >= 60) / len(scores),
        "优秀率": sum(1 for s in scores if s >= 90) / len(scores),
    }

result = analyze_scores([85, 92, 78, 65, 95, 88, 72, 90, 85, 60])
for k, v in result.items():
    if isinstance(v, float):
        print(f"{k}: {v:.1%}" if "率" in k else f"{k}: {v:.1f}")
    else:
        print(f"{k}: {v}")

练习4:递归求幂

用递归实现 power(base, exp),计算 base 的 exp 次方。不能使用 ** 运算符。

参考答案
def power(base, exp):
    if exp == 0:
        return 1
    if exp < 0:
        return 1 / power(base, -exp)
    return base * power(base, exp - 1)

print(power(2, 10))   # 1024
print(power(3, 3))    # 27
print(power(2, -2))   # 0.25

练习5:学生管理器

写3个函数:add_student(添加)、find_student(查找)、list_students(列出全部)。用全局列表存储学生数据(字典列表)。

参考答案
students = []

def add_student(name, age, score):
    students.append({"name": name, "age": age, "score": score})
    print(f"添加成功: {name}")

def find_student(name):
    for s in students:
        if s["name"] == name:
            return s
    return None

def list_students(sort_by="score"):
    return sorted(students, key=lambda s: s[sort_by], reverse=True)

add_student("张三", 20, 85)
add_student("李四", 21, 92)
add_student("王五", 19, 78)

print(find_student("李四"))
for s in list_students():
    print(f"{s['name']}: {s['score']}")

10. Day 4 小结

今天你学会了什么