08函数进阶

08函数进阶

_

注:学习资料来源<尚硅谷>

视频:点击跳转

代码仓库:pythonStudy

函数是 Python 中最基础也最强大的抽象单元。在入门阶段我们学会了用 def 定义函数、传递参数、返回结果;但 Python 函数的真正威力远不止于此——函数本身是一等对象,可以被赋值、传递、嵌套和动态修改。掌握这些进阶特性后,你将能写出更灵活、更可复用的代码,并顺畅进入装饰器、元编程等高级领域。

本文将从“函数即对象”这一根基出发,系统讲解多返回值与参数解包、高阶函数与匿名函数、数据处理核心工具(map / filter / sorted / reduce)、推导式、深浅拷贝、作用域与闭包、装饰器(含类装饰器)以及类型注解。所有示例均采用通用命名,面向 Python 3.10+ 环境,侧重概念精析与实战应用。


1. 重新认识函数:可调用的对象

在 Python 中,def 语句创建的并不是一个神秘的“子程序”,而是一个 function 类型的实例对象。这一点可以通过 type() 验证:

def greet():
    print("Hello!")

print(type(greet))  # <class 'function'>

正因函数是对象,它们具备对象的一切通用行为

  1. 可动态添加属性

    greet.author = "Team A"
    greet.version = 1.0
    print(greet.__dict__)  # {'author': 'Team A', 'version': 1.0}
  2. 可赋值给变量

    say_hello = greet
    say_hello()  # 等价于 greet()

    变量 say_hellogreet 指向同一个函数对象,内存中仅有一份函数体。

  3. 可作为参数传递(高阶函数的基础)

  4. 可作为返回值(闭包与装饰器的基础)

此外,理解函数参数传递时可变对象与不可变对象的差异至关重要:

  • 不可变对象(如 int, str, tuple)作为实参时,函数内部无法改变外部变量的值,因为任何修改都会创建新对象。

  • 可变对象(如 list, dict, 自定义实例)作为实参时,函数内部对对象的原地修改会直接反映到外部,因为它们共享同一块内存。

def modify_num(n):
    n = 999          # 仅改变了局部名称绑定,外部 a 不变
a = 100
modify_num(a)
print(a)             # 100

def modify_list(lst):
    lst.append(4)    # 修改了传入列表的内容
b = [1,2,3]
modify_list(b)
print(b)             # [1, 2, 3, 4]

这一特性直接影响后续对闭包和装饰器中状态管理的理解。


2. 灵活的参数与返回值

2.1 多返回值与隐式元组打包

return 后跟多个逗号分隔的值时,Python 会将其自动打包成一个元组返回。调用方可通过解包(unpacking)直接接收多个变量。

def calc(x, y):
    return x + y, x - y, x * y

res = calc(10, 5)        # (15, 5, 50)
sum_, sub_, prod = calc(10, 5)

2.2 参数的打包与解包

函数定义侧,*args 将多余的位置参数打包成元组,**kwargs 将多余的关键字参数打包成字典。
函数调用侧,*iterable 将可迭代对象解包为位置参数,**dict 将字典解包为关键字参数。

def show(*args, **kwargs):
    print("args:", args)
    print("kwargs:", kwargs)

nums = (1, 2, 3)
info = {"name": "Alice", "age": 30}
show(*nums, **info)
# args: (1, 2, 3)
# kwargs: {'name': 'Alice', 'age': 30}

这种对称性赋予了 API 设计极大的灵活性,尤其在编写装饰器、代理函数时需要透传任意参数时不可或缺。


3. 高阶函数与匿名函数(lambda)

高阶函数是指满足以下条件之一的函数:

  • 接受一个或多个函数作为参数

  • 返回一个函数

高阶函数将行为数据分离,是函数式编程风格的基石。例如,一个日志函数可以接收不同级别的格式化函数:

def log(formatter, message):
    print(formatter(message))

def info(msg):
    return f"[INFO] {msg}"
def error(msg):
    return f"[ERROR] {msg}"

log(info, "Server started")
log(error, "Connection lost")

