第三章 · 函数

lambda 与作用域

本节目标

学完这一节,你会知道:

  1. lambda 是什么,适合在什么时候使用
  2. 如何用 lambda 配合 sorted() 排序
  3. 什么是变量作用域
  4. Python 查找变量的 LEGB 规则
  5. globalnonlocal 为什么要谨慎使用

这一节有两个主题:一个是短小函数的写法,一个是变量在哪里能被看见。

先跑一个例子

新建文件 lambda_scope_demo.py,写入:

students = [
    {"name": "小明", "score": 85},
    {"name": "小红", "score": 92},
    {"name": "小刚", "score": 78},
]

students.sort(key=lambda student: student["score"])

for student in students:
    print(f"{student['name']}{student['score']}")

运行:

python3 lambda_scope_demo.py

你会看到学生按分数从低到高输出。

这里的 lambda student: student["score"] 是一个短小的匿名函数。

什么是 lambda?

普通函数:

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

lambda 写法:

add = lambda a, b: a + b

调用方式一样:

print(add(3, 5))

lambda 的语法是:

lambda 参数: 返回值

它适合非常简单、临时使用的小函数。

lambda 最常用场景:排序 key

按年龄排序:

users = [
    {"name": "张三", "age": 28},
    {"name": "李四", "age": 22},
    {"name": "王五", "age": 35},
]

users.sort(key=lambda user: user["age"])

print(users)

按字符串长度排序:

words = ["Python", "Go", "JavaScript", "C"]
words.sort(key=lambda word: len(word))

print(words)

key 需要一个函数,告诉 Python 用什么作为排序依据。lambda 正好适合这种短小函数。

lambda 不适合复杂逻辑

不推荐:

result = lambda score: "优秀" if score >= 90 else "及格" if score >= 60 else "不及格"

更清楚的写法:

def get_level(score):
    if score >= 90:
        return "优秀"
    elif score >= 60:
        return "及格"
    else:
        return "不及格"

lambda 的原则:一眼能看懂就用,看不懂就写普通函数。

什么是作用域?

作用域就是变量能被访问的范围。

def say_hi():
    message = "你好"
    print(message)


say_hi()
print(message)

最后一行会报 NameError,因为 message 是函数内部变量,函数外看不见。

Local:局部作用域

函数内部创建的变量是局部变量。

def demo():
    x = 10
    print(x)


demo()

x 只能在 demo() 里面使用。

Global:全局作用域

写在函数外的变量是全局变量。

site_name = "马哥的python小屋"


def show_site():
    print(site_name)


show_site()

函数内部可以读取全局变量。

但不建议随便在函数里修改全局变量。更推荐通过参数和返回值传递数据。

LEGB 规则

Python 查找变量时,大致按这个顺序:

  1. Local:当前函数内部
  2. Enclosing:外层函数
  3. Global:当前文件的全局变量
  4. Built-in:内置名字,比如 printlen

例子:

x = "global"


def outer():
    x = "enclosing"

    def inner():
        x = "local"
        print(x)

    inner()


outer()

输出:

local

因为 Python 先找到了当前函数里的 x

global

如果确实要在函数里修改全局变量,可以用 global

count = 0


def add_one():
    global count
    count += 1


add_one()
print(count)

global 能用,但要谨慎。全局变量改来改去,程序大了以后容易出 bug。

nonlocal

nonlocal 用来修改外层函数里的变量。

def make_counter():
    count = 0

    def add_one():
        nonlocal count
        count += 1
        return count

    return add_one


counter = make_counter()
print(counter())
print(counter())

输出:

1
2

这里 count 不在 add_one() 里面,也不是全局变量,而是在外层函数 make_counter() 里。

逐行拆解

再看开头的排序代码:

students.sort(key=lambda student: student["score"])

sort() 会遍历每个学生字典。

lambda student: student["score"] 告诉它:排序时使用每个学生的 score

for student in students:

这里的 student 是循环变量,只在当前循环逻辑中使用。

自己改一改

lambda_scope_demo.py 改成按名字长度排序:

names = ["Python", "Go", "JavaScript", "C"]

names.sort(key=lambda name: len(name))

print(names)

然后继续改:

  1. 改成按字符串最后一个字符排序
  2. 改成按分数从高到低排序,提示:reverse=True
  3. 把 lambda 改成普通函数再试一次

常见错误

1. lambda 写太复杂

lambda 适合简单表达式,不适合写很多判断和循环。复杂逻辑请用普通函数。

2. 函数外访问局部变量

def demo():
    name = "小明"


print(name)

这会报 NameError

3. 在函数里修改全局变量但没写 global

count = 0


def add_one():
    count += 1

这会报 UnboundLocalError。更推荐写成:

def add_one(count):
    return count + 1

4. 覆盖内置名字

不要这样写:

list = [1, 2, 3]

list 是 Python 内置名字。覆盖后,后面想用 list() 就容易出问题。

小练习

练习 1:按价格排序

给定商品列表:

products = [
    {"name": "苹果", "price": 3.5},
    {"name": "香蕉", "price": 2.8},
    {"name": "橙子", "price": 4.2},
]

lambda 按价格从低到高排序。

练习 2:局部变量观察

定义函数,在函数内部创建变量并打印。然后试着在函数外打印这个变量,观察报错。

练习 3:计数器

用普通函数写一个 add_one(count),接收数字并返回加 1 后的结果。

参考答案

练习 1:

products = [
    {"name": "苹果", "price": 3.5},
    {"name": "香蕉", "price": 2.8},
    {"name": "橙子", "price": 4.2},
]

products.sort(key=lambda product: product["price"])

print(products)

练习 2:

def demo():
    message = "函数内部变量"
    print(message)


demo()
# print(message)  # 取消注释会报 NameError

练习 3:

def add_one(count):
    return count + 1


count = 0
count = add_one(count)
count = add_one(count)

print(count)

小结

这一节你学会了:

  1. lambda 可以创建短小的匿名函数
  2. lambda 常用于 sorted()list.sort()key
  3. 作用域决定变量在哪里能被访问
  4. Python 按 LEGB 顺序查找变量
  5. globalnonlocal 能修改外层变量,但要谨慎使用

下一节我们会学习装饰器。它可以在不修改原函数代码的情况下,给函数增加新功能。

短函数和变量地盘,慢慢就能分清

lambda 适合短平快,作用域负责告诉变量能在哪儿被看见。这里有一点抽象,马哥建议别硬背 LEGB,先多跑几个函数内外变量的例子。等你遇到 NameError 时,会突然想起:原来它只是找不到家。

讨论 (0)

还没有评论,来抢沙发吧!