假设你要计算三个圆的面积,半径分别为3、5、7。没有函数时你会这样写:# 圆1pi = 3.14159area1 = pi * 3 * 3print(area1)# 圆2area2 = pi * 5 * 5print(area2)# 圆3area3 = pi * 7 * 7print(area3)
半径越多,重复越多。更糟的是,如果哪天要把π换成更高精度的值,你得改三处。def circle_area(radius): return 3.14159 * radius * radiusprint(circle_area(3))print(circle_area(5))print(circle_area(7))
函数解决的三个核心问题:消除重复、便于修改、语义清晰。读到circle_area(5),你立刻知道这是在算什么,而不需要逐行分析代码逻辑。def 函数名(参数列表): """文档字符串(可选)""" 函数体 return 返回值 # 可选
def greet(name): """向指定用户打招呼""" return f"你好,{name}!欢迎回来。"result = greet("小明")print(result) # 你好,小明!欢迎回来。
def show_banner(): print("=" * 30) print(" 欢迎使用学生管理系统 ") print("=" * 30)show_banner()
当函数没有return语句(或写了return但不带值)时,它默认返回None:output = show_banner() # 打印了横幅print(output) # None
def describe_pet(name, animal_type): print(f"{name}是一只{animal_type}")describe_pet("旺财", "狗") # 旺财是一只狗describe_pet("狗", "旺财") # 狗是一只旺财 ← 顺序错了,语义就崩了
位置参数要求调用者记住参数顺序,数量多了容易出错。describe_pet(animal_type="猫", name="咪咪") # 咪咪是一只猫describe_pet(name="咪咪", animal_type="猫") # 效果完全相同
关键字参数让调用意图一目了然,强烈推荐在参数较多时使用。def describe_pet(name, animal_type="狗"): print(f"{name}是一只{animal_type}")describe_pet("旺财") # 旺财是一只狗describe_pet("咪咪", animal_type="猫") # 咪咪是一只猫
# 错误写法——几乎所有新手都会踩的坑def add_item(item, target=[]): target.append(item) return targetprint(add_item(1)) # [1]print(add_item(2)) # [1, 2] ← 你以为会是[2],但默认列表被重复使用了!print(add_item(3)) # [1, 2, 3]
原因:Python在定义函数时只计算一次默认值,之后每次调用都复用同一个列表对象。def add_item(item, target=None): if target is None: target = [] target.append(item) return targetprint(add_item(1)) # [1]print(add_item(2)) # [2]print(add_item(3)) # [3]
当你不知道调用者会传多少个位置参数时,用*args接收为一个元组:def sum_all(*numbers): total = 0 for n in numbers: total += n return totalprint(sum_all(1, 2)) # 3print(sum_all(1, 2, 3, 4, 5)) # 15print(sum_all()) # 0
*的名字不一定要叫args,但*args几乎是Python社区的约定俗成。**kwargs把额外的关键字参数打包成一个字典:def build_profile(name, **info): profile = {"name": name} profile.update(info) return profileuser = build_profile("小明", age=18, city="北京", hobby="编程")print(user)# {'name': '小明', 'age': 18, 'city': '北京', 'hobby': '编程'}
*args和**kwargs可以同时使用,配合参数传递非常灵活:def log(message, *args, **kwargs): print(f"[LOG] {message}") if args: print(f" 附加数据: {args}") if kwargs: print(f" 附加信息: {kwargs}")log("用户登录", "127.0.0.1", "Chrome", user_id=1001, level="INFO")# [LOG] 用户登录# 附加数据: ('127.0.0.1', 'Chrome')# 附加信息: {'user_id': 1001, 'level': 'INFO'}
def func(位置参数, 默认参数, *args, 仅限关键字参数, **kwargs)
def demo(a, b, c=0, *args, d, **kwargs): print(a, b, c, args, d, kwargs)demo(1, 2, 3, 4, 5, d=6, e=7, f=8)# 1 2 3 (4, 5) 6 {'e': 7, 'f': 8}
注意:d在*args之后,所以调用时必须用关键字方式传——这叫“仅限关键字参数”(keyword-only argument)。Python查找变量名时遵循四层作用域,从内到外:x = "global" # 全局def outer(): x = "enclosing" # 外层函数的局部 def inner(): x = "local" # 内层函数的局部 print(x) # 查找顺序:local → enclosing → global → built-in inner()outer() # 输出 local
在函数内部默认只能读取全局变量,如果要修改,需要声明:count = 0def increment(): global count # 告诉Python:我要用的是外面的那个count count += 1increment()print(count) # 1
嵌套函数中修改外层函数的变量,用nonlocal:def outer(): x = 10 def inner(): nonlocal x x = 20 inner() print(x) # 20(被inner修改了)outer()
def min_max(numbers): return min(numbers), max(numbers)low, high = min_max([3, 1, 4, 1, 5, 9])print(low) # 1print(high) # 9
提前返回是一种常见的防御式编程手法——条件不满足就尽早退出:def divide(a, b): if b == 0: return "除数不能为零" return a / b
写多了函数,一个文件会变得臃肿。模块让函数可以分文件存放。# math_utils.pyPI = 3.14159def circle_area(radius): return PI * radius * radiusdef circle_perimeter(radius): return 2 * PI * radius
| | |
|---|
import math_utils | math_utils.circle_area(5) | |
from math_utils import circle_area | circle_area(5) | |
from math_utils import * | circle_area(5) | 不推荐 |
import math_utils as mu | mu.circle_area(5) | |
6.3 __name__ == '__main__' 是什么每个Python文件都有一个内置变量__name__:- 当文件被直接运行时,
__name__的值是'__main__' - 当文件被import导入时,
__name__的值是文件名(模块名)
# math_utils.pydef circle_area(radius): return 3.14159 * radius * radiusdef main(): # 测试代码 print(circle_area(3)) print(circle_area(5))if __name__ == '__main__': main()
效果:直接执行python math_utils.py会运行main()做测试;被其他文件import math_utils时,测试代码不会执行,只有函数定义被导入。一举两得。把今天学的串起来。写一个简单的“学生成绩管理”模块:# grade_manager.py"""学生成绩管理模块"""def average(*scores): """计算平均分""" if not scores: return 0 return sum(scores) / len(scores)def grade_level(score, thresholds=None): """根据分数返回等级""" if thresholds is None: thresholds = {"优秀": 90, "良好": 80, "中等": 70, "及格": 60} for level, limit in thresholds.items(): if score >= limit: return level return "不及格"def report(name, **subject_scores): """生成学生成绩报告""" scores = list(subject_scores.values()) avg = average(*scores) level = grade_level(avg) print(f"=== {name} 的成绩报告 ===") for subject, score in subject_scores.items(): print(f" {subject}: {score}") print(f" 平均分: {avg:.1f} ({level})") print("=" * 25)if __name__ == '__main__': report("小明", 数学=92, 英语=85, 语文=78, 物理=88)
=== 小明 的成绩报告 === 数学: 92 英语: 85 语文: 78 物理: 88 平均分: 85.8 (良好)=========================
这个例子用到了默认参数的安全写法(thresholds=None)、*args解包传参、**kwargs接受可变关键字——都是今天讲的核心知识点。这篇我们讲了函数定义、五种参数形态、LEGB作用域、return用法,以及模块的基本操作。如果你能把“默认参数的列表陷阱”和“__name__ == '__main__'”这两个点讲给别人听,说明你真的理解了。