匿名函数(lambda) 适用于需要临时、简单函数的场景,其语法为:

lambda 参数: 表达式

表达式的结果自动成为返回值,因此不能包含语句、循环或复杂逻辑。典型应用是作为 map / filter 等函数的快捷参数:

sum_result = (lambda a, b: a + b)(3, 5)  # 8

# 结合条件表达式
is_adult = lambda age: "Adult" if age >= 18 else "Minor"
print(is_adult(20))  # Adult

lambda 并非必需的语法糖,但合理使用能让代码更加简洁;过度使用则会降低可读性,尤其在逻辑复杂时应改用命名函数。


4. 数据处理四剑客:mapfiltersortedreduce

这些内置函数提供了声明式的序列处理方法,配合 lambda 可以避免显式循环。

4.1 map:映射转换

对可迭代对象的每个元素应用同一函数,返回迭代器(惰性计算)。

nums = [1, 2, 3, 4]
doubled = list(map(lambda x: x * 2, nums))  # [2, 4, 6, 8]

map 不改变原数据,也不改变元素个数。

4.2 filter:条件过滤

保留满足谓词函数(返回 True)的元素,返回迭代器。

nums = [10, 20, 30, 40, 50]
big = list(filter(lambda x: x > 30, nums))  # [40, 50]

若传入 filter(None, iterable),则会自动滤除所有“假值”(False, 0, '', None 等)。

4.3 sorted:灵活排序

返回一个新的排好序的列表,支持 key 函数自定义排序依据,reverse 控制方向。

people = [{"name": "Alice", "age": 25}, {"name": "Bob", "age": 20}]
sorted_people = sorted(people, key=lambda p: p["age"], reverse=True)

maxmin 函数也支持 key 参数,用于选取极值而非排序。

4.4 reduce:累积归并

functools.reduce(function, iterable, initial) 将序列逐步合并为单一值。它的工作方式为:

  1. 取出初始值(或首个元素)和下一个元素,执行 function(a, b)

  2. 将结果作为新的 a,继续与下一个元素合并

  3. 直至序列结束

from functools import reduce
total = reduce(lambda a, b: a + b, [1, 2, 3, 4], 0)  # 10

这四个工具均返回迭代器(reduce 除外),意味着它们是惰性计算,只在被遍历或构造为具体容器时才真正执行,这一特性在处理大数据集时尤为有用。


5. 推导式:简洁的容器生成语法

推导式是 Python 对数学“集合构造式”的直接翻译,比 map/filter 的组合更易读。

  • 列表推导式[expr for item in iterable if condition]

  • 字典推导式{key_expr: value_expr for ...}

  • 集合推导式{expr for ...}

squares = [n**2 for n in range(10) if n % 2 == 0] # 偶数平方

names = ["Alice", "Bob"]
scores = [90, 85]
score_dict = {n: s for n, s in zip(names, scores)}  # 字典

# 注意:(expr for ...) 不是元组推导式,它生成的是生成器对象!
gen = (n**2 for n in [1,2,3])
print(gen)        # <generator object ...>
print(list(gen))  # [1, 4, 9]

推导式在内部以 C 语言速度执行,性能通常优于显式的 for 循环 + append


6. 常用内置函数速览

掌握以下内置函数能大幅减少造轮子,更接近“Pythonic”代码风格:

类别

函数

说明

输出

print

支持 sep, end, file, flush

输入

input

读取用户输入字符串

类型转换

int, float, str, bool, list, tuple, set, dict

构造器或转换

数学

abs, round, pow, divmod

绝对值、舍入、幂运算、商余对

聚合

max, min, sum

支持 key 函数

序列

len, range, enumerate, zip, map, filter

长度、范围、索引枚举、并行迭代

逻辑

all, any

全真判断、存在真判断

字符编码

ord, chr

Unicode 码点与字符互转

对象

type, isinstance, issubclass, id

类型与身份检查


7. 深浅拷贝:理解共享与独立

Python 中的赋值 = 不拷贝对象,只复制引用。两类拷贝函数均位于 copy 模块。

