📊 阅读时长:18分钟 | 关键词:Python lambda、匿名函数、封包解包、命名空间、作用域、global、nonlocal

引言:从"会用函数"到"理解函数"

上一篇文章我们学了函数的基础——定义、调用、参数、return。这些足够让你写出能用的函数了。

但如果你想写出"好"的函数,想理解为什么有些代码会出 Bug、想读懂别人的代码,你需要更进一步:理解 Python 函数背后的运行机制。

这篇文章会讲四个进阶话题:

话题 解决的问题
lambda 匿名函数 什么时候不需要 def?
封包与解包 *args**kwargs 的本质是什么?
命名空间与作用域 变量到底存在哪里?为什么函数内访问不到外面的变量?
global 和 nonlocal 怎么在函数内修改外部变量?

一、lambda:匿名函数

1.1 什么是 lambda?

lambda 是 Python 中创建匿名函数的关键字。匿名函数就是"没有名字的函数",适合用在需要一个函数对象、但又不想专门用 def 定义的场景。

# 格式:lambda 参数1, 参数2, ... : 表达式

# 传统函数
def add(x, y):
    return x + y

# lambda 等价写法
add = lambda x, y: x + y

print(add(3, 4))   # 7
1.2 lambda 的限制

lambda 和 def 函数有几个关键区别:

特性 def 函数 lambda
名字 无(匿名)
函数体 多行代码块 只能一个表达式
return 需要显式写 自动返回表达式结果
文档字符串 支持 不支持
复杂逻辑 支持 不适合
# lambda 的参数可以有多个,但表达式只能有一个
print((lambda: '直接调用')())               # '直接调用'
print((lambda x, y, z: x + y + z)(1, 2, 3)) # 6

# 可以赋值给变量
f = lambda x, y: x * y
print(f(3, 4))  # 12
1.3 lambda 的典型使用场景

场景一:作为排序的 key

# 按绝对值排序
nums = [8, -5, 9, -6, 2, -1]
sorted_nums = sorted(nums, key=lambda x: abs(x))
print(sorted_nums)   # [-1, 2, -5, -6, 8, 9]

# 等价于 def 写法(但更啰嗦)
def abs_value(x):
    return abs(x)
sorted_nums = sorted(nums, key=abs_value)

场景二:作为 map/filter 的参数

nums = [1, 2, 3, 4, 5]

# 用 lambda 配合 map
squares = list(map(lambda x: x**2, nums))
print(squares)   # [1, 4, 9, 16, 25]

# 用 lambda 配合 filter
evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens)     # [2, 4]

场景三:简单的回调函数

# GUI 编程中常见
button.on_click(lambda: print('按钮被点击了'))

# 或者在定时器中
import time
# scheduler.add_task(lambda: send_notification(), delay=60)

什么时候该用 lambda,什么时候该用 def?

  • 逻辑简单、一行表达式能搞定 → lambda
  • 需要多行代码、需要文档说明 → def
  • lambda 嵌套或逻辑复杂 → 果断用 def

二、封包与解包

2.1 封包(Packing)

将多个值同时赋值给一个变量时,Python 自动打包成元组:

tup = 345, 'hello', 789
print(tup)          # (345, 'hello', 789)
print(type(tup))    # <class 'tuple'>
2.2 解包(Unpacking)

解包是把可迭代对象中的元素分别赋值给多个变量:

# 基本解包
a, b, c = [4, 3, 'a']
print(a, b, c)   # 4 3 a

# 用 * 捕获剩余元素
a, *b, c = 'hello'
print(a)    # 'h'
print(b)    # ['e', 'l', 'l'] —— 中间的被收集到列表
print(c)    # 'o'

# * 可以出现在任意位置
first, *mid, last = [1, 2, 3, 4, 5]
print(first)   # 1
print(mid)     # [2, 3, 4]
print(last)    # 5

# 用 _ 忽略不需要的值
_, *mid, _ = [1, 2, 3, 4, 5]
print(mid)     # [2, 3, 4]

