函数#

函数是组织代码的重要方式,可以将代码封装成可重用的块。Python 提供了强大的函数功能,包括参数传递、作用域、装饰器等。

函数定义和调用#

定义函数#

# 基本语法
def function_name(parameters):
    """文档字符串(可选)"""
    # 函数体
    return value  # 可选

# 示例
def greet(name):
    """打印问候语"""
    print(f"Hello, {name}!")

# 调用函数
greet("Python")  # 输出: Hello, Python!

函数文档字符串#

def add(a, b):
    """
    计算两个数的和
    
    参数:
        a: 第一个数
        b: 第二个数
    
    返回:
        两个数的和
    """
    return a + b

# 访问文档字符串
print(add.__doc__)
help(add)

返回值#

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

result = square(5)  # 25

# 返回多个值(实际上是返回元组)
def get_name_age():
    return "Alice", 30

name, age = get_name_age()  # 元组解包
result = get_name_age()      # ('Alice', 30)

# 没有return语句,返回None
def do_nothing():
    pass

result = do_nothing()  # None

参数传递#

位置参数#

位置参数按照定义顺序传递。

def greet(name, age):
    print(f"{name} is {age} years old")

greet("Alice", 25)  # Alice is 25 years old
greet(25, "Alice")  # 25 is Alice years old(顺序错误!)

关键字参数#

使用参数名传递,可以不按顺序。

def greet(name, age):
    print(f"{name} is {age} years old")

greet(name="Alice", age=25)  # Alice is 25 years old
greet(age=25, name="Alice")  # Alice is 25 years old(顺序可以改变)
greet("Alice", age=25)       # Alice is 25 years old(混合使用)

默认参数#

函数参数可以有默认值。

def greet(name, age=18):
    print(f"{name} is {age} years old")

greet("Alice")        # Alice is 18 years old(使用默认值)
greet("Bob", 30)      # Bob is 30 years old(覆盖默认值)

# 默认参数必须是不可变对象(重要!)
def add_item(item, my_list=[]):  # 危险!默认参数是可变对象
    my_list.append(item)
    return my_list

# 正确的方式
def add_item(item, my_list=None):
    if my_list is None:
        my_list = []
    my_list.append(item)
    return my_list

*args - 可变位置参数#

*args 用于接收任意数量的位置参数,以元组形式传递。

def sum_numbers(*args):
    """计算所有参数的和"""
    total = 0
    for num in args:
        total += num
    return total

result = sum_numbers(1, 2, 3)        # 6
result = sum_numbers(1, 2, 3, 4, 5)  # 15

# args是一个元组
def print_args(*args):
    print(type(args))  # <class 'tuple'>
    print(args)

print_args(1, 2, 3)  # (1, 2, 3)

# 解包传递
numbers = [1, 2, 3]
sum_numbers(*numbers)  # 等价于 sum_numbers(1, 2, 3)

**kwargs - 可变关键字参数#

**kwargs 用于接收任意数量的关键字参数,以字典形式传递。

def print_info(**kwargs):
    """打印所有关键字参数"""
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=30, city="Beijing")
# 输出:
# name: Alice
# age: 30
# city: Beijing

# kwargs是一个字典
def print_kwargs(**kwargs):
    print(type(kwargs))  # <class 'dict'>
    print(kwargs)

print_kwargs(a=1, b=2)  # {'a': 1, 'b': 2}

# 解包传递
info = {"name": "Alice", "age": 30}
print_info(**info)  # 等价于 print_info(name="Alice", age=30)

参数顺序规则#

函数参数必须按照以下顺序定义:

  1. 位置参数
  2. *args
  3. 关键字参数(有默认值的)
  4. **kwargs
def example(pos1, pos2, *args, kw1="default", kw2="default", **kwargs):
    pass

# 调用示例
example(1, 2, 3, 4, kw1="value1", extra="value2")
# pos1=1, pos2=2, args=(3, 4), kw1="value1", kw2="default", kwargs={"extra": "value2"}

作用域和命名空间(重点难点)#

什么是命名空间#

命名空间(namespace)是名字到对象的映射,可理解为「名字 → 对象」的字典。不同命名空间中的同名名字互不干扰。

常见类型:

类型说明示例
内置解释器启动时加载len, print, open
全局模块级名字模块顶层的变量、函数、类
局部函数/类体内名字函数参数、函数内赋值的变量
闭包嵌套函数的外层名字外层函数的局部名字

命名空间与作用域:作用域是某段代码能访问的命名空间范围;LEGB 规则即名字查找时访问命名空间的顺序(见下)。

局部作用域和全局作用域#

# 全局变量
x = 10

def my_function():
    # 局部变量
    y = 20
    print(x)  # 可以访问全局变量
    print(y)  # 可以访问局部变量

my_function()
# print(y)  # 错误!y是局部变量,外部无法访问

global 关键字#

使用 global 关键字可以在函数内部修改全局变量,数字为不可更改对象,修改全局作用域的变量绑定

x = 10

def modify_global():
    global x  # 声明x是全局变量
    x = 20    # 修改全局变量

modify_global()
print(x)  # 20

# 不使用global的情况
x = 10

def try_modify():
    x = 20  # 这创建了一个新的局部变量x,不是修改全局变量

try_modify()
print(x)  # 10(全局变量未改变)

nonlocal 关键字#

nonlocal 用于在嵌套函数中修改外层函数的变量。