操作

效果

内部依赖

b = a

别名,a 和 b 指向同一对象

copy.copy(a)

浅拷贝:复制外层容器,内部元素仍共享

__copy__ 方法

copy.deepcopy(a)

深拷贝:递归复制所有可变对象,完全独立

__deepcopy__ 方法

关键注意点:

  • 深拷贝并非复制一切:不可变对象(如 intstr、仅含不可变元素的元组)会被直接共享引用,因为不可变对象无需担心相互干扰。

  • 包含可变子对象的元组,若执行深拷贝,可变子对象会被复制。

  • 自定义类可通过实现 __copy__ / __deepcopy__ 控制拷贝行为。

import copy
a = [[1, 2], (3, 4)]
b = copy.deepcopy(a)
b[0].append(99)
print(a[0])  # [1, 2] 未受影响
print(a[1] is b[1])  # True,元组不可变,故共享

深浅拷贝对理解闭包环境中的可变状态管理、装饰器缓存等至关重要。


8. 四种作用域与 LEGB 规则

变量在 Python 中的可见性由作用域控制,存在四个层级:

作用域

缩写

描述

Local

L

函数内部(每次调用创建新作用域)

Enclosing

E

外层函数的作用域(嵌套函数中的中间层)

Global

G

模块级别(.py 文件)

Built-in

B

Python 内置命名空间(print, len 等)

LEGB 查找顺序:当访问一个变量时,Python 按 L → E → G → B 的顺序搜索,找到即停止。

若需在函数内部修改外层变量,必须显式声明:

  • nonlocal:用于 Enclosing(外层函数)变量

  • global:用于全局变量

x = "global"
def outer():
    x = "outer"
    def inner():
        nonlocal x      # 表示修改 outer 中的 x
        x = "inner"
    inner()
    print(x)            # "inner"
outer()
print(x)                # "global"

未使用 nonlocalglobal 时,在作用域内进行的赋值会被视为创建新的局部变量,这是大多数未预期错误的来源。


9. 闭包:携带状态的函数