# 全部解包到列表
*a, = 'hel'
print(a)       # ['h', 'e', 'l']
2.3 函数调用中的解包

这是解包最实用的场景——在调用函数时展开可迭代对象:

def func(a, b, c):
    print(a, b, c)

# * 解包:将可迭代对象展开为位置参数
tup = (1, 2, 3)
func(*tup)         # 等价于 func(1, 2, 3) → 1 2 3

lst = [4, 5, 6]
func(*lst)         # 等价于 func(4, 5, 6) → 4 5 6

# ** 解包:将字典展开为关键字参数
d = {'a': 1, 'b': 2, 'c': 3}
func(**d)          # 等价于 func(a=1, b=2, c=3) → 1 2 3

# 注意:字典的 key 必须和形参名匹配
# func(**{'x': 1, 'y': 2})  # TypeError! 形参名不匹配

在这里插入图片描述

三、命名空间:变量存在哪里?

3.1 什么是命名空间?

命名空间(Namespace)是从名称到对象的映射。简单说,就是"变量名 → 值"的对照表。

Python 有三个层级的命名空间:

命名空间 生命周期 内容
内置命名空间 解释器启动 → 终止 printlenint 等内置函数
全局命名空间 模块读入 → 终止 模块中定义的变量、函数、类
局部命名空间 函数调用 → 函数返回 函数中定义的变量、参数
# 查看命名空间
import builtins
# print(dir(builtins))  # 查看所有内置名称

def func(arg1, arg2):
    num = 666
    print(locals())    # 查看局部命名空间
    # {'arg1': ..., 'arg2': ..., 'num': 666}

num = 111
func(222, 333)

print(globals())       # 查看全局命名空间(内容很多)
# 在全局作用域中,locals() 等价于 globals()
3.2 命名空间的查找顺序

当 Python 遇到一个变量名时,按照 局部 → 全局 → 内置 的顺序查找:

局部命名空间  →  全局命名空间  →  内置命名空间
   (Local)         (Global)        (Built-in)
# 演示查找顺序
x = '全局变量'      # 全局命名空间

def demo():
    x = '局部变量'  # 局部命名空间
    print(x)        # 输出:局部变量 —— 先在局部找到了

demo()
print(x)            # 输出:全局变量 —— 在全局找到
# 内置命名空间优先级最低
print = '我把 print 覆盖了'   # 全局命名空间中定义了 print

def demo():
    # print('hello')          # TypeError! 现在 print 是字符串了
    pass

# 但可以通过 builtins 模块访问
import builtins
builtins.print('我还能用')    # 我还能用

在这里插入图片描述

四、作用域:哪里可以访问什么变量

4.1 什么是作用域?

作用域决定了代码的哪部分可以访问哪些变量。Python 有四个层级的作用域(LEGB):

缩写 作用域 含义
L Local 局部作用域——函数内部
E Enclosing 闭包作用域——外层函数的局部
G Global 全局作用域——模块级别
B Built-in 内置作用域——Python 内置

查找顺序:L → E → G → B,找到即停止,找不到就报 NameError

4.2 局部作用域(Local)
def func(x, y):
    a = 3
    b = 4
    print(x, y, a, b)   # 可以访问函数内部的变量

func(1, 2)   # 1 2 3 4
# print(a)   # NameError! a 只在函数内部可见

重要规则:函数会引入新的作用域,但条件语句、循环语句不会

if True:
    x = 10          # 在 if 中定义的变量

print(x)            # 10 —— 在外部也能访问!

for i in range(3):
    y = i * 2

print(y)            # 4 —— 循环中定义的变量在外部也能访问
4.3 闭包作用域(Enclosing)

嵌套函数中,内部函数可以访问外部函数的变量:

def outer(a):
    b = 2
    def inner(c):
        # inner 可以访问 outer 中的 a 和 b
        return a + b + c
    return inner

f = outer(1)
print(f(3))    # 1 + 2 + 3 = 6
4.4 全局作用域(Global)
x = 10     # 全局变量

def func():
    print(x)    # 可以读取全局变量

func()     # 10

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

