函数|第四部分:函数、模块与代码组织 (Functions, Modules & Code Organization)

发布于 2025-09-11 分类: Python
系列文章: Python全方位教程
第 1 部分: Python语言概览|第一部分:Python入门与环境搭建 (Python Foundations) 第 2 部分: 搭建开发环境|第一部分:Python入门与环境搭建 (Python Foundations) 第 3 部分: Python基本语法与规范|第一部分:Python入门与环境搭建 (Python Foundations) 第 4 部分: 数据类型深入解析|第二部分:Python核心数据类型与运算符 (Core Data Types & Operators) 第 5 部分: 运算符大全|第二部分:Python核心数据类型与运算符 (Core Data Types & Operators) 第 6 部分: 条件与循环|第三部分:流程控制与数据结构操作 (Control Flow & Data Structures Manipulation) 第 7 部分: 数据结构高级操作|第三部分:流程控制与数据结构操作 (Control Flow & Data Structures Manipulation) 第 8 部分: 函数|第四部分:函数、模块与代码组织 (Functions, Modules & Code Organization) (当前) 第 9 部分: 模块与包|第四部分:函数、模块与代码组织 (Functions, Modules & Code Organization) 第 10 部分: 类与对象|第五部分:面向对象编程 (Object-Oriented Programming - OOP) 第 11 部分: OOP核心特性|第五部分:面向对象编程 (Object--Oriented Programming - OOP) 第 12 部分: 高级OOP主题|第五部分:面向对象编程 (Object-Oriented Programming - OOP) 第 13 部分: 迭代与生成|第六部分:高级Python编程 (Advanced Python) 第 15 部分: 内存管理与性能|第六部分:高级Python编程 (Advanced Python) 第 16 部分: 文件与目录操作|第七部分:Python标准库精选 (The Standard Library) 第 17 部分: 数据处理与序列化|第七部分:Python标准库精选 (The Standard Library) 第 18 部分: 网络与并发编程|第七部分:Python标准库精选 (The Standard Library) 第 20 部分: 系统交互|第七部分:Python标准库精选 (The Standard Library) 第 21 部分: 数据科学与分析入门|第八部分:Python生态与实战应用 (Ecosystem & Applications) 第 22 部分: Web开发入门|第八部分:Python生态与实战应用 (Ecosystem & Applications) 第 23 部分: GUI编程入门|第八部分:Python生态与实战应用 (Ecosystem & Applications) 第 24 部分: 图像处理入门|第八部分:Python生态与实战应用 (Ecosystem & Applications) 第 25 部分: 自动化脚本|第八部分:Python生态与实战应用 (Ecosystem & Applications) 第 26 部分: Python备忘单 (Cheatsheet)|第九部分:附录与资源 (Appendix & Resources) 第 27 部分: 常见面试题与解答|第九部分:附录与资源 (Appendix & Resources) 第 28 部分: 官方文档与其他学习资源链接|第九部分:附录与资源 (Appendix & Resources)

欢迎来到代码的“组织学”!在前面的章节里,我们写了不少代码。你可能已经发现,有些代码片段我们总是在重复书写。这就像你每次做饭都要从头开始研究菜谱,效率低下且容易出错。如果我们能把一套固定的“烹饪流程”打包起来,给它起个名字,比如“红烧肉菜谱”,下次想吃的时候直接调用这个“菜谱”就行了,岂不美哉?

这个“菜谱”,在编程世界里,就是函数 (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'}
    

argskwargs 只是约定俗成的名字,真正起作用的是 *** 这两个符号。

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 递归函数

递归,就是函数在自己的定义中调用自己。这是一种非常强大且优雅的解决问题的方式,尤其适合处理那些可以被分解为与自身结构相同的子问题的问题。

一个正确的递归函数必须包含两个部分:

  1. 基本情况 (Base Case): 一个或多个能直接解决的终止条件,防止无限循环。
  2. 递归步骤 (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

递归的思想初看可能有些烧脑,但一旦理解,你会发现它能用极少的代码解决非常复杂的问题。


太了不不起了!你已经从一个代码的“执行者”转变为一个“设计师”。你学会了如何将复杂的逻辑封装成简洁、可复用的函数,这是迈向专业程序员之路最坚实的一步。

现在,你已经能写出很多有用的函数了。但如果一个项目里有成百上千个函数,我们该如何管理它们呢?难道要把所有函数都写在一个文件里吗?当然不是!在下一章,我们将学习模块与包,探索如何将函数和类组织到不同的文件中,构建起一个清晰、有序、可扩展的大型项目结构。准备好,从“建筑师”向“城市规划师”进阶吧!


-- 感谢阅读 --