函数|第四部分:函数、模块与代码组织 (Functions, Modules & Code Organization)
欢迎来到代码的“组织学”!在前面的章节里,我们写了不少代码。你可能已经发现,有些代码片段我们总是在重复书写。这就像你每次做饭都要从头开始研究菜谱,效率低下且容易出错。如果我们能把一套固定的“烹饪流程”打包起来,给它起个名字,比如“红烧肉菜谱”,下次想吃的时候直接调用这个“菜谱”就行了,岂不美哉?
这个“菜谱”,在编程世界里,就是函数 (Function)。
函数是组织好的、可重复使用的、用来实现单一或相关联功能的代码段。它能极大提高代码的模块化和复用性。掌握了函数,你就掌握了**“不要重复你自己”(Don't Repeat Yourself - DRY)**这一核心编程原则,你的代码将变得前所未有的优雅、清晰和强大。你将从一个代码的“使用者”,转变为代码的“创造者”和“建筑师”。
8.1 定义与调用函数
创建一个函数就像是给一段代码赋予一个名字。
- 定义函数: 使用
def
关键字,后面跟函数名和圆括号()
,括号内可以放置参数。函数体的代码块必须缩进。 - 调用函数: 使用函数名加上圆括号
()
。
# 1. 定义一个函数,它的功能是打印问候语
def greet():
print("Hello, aspiring Python master!")
print("Welcome to the world of functions.")
# 2. 调用这个函数
print("程序开始...")
greet() # 每次调用,它都会执行函数内部的所有代码
print("程序结束.")
运行结果:
程序开始...
Hello, aspiring Python master!
Welcome to the world of functions.
程序结束.
8.2 函数参数详解:让你的函数更强大
光会打招呼的函数还不够酷。我们希望函数能处理我们传递给它的数据。这些数据,就是参数 (Parameters)。
位置参数 (Positional Arguments)
这是最常见的参数类型。调用函数时,传入的值(我们称之为实参 (Arguments))会按照位置顺序,依次赋给函数定义中的参数(我们称之为形参 (Parameters))。
def personal_greet(name, city):
print(f"你好,来自 {city} 的 {name}!")
# 调用时,"Alice" 对应 name, "北京" 对应 city
personal_greet("Alice", "北京")
运行结果: 你好,来自 北京 的 Alice!
关键字参数 (Keyword Arguments)
如果你不想记住参数的顺序,或者想让代码更具可读性,可以使用关键字参数。
# 调用时明确指定参数名,顺序可以打乱
personal_greet(city="上海", name="Bob")
运行结果: 你好,来自 上海 的 Bob!
默认参数 (Default Arguments)
我们可以为参数提供一个默认值。如果在调用函数时没有提供该参数的值,它就会使用这个默认值。
重要规则: 默认参数必须放在所有位置参数的后面。
def order_coffee(size, coffee_type="拿铁"):
print(f"为您准备一杯 {size} 的 {coffee_type}。")
order_coffee("大杯") # 没有提供 coffee_type,使用默认值 "拿铁"
order_coffee("中杯", "美式咖啡") # 提供了 coffee_type,覆盖默认值
运行结果:
为您准备一杯 大杯 的 拿铁。
为您准备一杯 中杯 的 美式咖啡。
任意参数 (*args
和 **kwargs
)
这是函数的“终极武器”,让你的函数可以接收任意数量的参数。
-
*args
(任意位置参数): 当你不确定要传入多少个位置参数时使用。Python会将这些参数打包成一个元组 (tuple)。def calculate_sum(*numbers): print(f"收到的数字元组: {numbers}") total = 0 for num in numbers: total += num return total print(calculate_sum(1, 2, 3)) # 输出: 6 print(calculate_sum(10, 20, 30, 40, 50)) # 输出: 150
-
**kwargs
(任意关键字参数): 当你不确定要传入多少个关键字参数时使用。Python会将它们打包成一个字典 (dict)。def build_profile(first_name, last_name, **user_info): profile = { 'first': first_name, 'last': last_name } profile.update(user_info) # 将 user_info 字典合并到 profile 中 return profile user1 = build_profile("Albert", "Einstein", location="Princeton", field="Physics") print(user1) # 输出: {'first': 'Albert', 'last': 'Einstein', 'location': 'Princeton', 'field': 'Physics'}
args
和 kwargs
只是约定俗成的名字,真正起作用的是 *
和 **
这两个符号。
8.3 函数返回值 (return
语句)
函数不仅能执行操作,还能“交还”一个结果给我们。这就是 return
语句的使命。
import math
def circle_area(radius):
if radius < 0:
return "错误:半径不能为负数" # return 可以提前结束函数
area = math.pi * (radius ** 2)
return area # 返回计算结果
# 调用函数并把返回值赋给变量
result = circle_area(10)
print(f"半径为10的圆的面积是: {result}")
# 函数可以返回多个值,它们会被打包成一个元组
def get_point():
return 10, 20
x, y = get_point() # 使用解包
print(f"坐标是: ({x}, {y})")
注意: 如果一个函数没有 return
语句,或者 return
后面没有跟任何值,它会默认返回 None
。
8.4 变量作用域 (LEGB规则)
变量不是在程序的任何地方都可以访问的,它有自己的“生命周期”和“可见范围”,这就是作用域 (Scope)。Python遵循LEGB规则来查找一个变量:
- L (Local): 局部作用域。函数内部定义的变量,只在该函数内部有效。
- E (Enclosing): 闭包函数作用域。当一个函数嵌套在另一个函数内部时产生。
- G (Global): 全局作用域。在所有函数之外定义的变量。
- B (Built-in): 内建作用域。Python预先定义的变量和函数,如
print()
,len()
。
x = "我是全局变量 (Global)"
def outer_func():
y = "我是嵌套作用域变量 (Enclosing)"
def inner_func():
z = "我是局部变量 (Local)"
print(z) # 优先找到 Local 的 z
print(y) # 在 Local 找不到 y,去 Enclosing 找,找到了
print(x) # 在 Local 和 Enclosing 都找不到 x,去 Global 找,找到了
inner_func()
outer_func()
重要: 在函数内部,你可以读取全局变量,但如果你想修改它,必须使用 global
关键字声明。不过,滥用 global
会让代码难以维护,应尽量避免。
8.5 匿名函数 (lambda
)
lambda
是一种创建小型、一次性使用的匿名函数(没有名字的函数)的快捷方式。它非常适合用在需要一个简单函数作为参数的场合。
语法: lambda arguments: expression
# 传统函数
def add(x, y):
return x + y
# 等价的 lambda 函数
adder_lambda = lambda x, y: x + y
print(add(3, 5)) # 输出: 8
print(adder_lambda(3, 5)) # 输出: 8
# lambda 的真正威力:作为参数传递
points = [(1, 5), (3, 2), (8, 9)]
# 使用 lambda 按每个元组的第二个元素 (y坐标) 排序
points.sort(key=lambda point: point[1])
print(points) # 输出: [(3, 2), (1, 5), (8, 9)]
8.6 递归函数
递归,就是函数在自己的定义中调用自己。这是一种非常强大且优雅的解决问题的方式,尤其适合处理那些可以被分解为与自身结构相同的子问题的问题。
一个正确的递归函数必须包含两个部分:
- 基本情况 (Base Case): 一个或多个能直接解决的终止条件,防止无限循环。
- 递归步骤 (Recursive Step): 函数调用自身,但处理的数据规模会向基本情况靠近。
经典案例:计算阶乘 (n!)
n! = n * (n-1) * (n-2) * ... * 1
def factorial(n):
# 基本情况
if n == 1:
return 1
# 递归步骤
else:
return n * factorial(n - 1)
print(f"5的阶乘是: {factorial(5)}") # 5 * 4 * 3 * 2 * 1 = 120
递归的思想初看可能有些烧脑,但一旦理解,你会发现它能用极少的代码解决非常复杂的问题。
太了不不起了!你已经从一个代码的“执行者”转变为一个“设计师”。你学会了如何将复杂的逻辑封装成简洁、可复用的函数,这是迈向专业程序员之路最坚实的一步。
现在,你已经能写出很多有用的函数了。但如果一个项目里有成百上千个函数,我们该如何管理它们呢?难道要把所有函数都写在一个文件里吗?当然不是!在下一章,我们将学习模块与包,探索如何将函数和类组织到不同的文件中,构建起一个清晰、有序、可扩展的大型项目结构。准备好,从“建筑师”向“城市规划师”进阶吧!