func2()    # 20
print(x)   # 10 —— 全局变量没有被修改

核心规则:在函数内部,如果只是读取变量,可以访问外层作用域;但如果赋值,Python 会创建一个新的局部变量。

五、global 和 nonlocal:跨越作用域边界

5.1 global:在函数内修改全局变量
x = 10

def func():
    global x        # 声明:我要用全局的 x,不要创建局部的
    x = 20          # 修改全局变量
    print(f'函数内: x={x}')

func()             # 函数内: x=20
print(f'函数外: x={x}')  # 函数外: x=20 —— 全局变量被修改了

global 使用原则

  • 只在确实需要修改全局变量时使用
  • 过度使用 global 是代码设计不佳的信号——考虑用返回值或类来替代
  • 在函数内只读取全局变量时,不需要 global
5.2 nonlocal:在嵌套函数中修改外层函数的变量
def outer():
    x = 10
    def inner():
        nonlocal x     # 声明:我要用外层函数的 x
        x = 20         # 修改外层函数的变量
        print(f'inner: x={x}')
    inner()
    print(f'outer: x={x}')

outer()
# inner: x=20
# outer: x=20 —— 外层函数的变量被修改了

nonlocal vs global

关键字 作用范围 使用场景
global 声明使用全局变量 在函数内修改模块级别的变量
nonlocal 声明使用外层函数的变量 在嵌套函数中修改外层函数的变量
# 综合示例
def outer():
    global a, b         # a, b 是全局变量
    a, b, c, d = 3, 4, 5, 6
    print(f'outer: a={a}, b={b}')

    def inner():
        global a, b     # a, b 是全局变量
        nonlocal c, d   # c, d 是 outer 中的变量
        a, b, c, d = 7, 8, 9, 0

    inner()
    print(f'outer 内层后: c={c}, d={d}')

a, b = 1, 2
outer()
print(f'全局: a={a}, b={b}')

# 输出:
# outer: a=3, b=4
# outer 内层后: c=9, d=0
# 全局: a=7, b=8

在这里插入图片描述

六、动手练习

练习 1:lambda 排序实战

# 有一个学生列表,每个学生是 (姓名, 成绩) 元组
students = [('小明', 85), ('小红', 92), ('小刚', 78), ('小丽', 95)]

# 1. 按成绩从高到低排序
sorted_by_score = sorted(students, key=lambda s: s[1], reverse=True)
print(sorted_by_score)

# 2. 按姓名长度排序
sorted_by_name_len = sorted(students, key=lambda s: len(s[0]))
print(sorted_by_name_len)

练习 2:解包的高级用法

# 交换两个变量的值(一行搞定)
a, b = 10, 20
a, b = b, a
print(a, b)   # 20, 10

# 同时遍历两个列表
names = ['小明', '小红', '小刚']
scores = [85, 92, 78]
for name, score in zip(names, scores):
    print(f'{name}: {score}')

练习 3:作用域练习

# 预测以下代码的输出
x = 1

def outer():
    x = 2
    def inner():
        x = 3
        print(f'inner: x={x}')     # ?
    inner()
    print(f'outer: x={x}')         # ?

outer()
print(f'global: x={x}')            # ?

小结

这篇文章深入了 Python 函数的四个进阶话题:

| 知识点 | 核心内容 |
|:—|:—|:—|
| lambda | 匿名函数,一个表达式,适合简单场景 |
| 解包 | * 展开可迭代对象为位置参数,** 展开字典为关键字参数 |
| 命名空间 | 局部 → 全局 → 内置 的查找顺序 |
| 作用域 LEGB | Local → Enclosing → Global → Built-in |
| global | 在函数内声明使用全局变量 |
| nonlocal | 在嵌套函数中声明使用外层函数的变量 |

下一篇文章,我们将正式进入 Python 中最重要的编程范式——面向对象编程(OOP):类与对象、属性和方法、self 的本质,带你理解 Python 中一切皆对象的哲学。


本文是「Python从入门到数据分析」系列的第 8 篇,共 24 篇。关注我,不错过后续更新。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