def outer():
    x = 10
    
    def inner():
        nonlocal x  # 声明x是外层函数的变量
        x = 20      # 修改外层函数的变量
    
    inner()
    print(x)  # 20

outer()

# 不使用nonlocal的情况
def outer():
    x = 10
    
    def inner():
        x = 20  # 这创建了一个新的局部变量x
    
    inner()
    print(x)  # 10(外层变量未改变)

outer()

LEGB 规则#

Python 按以下顺序在命名空间中查找名字(LEGB):

  • Local(局部作用域)
  • Enclosing(嵌套作用域)
  • Global(全局作用域)
  • Built-in(内置作用域)
# 示例
x = "global"

def outer():
    x = "enclosing"
    
    def inner():
        x = "local"
        print(x)  # local(局部作用域)
    
    inner()
    print(x)  # enclosing

outer()
print(x)  # global

lambda 匿名函数#

lambda 用于创建简单的匿名函数。

# 基本语法:lambda 参数: 表达式
square = lambda x: x ** 2
print(square(5))  # 25

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

# 多个参数
add = lambda x, y: x + y
print(add(2, 3))  # 5

# 常用场景:作为参数传递
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))  # [1, 4, 9, 16, 25]

# 排序
students = [("Alice", 25), ("Bob", 20), ("Charlie", 30)]
students.sort(key=lambda x: x[1])  # 按年龄排序

函数装饰器(重点难点)#

装饰器是 Python 的高级特性,用于在不修改原函数的情况下扩展函数功能。

装饰器的工作原理#

装饰器的本质是函数替换。当使用 @decorator 语法时,Python 会执行以下步骤:

  1. 定义阶段:Python 先定义被装饰的函数
  2. 装饰阶段:立即将函数对象传递给装饰器函数
  3. 替换阶段:用装饰器返回的新函数替换原函数

关键理解

  • 装饰器在函数定义时立即执行,而不是在函数调用时
  • 装饰器接收函数对象作为参数,返回一个新的可调用对象
  • 原函数被替换为装饰器返回的新函数
def decorator(func):
    print(f"装饰器执行,装饰函数: {func.__name__}")  # 在函数定义时执行
    def wrapper():
        print("函数调用时执行")
        return func()
    return wrapper

@decorator
def test():
    print("原函数执行")

# 输出: 装饰器执行,装饰函数: test(在定义时立即执行)

test()  # 输出: 函数调用时执行 \n 原函数执行

装饰器的闭包机制#

装饰器利用了 Python 的闭包特性。内部函数 wrapper 可以访问外部函数 decorator 的参数 func,即使外部函数已经返回。

def my_decorator(func):
    # func 被闭包捕获,保存在 wrapper 的 __closure__ 中
    def wrapper():
        print("Before")
        func()  # 访问外部函数的参数 func
        print("After")
    return wrapper

@my_decorator
def say_hello():
    print("Hello")

# 查看闭包
print(say_hello.__closure__)  # (<cell at 0x...: function object at 0x...>,)
print(say_hello.__closure__[0].cell_contents)  # <function say_hello at 0x...>

递归函数#

递归函数是调用自身的函数。

# 计算阶乘
def factorial(n):
    if n == 0 or n == 1:
        return 1
    return n * factorial(n - 1)

result = factorial(5)  # 120

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

result = fibonacci(6)  # 8

# 递归深度限制
import sys
print(sys.getrecursionlimit())  # 默认1000

函数作为对象#

在 Python 中,函数是一等对象,可以作为参数传递、赋值等。

def greet(name):
    return f"Hello, {name}!"

# 函数可以赋值给变量
my_func = greet
print(my_func("Python"))  # Hello, Python!

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

result = apply_func(greet, "World")  # Hello, World!

# 函数可以作为返回值
def get_greeter():
    def greeter(name):
        return f"Hello, {name}!"
    return greeter

greeter = get_greeter()
print(greeter("Python"))  # Hello, Python!

内置函数#

Python 提供了许多内置函数,常用的有:

# 类型转换
int("123")
float("3.14")
str(123)
list("abc")
tuple([1, 2, 3])

# 数学函数
abs(-5)        # 5
round(3.14159, 2)  # 3.14
min(1, 2, 3)   # 1
max(1, 2, 3)   # 3
sum([1, 2, 3]) # 6

# 序列操作
len("hello")   # 5
sorted([3, 1, 2])  # [1, 2, 3]
reversed([1, 2, 3])  # 迭代器
enumerate(["a", "b"])  # 迭代器
zip([1, 2], ["a", "b"])  # 迭代器

# 其他
isinstance(10, int)  # True
type(10)             # <class 'int'>
hasattr(obj, "attr")  # 检查对象是否有属性
getattr(obj, "attr", default)  # 获取属性

函数式编程工具#

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

# filter() - 过滤序列
numbers = [1, 2, 3, 4, 5]
evens = list(filter(lambda x: x % 2 == 0, numbers))  # [2, 4]

# reduce() - 累积计算(需要导入)
from functools import reduce
numbers = [1, 2, 3, 4]
product = reduce(lambda x, y: x * y, numbers)  # 24

最佳实践#

  1. 函数应该只做一件事
  2. 使用有意义的函数名
  3. 添加文档字符串
  4. 避免使用可变对象作为默认参数
  5. 合理使用装饰器
  6. 注意作用域和命名空间

下一步#

掌握了函数后,可以学习:

  1. 面向对象编程 - 学习类和对象,另一种组织代码的方式
  2. 异常处理 - 学习如何处理错误和异常