跳转至

02 - 函数与模块

学习时间: 45-60分钟 重要性: ⭐⭐⭐⭐⭐ 组织代码的基本单位


🎯 学习目标

  • 理解函数的定义和调用
  • 掌握参数传递的各种方式
  • 理解作用域和命名空间
  • 学会组织和使用模块

🔧 函数基础

定义和调用

Python
# 基本语法
def greet():
    """函数文档字符串(docstring)"""
    print("Hello!")

greet()  # 调用函数

# 带参数的函数
def greet_person(name):
    print(f"Hello, {name}!")

greet_person("张三")  # "Hello, 张三!"

# 返回值
def add(a, b):
    return a + b

result = add(5, 3)  # result = 8

# 没有return语句,默认返回None
def no_return():
    print("这个函数不返回值")

result = no_return()  # result = None

参数类型(重要!)

Python
# 1. 位置参数
def describe_person(name, age):
    print(f"{name} is {age} years old")

describe_person("张三", 25)  # 按位置传递

# 2. 关键字参数
describe_person(name="李四", age=30)
describe_person(age=30, name="李四")  # 顺序可以改变

# 3. 默认参数
def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("张三")                    # "Hello, 张三!"
greet("张三", greeting="你好")    # "你好, 张三!"

# ⚠️ 警告:默认参数只计算一次
def append_to_list(item, lst=[]):  # 危险!
    lst.append(item)
    return lst

print(append_to_list(1))  # [1]
print(append_to_list(2))  # [1, 2] - lst保留了之前的值!

# ✅ 正确做法
def append_to_list(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

# 4. 可变位置参数 (*args)
def sum_all(*numbers):
    return sum(numbers)

print(sum_all(1, 2, 3))      # 6
print(sum_all(1, 2, 3, 4))   # 10

def print_info(*args):
    for arg in args:
        print(arg)

print_info("张三", 25, "北京")

# 5. 可变关键字参数 (**kwargs)
def create_profile(**info):
    for key, value in info.items():
        print(f"{key}: {value}")

create_profile(name="张三", age=25, city="北京")

# 组合使用(参数顺序很重要)
def complex_function(a, b, *args, c=10, **kwargs):
    print(f"a: {a}, b: {b}")
    print(f"args: {args}")
    print(f"c: {c}")
    print(f"kwargs: {kwargs}")

complex_function(1, 2, 3, 4, c=20, name="张三")
# a: 1, b: 2
# args: (3, 4)
# c: 20
# kwargs: {'name': '张三'}

返回多个值

Python
# 返回元组(实际上是返回一个对象)
def get_user_info():
    name = "张三"
    age = 25
    city = "北京"
    return name, age, city  # 等价于 return (name, age, city)

# 解包接收
name, age, city = get_user_info()

# 或者作为元组接收
info = get_user_info()
print(info)  # ("张三", 25, "北京")

# 返回字典(更清晰)
def get_user_info_dict():
    return {
        "name": "张三",
        "age": 25,
        "city": "北京"
    }

info = get_user_info_dict()
print(info["name"])  # "张三"

📦 作用域与命名空间

LEGB规则

Python
# L - Local(局部)
# E - Enclosing(闭包)
# G - Global(全局)
# B - Built-in(内置)

x = "global"  # 全局变量

def outer():
    x = "enclosing"  # 闭包变量

    def inner():
        x = "local"  # 局部变量
        print(x)  # 输出: local

    inner()
    print(x)  # 输出: enclosing

outer()
print(x)  # 输出: global

global 和 nonlocal

Python
# global - 修改全局变量
count = 0

def increment():
    global count  # 声明使用全局变量
    count += 1

increment()
print(count)  # 1

# nonlocal - 修改闭包变量
def outer():
    count = 0

    def inner():
        nonlocal count
        count += 1

    inner()
    return count

print(outer())  # 1

变量查找示例

Python
x = 10  # 全局

def func():
    x = 20  # 局部
    print(x)  # 20 (优先使用局部)

func()
print(x)  # 10 (全局没有被修改)

def func2():
    print(x)  # 10 (没有局部变量,使用全局)

func2()

def func3():
    global x
    x = 30  # 修改全局变量

func3()
print(x)  # 30

🗂️ 模块与包

导入模块

Python
# 导入整个模块
import math
print(math.sqrt(16))  # 4.0

# 导入并重命名
import numpy as np
import pandas as pd

# 从模块导入特定函数
from math import sqrt, pi
print(sqrt(16))  # 4.0 (直接使用,不需要math.)
print(pi)        # 3.14159...

# 导入所有(不推荐)
from math import *
# 问题:污染命名空间,可能覆盖现有变量

# 推荐做法:明确导入需要的
from math import sqrt, sin, cos

创建自己的模块

Python
# my_utils.py
def greet(name):
    return f"Hello, {name}!"

def calculate_sum(numbers):
    return sum(numbers)

PI = 3.14159  # 示例常量;实际项目中建议使用 math.pi 获取更精确的值

# 另一个文件中使用
# main.py
import my_utils

print(my_utils.greet("张三"))
print(my_utils.calculate_sum([1, 2, 3]))
print(my_utils.PI)

# 或者
from my_utils import greet, calculate_sum
print(greet("张三"))

包(Package)

Text Only
my_package/
├── __init__.py      # 标识这是一个包
├── utils.py
└── models.py
Python
# __init__.py
from .utils import helper_function
from .models import Model

# 使用
import my_package
my_package.helper_function()

from my_package import utils, models
utils.helper_function()

模块搜索路径

Python
import sys
print(sys.path)
# Python按以下顺序查找模块:
# 1. 当前目录
# 2. PYTHONPATH环境变量
# 3. 标准库路径
# 4. site-packages目录(第三方库)

🎯 函数最佳实践

1. 单一职责原则

Python
# ❌ 不好:函数做太多事情
def process_data(data):
    # 读取数据
    # 清洗数据
    # 训练模型
    # 保存结果
    pass

# ✅ 好:每个函数只做一件事
def read_data(filepath):
    pass

def clean_data(data):
    pass

def train_model(data):
    pass

def save_results(results, filepath):
    pass

2. 使用类型注解(Python 3.9+ 推荐内置泛型)

Python
from typing import Any

def calculate_sum(numbers: list[int]) -> int:
    """计算数字列表的和"""
    return sum(numbers)

def find_user(user_id: int) -> dict | None:
    """查找用户,如果不存在返回None"""
    pass

def process_data(
    data: list[dict[str, Any]],
    limit: int = 10
) -> dict[str, int]:
    """处理数据并返回统计"""
    pass

3. 编写清晰的文档字符串

Python
def train_model(
    data: np.ndarray,
    epochs: int = 100,
    learning_rate: float = 0.001
) -> Model:
    """
    训练机器学习模型

    参数:
        data: 训练数据,形状为(n_samples, n_features)
        epochs: 训练轮数,默认为100
        learning_rate: 学习率,默认为0.001

    返回:
        训练好的模型对象

    异常:
        ValueError: 如果数据为空或形状不正确

    示例:
        >>> model = train_model(X_train, epochs=50)
        >>> predictions = model.predict(X_test)
    """
    pass

4. 使用纯函数(无副作用)

Python
# ❌ 有副作用:修改输入参数
def add_tax_bad(prices):
    prices.append(prices[-1] * 0.1)  # 修改了原列表
    return prices

# ✅ 纯函数:不修改输入
def add_tax_good(prices):
    new_prices = prices.copy()  # 创建副本
    new_prices.append(prices[-1] * 0.1)
    return new_prices

# 或者
def add_tax_better(prices):
    return prices + [prices[-1] * 0.1]

💡 实用技巧

高阶函数

Python
# 函数作为参数
def apply_operation(x, operation):
    return operation(x)

result = apply_operation(5, lambda x: x ** 2)  # lambda匿名函数:lambda 参数: 表达式

# 常用高阶函数
numbers = [1, 2, 3, 4, 5]

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

# filter: 过滤元素
evens = list(filter(lambda x: x % 2 == 0, numbers))  # [2, 4]

# 列表推导式通常更清晰
squared = [x ** 2 for x in numbers]
evens = [x for x in numbers if x % 2 == 0]

装饰器基础(下一章详解)

Python
# 简单的计时装饰器
import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.2f} seconds")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)
    return "Done"

