注:学习资料来源<尚硅谷>
视频:点击跳转
代码仓库: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'>正因函数是对象,它们具备对象的一切通用行为:
可动态添加属性
greet.author = "Team A" greet.version = 1.0 print(greet.__dict__) # {'author': 'Team A', 'version': 1.0}可赋值给变量
say_hello = greet say_hello() # 等价于 greet()变量
say_hello和greet指向同一个函数对象,内存中仅有一份函数体。可作为参数传递(高阶函数的基础)
可作为返回值(闭包与装饰器的基础)
此外,理解函数参数传递时可变对象与不可变对象的差异至关重要:
不可变对象(如
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)) # Adultlambda 并非必需的语法糖,但合理使用能让代码更加简洁;过度使用则会降低可读性,尤其在逻辑复杂时应改用命名函数。
4. 数据处理四剑客:map、filter、sorted、reduce
这些内置函数提供了声明式的序列处理方法,配合 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)max、min 函数也支持 key 参数,用于选取极值而非排序。
4.4 reduce:累积归并
functools.reduce(function, iterable, initial) 将序列逐步合并为单一值。它的工作方式为:
取出初始值(或首个元素)和下一个元素,执行
function(a, b)将结果作为新的
a,继续与下一个元素合并直至序列结束
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”代码风格:
7. 深浅拷贝:理解共享与独立
Python 中的赋值 = 不拷贝对象,只复制引用。两类拷贝函数均位于 copy 模块。
关键注意点:
深拷贝并非复制一切:不可变对象(如
int、str、仅含不可变元素的元组)会被直接共享引用,因为不可变对象无需担心相互干扰。包含可变子对象的元组,若执行深拷贝,可变子对象会被复制。
自定义类可通过实现
__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 中的可见性由作用域控制,存在四个层级:
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"未使用 nonlocal 或 global 时,在作用域内进行的赋值会被视为创建新的局部变量,这是大多数未预期错误的来源。
9. 闭包:携带状态的函数
闭包(Closure) 由两部分组成:
内层(嵌套)函数
内层函数引用的外层函数的变量(称为自由变量)
即使外层函数已执行完毕,闭包依然能“记住”这些自由变量的值,因为它们被保存在函数对象的 __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:以函数为参数/返回值,分离关注点
数据处理工具链:
map、filter、sorted、reduce实现声明式转换推导式:简洁构建容器的 Pythonic 语法
深浅拷贝:理解对象共享与独立复制
作用域 LEGB 与闭包:变量查找规则、携带状态的函数
装饰器:无侵入增强函数,函数装饰器与类装饰器
类型注解:提升可维护性的文档化类型系统
掌握这些知识后,你将能够编写出更抽象、灵活和可维护的 Python 代码,也为深入学习类元编程、上下文管理器、异步编程等主题打下坚实基础。建议读者在项目中刻意练习这些特性,逐步内化为直觉。