闭包(Closure) 由两部分组成:

  1. 内层(嵌套)函数

  2. 内层函数引用的外层函数的变量(称为自由变量

即使外层函数已执行完毕,闭包依然能“记住”这些自由变量的值,因为它们被保存在函数对象的 __closure__ 元组中,以 cell 对象 的形式存在。

闭包形成的三个条件

  • 函数嵌套

  • 内层函数使用了外层函数的变量

  • 外层函数返回内层函数

def counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

c1 = counter()
print(c1(), c1(), c1())  # 1 2 3
c2 = counter()            # 新闭包,独立状态
print(c2())              # 1

闭包的 cell 对象可通过 f.__closure__[0].cell_contents 访问。只有被内层实际引用的变量才会捕获到闭包中,未被引用的变量会随着外层函数结束而正常销毁。

应用场景

  • 状态保持:避免全局变量,实现轻量级计数器、累加器

  • 配置化函数:固定部分参数,生成专用版本(偏函数功能)

  • 装饰器核心基石:装饰器本质上就是接受函数作为参数的闭包

闭包 vs 类:闭包适合简单的状态封装;当状态复杂或需要共享多个方法时,使用类 + 实例属性更为清晰。

def make_formatter(char, n):
    def format(msg):
        return char * n + msg + char * n
    return format

star = make_formatter('*', 3)
print(star("Hello"))  # ***Hello***

10. 装饰器:无侵入的功能增强

装饰器是 Python 最优雅的元编程特性之一,它能在不修改原函数代码的前提下,动态地为函数增加额外行为。其本质是一个接受函数并返回新函数的高阶函数

10.1 函数装饰器

一个最简日志装饰器:

def log(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        result = func(*args, **kwargs)
        print(f"Result: {result}")
        return result
    return wrapper

@log
def add(a, b):
    return a + b

@log 语法糖等价于 add = log(add)。此后调用 add 实际上是调用 wrapper,由 wrapper 执行前置/后置逻辑并调用原函数。

wrapper 必须使用 *args, **kwargs 以兼容原函数的任意参数签名,并 return 原函数的结果以保持返回值语义。

带参数的装饰器需要三层嵌套:最外层接收配置参数,中间层接收被装饰函数,内层 wrapper 执行实际逻辑。

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    print(f"Hello {name}")

多个装饰器叠加:距离函数定义最近的装饰器最先被应用(从下往上装饰),执行时则是从上往下:

@bold
@italic
def text():
    return "Hi"
# 等价于 bold(italic(text))
# 执行顺序:bold → italic → 原函数

10.2 类装饰器

任何实现了 __call__ 方法的类的实例都是可调用对象,因此可以充当装饰器。类装饰器在需要维护较复杂状态时比函数装饰器更具优势。

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0
    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"Call {self.count} of {self.func.__name__}")
        return self.func(*args, **kwargs)

@CountCalls
def say_hello():
    print("Hello!")

若装饰器需要参数,则在 __init__ 中接收参数,__call__ 接收函数:

class Prefix:
    def __init__(self, prefix):
        self.prefix = prefix
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print(f"{self.prefix}: before")
            return func(*args, **kwargs)
        return wrapper

@Prefix(">>>")
def task():
    return "done"

装饰器的常见用途:日志记录、性能计时、权限校验、缓存、输入验证、事务控制、注册回调等。


11. 类型注解:代码即文档

类型注解(Type Hints)是 Python 3.5+ 引入的语法,允许为变量和函数标注预期类型。注解不强制类型检查,但极大地提升了代码可读性、IDE 智能提示和静态分析(如 mypy)的效果。

11.1 变量注解

基础语法:变量名: 类型 = 值。可以使用内置类型和 list[int]dict[str, int] 等泛型写法(Python 3.9+ 内置集合支持原生泛型,此前需从 typing 导入)。

name: str = "Alice"
scores: list[int] = [90, 85]
city_set: set[str] = {"Paris", "Tokyo"}
person: dict[str, int] = {"Alice": 25}
# 联合类型
mixed: list[str | int] = ["apple", 3]

元组注解较为特殊:

  • tuple[int, int] 固定长度和对应类型

  • tuple[int, ...] 任意长度但元素均为 int

11.2 函数注解

def process(data: list[int]) -> tuple[int, int]:
    return max(data), min(data)
  • 参数默认值后的注解可省略

  • 使用 -> 返回类型 标注返回值

  • 可通过 函数名.__annotations__ 查看注解字典

对于 *args**kwargs,类型代表元素的类型:

def log(*msgs: str) -> None:
    for m in msgs:
        print(m)

11.3 类型推导与静态检查

Python 解释器本身不执行类型注解,但 IDE 和 mypy 等工具能检测类型不匹配。例如:

nums: list[int] = [1, 2, 3]
nums.append("4")  # IDE 可能会警告

这有助于在开发阶段捕获错误。

类型注解是 Python 走向大型项目的关键基础设施,与文档注释、自动化测试共同构成代码质量保障体系。


12. 小结

本文深入剖析了 Python 函数进阶中的关键概念:

  • 函数是一等对象:可赋值、可传递、可携带属性

  • 灵活的参数系统:多返回值、*args/**kwargs 打包与解包

  • 高阶函数与 lambda:以函数为参数/返回值,分离关注点

  • 数据处理工具链mapfiltersortedreduce 实现声明式转换

  • 推导式:简洁构建容器的 Pythonic 语法

  • 深浅拷贝:理解对象共享与独立复制

  • 作用域 LEGB 与闭包:变量查找规则、携带状态的函数

  • 装饰器:无侵入增强函数,函数装饰器与类装饰器

  • 类型注解:提升可维护性的文档化类型系统

掌握这些知识后,你将能够编写出更抽象、灵活和可维护的 Python 代码,也为深入学习类元编程、上下文管理器、异步编程等主题打下坚实基础。建议读者在项目中刻意练习这些特性,逐步内化为直觉。


07面向对象 2026-04-24

评论区