slow_function()  # 会打印执行时间

函数缓存

Python
from functools import lru_cache

@lru_cache(maxsize=128)  # 缓存最近128次调用
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# 第一次调用慢
print(fibonacci(100))

# 第二次调用快(从缓存读取)
print(fibonacci(100))

📝 练习

练习1: 数据处理函数

Python
def filter_and_transform(
    numbers: list[int],
    min_value: int = 0,
    max_value: int = 100
) -> list[int]:
    """
    1. 筛选出min_value到max_value之间的数字
    2. 将筛选出的数字乘以2
    3. 返回结果列表
    """
    # TODO: 实现这个函数
    pass

# 测试
print(filter_and_transform([1, 50, 150, 75, -10]))  # 应该输出 [2, 100, 150]

练习2: 统计函数

Python
def analyze_text(text: str) -> dict:
    """
    分析文本并返回统计信息:
    - 字符数
    - 单词数
    - 句子数(以.!?结尾)
    """
    # TODO: 实现这个函数
    pass

# 测试
text = "Hello world. How are you? I'm fine!"
print(analyze_text(text))
# 应该输出类似: {'chars': 37, 'words': 8, 'sentences': 3}

练习3: 创建模块

Python
# 创建一个文件 text_utils.py,包含以下函数:
# - count_words(text): 统计单词数
# - reverse_text(text): 反转文本
# - remove_punctuation(text): 去除标点符号

# 然后在另一个文件中导入使用

🎯 自我检查

完成这个主题后,你应该:

  • 能定义和使用各种类型的参数
  • 理解LEGB作用域规则
  • 知道如何组织模块和包
  • 能写出类型注解和文档字符串
  • 理解纯函数的概念
  • 不查资料完成上面的练习

📚 延伸阅读


下一步: 03 - 面向对象基础