Python 函数全面总结

2 阅读21分钟

本文面向已有前端开发基础、正在学习 Python 的开发者。

用前端开发中熟悉的函数模型,理解 Python 函数的定义、参数、返回值和常见用法。

这篇不是只讲 def 怎么写,而是把 Python 函数当成一个完整系统来看。

如果你有 JavaScript / TypeScript 经验,可以先这样建立直觉:

  • def:像 JS 的 function
  • return:像 JS 的 return
  • None:像 JS 函数没有返回值时的 undefined
  • *args:像 JS 的剩余参数 ...args
  • **kwargs:像 JS 常用的对象参数,但由 Python 自动收集
  • lambda:像 JS 箭头函数,但只能写一个表达式
  • 闭包:和 JS 闭包本质一样,内部函数记住外部变量
  • 装饰器:像“函数外面套一层函数”,常见于日志、权限、缓存、路由注册
  • 生成器:像可以暂停的函数,用来惰性产出数据
  • async 函数:像 JS 的 async function,返回可等待对象

学习函数时,重点记 7 件事:

  • 函数怎么定义和调用
  • 参数怎么传
  • 返回值怎么拿
  • 变量作用域怎么找
  • 函数本身也是对象
  • 闭包和装饰器怎么理解
  • 生成器和异步函数什么时候用
Python 函数系统

  定义调用
     |
     +-- def / return / None / docstring
     |
  参数系统
     |
     +-- 位置参数 / 关键字参数 / 默认参数
     +-- *args / **kwargs / 解包传参
     +-- / 位置-only / * 关键字-only
     |
  返回值
     |
     +-- 单返回值 / 多返回值 / 提前 return
     |
  作用域
     |
     +-- LEGB / global / nonlocal
     |
  函数对象
     |
     +-- 回调 / lambda / 高阶函数 / 闭包 / 装饰器
     |
  特殊函数
     |
     +-- 生成器 / async 函数
     |
  工程写法
     |
     +-- 文档字符串 / 命名 / 坑点

一、先建立总地图

知识点前端脑内映射一句话记住
deffunction定义一个函数
returnreturn把结果交回调用方
不写 return返回 undefinedPython 返回 None
位置参数普通实参按顺序传
关键字参数对象参数的一部分能力按名字传
默认参数默认值参数不传就用默认值
*args...args收集多余的位置参数
**kwargs对象参数收集多余的关键字参数
*list展开数组把列表拆成多个位置参数
**dict展开对象把字典拆成多个关键字参数
lambda箭头函数只能写一个表达式的小函数
闭包JS 闭包内层函数记住外层变量
装饰器包装函数在不改原函数的情况下增强能力
生成器iterator / generator一次产出一个值
async 函数async function定义可等待的异步逻辑
def add(a, b): # 定义函数,接收 a 和 b
    return a + b # 返回两个数字之和

result = add(1, 2) # 调用函数
print(result) # 输出 3

二、def:Python 用缩进定义函数体

1. 前端脑内映射

Python 的 def 对应 JS 的 function

JS 使用 {} 表示函数体,Python 使用缩进表示函数体。

2. Python 写法

def format_user(name, age): # 定义函数
    text = f'{name} is {age} years old' # 拼接字符串
    return text # 返回结果

message = format_user('Alice', 18) # 调用函数
print(message) # 输出 Alice is 18 years old

函数体里如果暂时不写内容,可以用 pass 占位。

def todo():
    pass # 先占位,避免语法报错

5. 和 JS/TS 的关键差异

JS:

function formatUser(name, age) {
  const text = `${name} is ${age} years old`;
  return text;
}

Python:

def format_user(name, age):
    text = f'{name} is {age} years old'
    return text

6. 容易踩的坑

Python 的缩进就是语法,不是单纯格式。

def say_hello():
    print('hello') # 在函数里面

print('done') # 不在函数里面

三、return:返回值和提前结束

1. 前端脑内映射

Python 的 return 和 JS 的 return 基本一样。

2. Python 写法

def get_total(price, count):
    return price * count # 返回总价

total = get_total(10, 3)
print(total) # 输出 30

可以提前返回,常用于参数校验。

def get_discount(amount):
    if amount <= 0:
        return 0 # 提前结束函数

    if amount >= 100:
        return 20

    return 5

print(get_discount(120)) # 输出 20

4. 不写 return 时返回 None

def log_user(name):
    print(f'user: {name}') # 只打印,不返回

result = log_user('Alice')
print(result) # 输出 None

JS 函数不写 return 时返回 undefined;Python 函数不写 return 时返回 None

5. 条件表达式:简单 if-else 可以写成一行

Python 也有类似 JS 三元表达式的写法,但顺序不一样。

age = 18
text = '成年' if age >= 18 else '未成年'

print(text) # 输出 成年

它适合非常简单的二选一逻辑。

def get_level(score):
    return 'pass' if score >= 60 else 'fail'

如果条件很多,或者每个分支里要做多步事情,就不要硬写成一行,普通 if-else 更清楚。

四、参数系统:函数最重要的部分

Python 参数比 JS 更细,因为它同时支持按顺序传、按名字传、收集参数、强制规则。

调用函数时,参数大概分两类

  位置参数
    add(1, 2)

  关键字参数
    create_user(name='Alice', age=18)

位置参数和关键字参数

1. 前端脑内映射

位置参数像 JS 普通实参,靠顺序匹配。

关键字参数像对象参数的体验,靠名字匹配。

2. Python 写法

def create_user(name, age):
    return {'name': name, 'age': age}

user1 = create_user('Alice', 18) # 位置参数,按顺序传
user2 = create_user(age=20, name='Bob') # 关键字参数,按名字传

print(user1) # 输出 {'name': 'Alice', 'age': 18}
print(user2) # 输出 {'name': 'Bob', 'age': 20}

3. 容易踩的坑

位置参数必须写在关键字参数前面。

def create_user(name, age):
    return {'name': name, 'age': age}

create_user('Alice', age=18) # 正确

# create_user(name='Alice', 18) # 错误,位置参数不能放在关键字参数后面

同一个参数不能传两次。

def create_user(name, age):
    return {'name': name, 'age': age}

# create_user('Alice', name='Bob') # 错误,name 被传了两次

默认参数

1. 前端脑内映射

默认参数和 JS / TS 的默认参数类似。

2. Python 写法

def greet(name='Guest'):
    return f'Hello {name}'

print(greet()) # 输出 Hello Guest
print(greet('Alice')) # 输出 Hello Alice

默认参数一般放在必填参数后面。

def create_product(name, price, category='default'):
    return {
        'name': name,
        'price': price,
        'category': category
    }

product = create_product('Keyboard', 199)
print(product) # category 使用默认值 default

3. 容易踩的坑:不要用可变对象做默认值

错误写法:

def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item('A')) # 输出 ['A']
print(add_item('B')) # 输出 ['A', 'B'],不是 ['B']

原因:默认参数只在函数定义时创建一次。

推荐写法:

def add_item(item, items=None):
    if items is None:
        items = []

    items.append(item)
    return items

print(add_item('A')) # 输出 ['A']
print(add_item('B')) # 输出 ['B']

*args:可变位置参数

1. 前端脑内映射

*args 类似 JS 的剩余参数 ...args

2. Python 写法

def sum_all(*args):
    total = 0

    for number in args:
        total += number

    return total

print(sum_all(1, 2, 3)) # 输出 6

args 本质是一个元组。

def show_args(*args):
    print(args)
    print(type(args))

show_args('a', 'b') # 输出 ('a', 'b') 和 <class 'tuple'>

**kwargs:可变关键字参数

1. 前端脑内映射

**kwargs 可以理解为 Python 自动把多余的命名参数收集成一个对象。

2. Python 写法

def create_user(**kwargs):
    print(kwargs)
    return kwargs

user = create_user(name='Alice', age=18, city='Shanghai')
print(user) # 输出 {'name': 'Alice', 'age': 18, 'city': 'Shanghai'}

kwargs 本质是一个字典。

def show_kwargs(**kwargs):
    print(type(kwargs))

show_kwargs(name='Alice') # 输出 <class 'dict'>

参数完整顺序

Python 函数参数推荐按这个顺序写:

def demo(pos1, pos2, default='x', *args, keyword_only=True, **kwargs):
    print(pos1)
    print(pos2)
    print(default)
    print(args)
    print(keyword_only)
    print(kwargs)

调用示例:

demo(1, 2, 'value', 3, 4, keyword_only=False, name='Alice')

可以理解为:

pos1, pos2        接收前两个位置参数
default           有默认值,也可以被位置参数覆盖
*args             收集剩下的位置参数
keyword_only      *args 后面的参数,只能用关键字传
**kwargs          收集剩下的关键字参数

强制关键字参数:*

* 后面的参数必须写参数名。

def list_users(page, *, page_size=10, keyword=''):
    return {
        'page': page,
        'page_size': page_size,
        'keyword': keyword
    }

print(list_users(1, page_size=20)) # 正确

# list_users(1, 20) # 错误,page_size 必须写参数名

这个写法很适合配置参数,避免调用时看不懂。

def request(url, *, method='GET', timeout=10, headers=None):
    return {
        'url': url,
        'method': method,
        'timeout': timeout,
        'headers': headers or {}
    }

request('/api/users', method='POST', timeout=30)

强制位置参数:/

/ 前面的参数只能按位置传,不能写参数名。

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

print(add(1, 2)) # 正确

# add(a=1, b=2) # 错误,a 和 b 只能按位置传

这个语法在自己写业务代码时不一定常用,但阅读内置函数签名时会遇到。

解包传参:*list 和 **dict

列表或元组可以用 * 拆成位置参数。

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

numbers = [1, 2]
print(add(*numbers)) # 相当于 add(1, 2)

字典可以用 ** 拆成关键字参数。

def create_user(name, age):
    return {'name': name, 'age': age}

data = {'name': 'Alice', 'age': 18}
user = create_user(**data)

print(user) # 输出 {'name': 'Alice', 'age': 18}

这和 JS 里的展开运算符有点像:

add(...numbers);
createUser({ ...data });

但 Python 的 **data 是把字典的 key 对应到函数形参名。

函数传参和可变对象

Python 函数传参时,不是把对象复制一份传进去,而是让形参这个名字绑定到同一个对象。

def add_user(users):
    users.append('Alice')

names = []
add_user(names)

print(names) # 输出 ['Alice']

这里函数内部没有创建新列表,而是在原列表上执行了 append,所以函数外也能看到变化。

如果只是让形参重新绑定到一个新对象,不会影响外面的变量。

def reset_users(users):
    users = [] # 只是让局部变量 users 指向新列表
    users.append('Bob')

names = ['Alice']
reset_users(names)

print(names) # 输出 ['Alice']

简单记:

  • 修改可变对象本身:外面可能受影响
  • 重新给形参赋值:只改函数内部这个局部名字
  • 不想影响外部数据:传入前复制,或在函数内部先复制

五、多返回值:本质是元组

1. 前端脑内映射

JS 常用数组或对象返回多个值。

Python 可以直接写多个返回值,本质是返回一个元组。

2. Python 写法

def get_position():
    return 10, 20

x, y = get_position()

print(x) # 输出 10
print(y) # 输出 20

本质等价于:

def get_position():
    return (10, 20)

如果返回值含义很明确,可以用多返回值;如果字段很多,推荐返回字典、数据类或 Pydantic 模型。

def parse_user():
    return {
        'name': 'Alice',
        'age': 18,
        'city': 'Shanghai'
    }

六、作用域:LEGB 查找规则

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

L - Local       函数内部
E - Enclosing   外层函数
G - Global      当前模块
B - Built-in    内置作用域

这四层可以理解成 Python 的“变量查找链”:先找当前函数内部,找不到再向外层函数找,再找当前 .py 文件里的全局变量,最后找 printlenrange 这类内置名字。

name = 'global'

def outer():
    name = 'outer'

    def inner():
        name = 'inner'
        print(name)

    inner()

outer() # 输出 inner

局部变量

函数内部定义的变量,默认只在函数内部可用。

def create_name():
    name = 'Alice'
    return name

print(create_name()) # 输出 Alice
# print(name) # 错误,函数外不能访问函数内部变量

global:修改全局变量

如果要在函数里修改全局变量,需要使用 global

count = 0

def add_count():
    global count
    count += 1

add_count()
print(count) # 输出 1

业务代码里不要滥用 global,它会让数据来源变得不清楚。

注意:读取全局变量不需要 global,只有在函数里要给它重新赋值时才需要。

count = 10

def show_count():
    print(count) # 只是读取,不需要 global

show_count()

nonlocal:修改外层函数变量

闭包里如果要修改外层函数变量,需要使用 nonlocal

def create_counter():
    count = 0

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

    return counter

counter = create_counter()
print(counter()) # 输出 1
print(counter()) # 输出 2

同样,读取外层变量不需要 nonlocal,只有要重新赋值时才需要。

七、函数也是对象

1. 前端脑内映射

Python 和 JS 一样,函数可以赋值、传参、返回。

2. 赋值给变量

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

handler = add
print(handler(1, 2)) # 输出 3

3. 作为参数传入

def double(number):
    return number * 2

def apply(value, handler):
    return handler(value)

print(apply(10, double)) # 输出 20

4. 作为返回值返回

def create_multiplier(rate):
    def multiplier(value):
        return value * rate

    return multiplier

double = create_multiplier(2)
print(double(10)) # 输出 20

这就是高阶函数的基础。

八、lambda:小型匿名函数

1. 前端脑内映射

lambda 像 JS 箭头函数。

2. 一句话记住

lambda = 只能写一个表达式的小函数。

表达式会产生一个值,所以 lambda 的结果会自动作为返回值。

3. Python 写法

add = lambda a, b: a + b
print(add(1, 2)) # 输出 3

更常见的场景是传给 sortedmapfilter

users = [
    {'name': 'Alice', 'age': 18},
    {'name': 'Bob', 'age': 20},
    {'name': 'Tom', 'age': 16}
]

result = sorted(users, key=lambda user: user['age'])
print(result)

4. 容易踩的坑

复杂逻辑不要硬写 lambda,直接用 def

def is_adult(user):
    return user['age'] >= 18

adults = list(filter(is_adult, users))
print(adults)

lambda 里不能写 if 代码块、for 代码块、while 代码块。需要条件时,只能写条件表达式。

get_text = lambda age: '成年' if age >= 18 else '未成年'
print(get_text(20)) # 输出 成年

九、常用内置高阶函数

高阶函数就是:函数可以接收另一个函数,或者返回另一个函数。

def double(number):
    return number * 2

def apply(value, handler):
    return handler(value)

print(apply(10, double)) # 输出 20

sortedmapfilterreduce 都属于常见的高阶函数使用场景。

sorted:排序

users = [
    {'name': 'Alice', 'age': 18},
    {'name': 'Bob', 'age': 20},
    {'name': 'Tom', 'age': 16}
]

result = sorted(users, key=lambda user: user['age'])
print(result)

map:映射

map 会对可迭代对象里的每个元素执行同一个处理函数。

它的核心作用是:把一个处理函数应用到每个元素上,得到一组处理后的结果。

numbers = [1, 2, 3]
result = list(map(lambda number: number * 2, numbers))

print(result) # 输出 [2, 4, 6]

map 本身返回的是迭代器,不是列表。如果想得到列表,需要手动用 list() 转换:

numbers = [10, 20, 30]
result = map(lambda number: number * 2, numbers)

print(result) # map 对象
print(list(result)) # 输出 [20, 40, 60]
print(list(result)) # 输出 [],迭代器已经被消耗完

不过日常写简单映射逻辑时,很多时候会更推荐列表推导式,语义更直接:

result = [number * 2 for number in numbers]
print(result)

filter:过滤

filter 会保留判断结果为真的元素。

numbers = [1, 2, 3, 4]
result = list(filter(lambda number: number % 2 == 0, numbers))

print(result) # 输出 [2, 4]

它和 map 一样,返回的也是迭代器。

也可以用列表推导式:

result = [number for number in numbers if number % 2 == 0]
print(result)

如果 filter 的第一个参数传 None,会自动过滤掉假值。

values = [0, 1, '', 'hello', None, [], [1]]
result = list(filter(None, values))

print(result) # 输出 [1, 'hello', [1]]

reduce:合并

reduce 会把一组数据不断合并,最后得到一个结果。

它的常用格式是:

reduce(处理函数, 可迭代对象, 初始值)

第三个参数就是初始值,类似 JavaScript 里的 array.reduce(callback, initialValue)

from functools import reduce

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

def add(total, number):
    return total + number

result = reduce(add, numbers)

print(result) # 输出 15

如果不传初始值,reduce 不会默认从 0 开始,而是把第一个元素当成初始的 total

所以 reduce(add, numbers) 的执行过程可以理解成:

第一次:1 + 2 = 3
第二次:3 + 3 = 6
第三次:6 + 4 = 10
第四次:10 + 5 = 15

如果希望它从指定值开始合并,就把初始值作为第三个参数传进去。

from functools import reduce

numbers = [1, 2, 3]
result = reduce(lambda total, number: total + number, numbers, 100)

print(result) # 输出 106

这时执行过程是:

第一次:100 + 1 = 101
第二次:101 + 2 = 103
第三次:103 + 3 = 106

日常代码里,求和优先用 sum(numbers),不要为了炫技硬写 reducereduce 更适合“把多个对象合并成一个对象”这类场景。

all / any:判断一组条件

scores = [90, 80, 70]

print(all(score >= 60 for score in scores)) # 是否全部及格
print(any(score >= 90 for score in scores)) # 是否有人大于等于 90

十、闭包:内部函数记住外部变量

1. 前端脑内映射

Python 闭包和 JS 闭包本质一样。

2. 一句话记住

闭包 = 函数 + 它记住的外层变量。

3. Python 写法

def create_prefixer(prefix):
    def format_text(text):
        return f'{prefix}-{text}'

    return format_text

api_prefixer = create_prefixer('/api')
print(api_prefixer('users')) # 输出 /api-users

外层函数已经执行完了,但 api_prefixer 仍然能使用当时的 prefix,这就是闭包最核心的价值:把一部分状态保存到函数里。

4. 修改外层变量要用 nonlocal

def create_counter():
    count = 0

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

    return counter

5. 闭包单元:Python 把外层变量放进函数对象里

可以通过 __closure__ 观察闭包保存了什么。

def outer():
    num = 10

    def inner():
        return num + 1

    return inner

fn = outer()

print(fn.__closure__)
print(fn()) # 输出 11

__closure__ 里保存的是闭包单元,可以理解为“inner 记住 outer 变量的地方”。日常业务里不需要手动操作它,知道这个机制有助于理解为什么外层函数结束后,内层函数还能访问外层变量。

每调用一次外层函数,都会创建一份新的闭包状态。

def create_counter():
    count = 0

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

    return counter

counter1 = create_counter()
counter2 = create_counter()

print(counter1()) # 输出 1
print(counter1()) # 输出 2
print(counter2()) # 输出 1

6. 什么时候用

  • 保存配置
  • 创建计数器
  • 生成定制函数
  • 装饰器
  • 延迟执行逻辑

十一、装饰器:函数外面套函数

1. 前端脑内映射

装饰器本质上就是高阶函数:接收一个函数,返回一个新的函数。

如果从前端角度理解,它很像这样:

function logger(fn) {
  return function (...args) {
    console.log('before')
    const result = fn(...args)
    console.log('after')
    return result
  }
}

function sayHello() {
  console.log('hello')
}

const newSayHello = logger(sayHello)
newSayHello()

也就是:

原函数
  |
  +-- 传给装饰器函数
        |
        +-- 装饰器内部创建 wrapper
              |
              +-- wrapper 里可以在原函数前后加逻辑
                    |
                    +-- 最后返回 wrapper

2. 一句话记住

装饰器 = 不改原函数代码,但给它加能力。

比如原函数只负责说 hello

def say_hello():
    print('hello')

现在想给它加日志:

调用前:打印 before
调用原函数:打印 hello
调用后:打印 after

如果直接改 say_hello,当然也能实现:

def say_hello():
    print('before')
    print('hello')
    print('after')

但问题是:如果有很多函数都要加同样的日志,就不能每个函数都手动改一遍。

装饰器解决的就是这个问题:把“额外能力”抽出去,套在原函数外面。

3. 先不用 @,手动理解装饰器

先写一个普通函数 logger

def logger(func):
    def wrapper():
        print('before')

        result = func()

        print('after')
        return result

    return wrapper

这段代码有三个关键点:

  • logger(func) 接收一个函数
  • wrapper() 是新函数,真正执行时会先打印 before,再调用原函数,最后打印 after
  • return wrapper 是把新函数返回出去

然后手动把 say_hello 包起来:

def say_hello():
    print('hello')

say_hello = logger(say_hello)

say_hello()

执行结果:

before
hello
after

这一句是理解装饰器的核心:

say_hello = logger(say_hello)

它的意思是:

右边的 say_hello:原来的函数
logger(say_hello):把原函数传进去,返回 wrapper
左边的 say_hello:重新指向 wrapper

所以后面再调用 say_hello(),其实已经不是直接调用原函数了,而是在调用 wrapper()

4. @logger 只是语法糖

手动写:

say_hello = logger(say_hello)

可以简写成:

@logger
def say_hello():
    print('hello')

完整写法:

def logger(func):
    def wrapper():
        print('before')

        result = func()

        print('after')
        return result

    return wrapper

@logger
def say_hello():
    print('hello')

say_hello()

5. 为什么 wrapper 里还能拿到 func

因为 wrapper 是定义在 logger 里面的内部函数,它会形成闭包。

def logger(func):
    def wrapper():
        return func()

    return wrapper

即使 logger 执行结束了,wrapper 仍然记得当时传进来的 func。所以装饰器本质上也依赖闭包。

6. 装饰带参数的函数

def logger(func):
    def wrapper(*args, **kwargs):
        print(f'call {func.__name__}')
        return func(*args, **kwargs)

    return wrapper

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

print(add(1, 2)) # 输出 3

这里的 *args**kwargs 是为了让装饰器能兼容各种参数形式:

原函数传什么参数
  -> wrapper 就接收什么参数
  -> wrapper 再原样传给 func

如果不写 *args, **kwargs,装饰器只能包装没有参数的函数。

7. 保留原函数信息

实际写装饰器时推荐使用 functools.wraps

from functools import wraps

def logger(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f'call {func.__name__}')
        return func(*args, **kwargs)

    return wrapper

如果不用 wraps,被装饰后的函数名、文档字符串等元信息可能会丢。

8. 带参数的装饰器

普通装饰器是:

@logger
def fn():
    pass

如果装饰器本身也需要参数,就要再多套一层函数:

@retry(3)
def request_data():
    pass

它大概等价于:

request_data = retry(3)(request_data)

所以它会多一层结构:

retry(3)
  -> 返回 decorator
        -> decorator(func)
              -> 返回 wrapper
from functools import wraps

def retry(times):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_error = None

            for _ in range(times):
                try:
                    return func(*args, **kwargs)
                except Exception as error:
                    last_error = error

            raise last_error

        return wrapper

    return decorator

@retry(3)
def request_data():
    print('request')
    return 'ok'

9. 常见使用场景

  • Web 框架路由注册
  • 登录校验
  • 权限校验
  • 日志记录
  • 统计耗时
  • 缓存结果
  • 重试逻辑

十二、生成器函数:yield 一次产出一个值

1. 前端脑内映射

生成器像 JS 的 function*

2. 一句话记住

普通函数 return 一次性返回结果;生成器 yield 可以分批产出结果。

3. Python 写法

def create_numbers():
    yield 1
    yield 2
    yield 3

numbers = create_numbers()

print(next(numbers)) # 输出 1
print(next(numbers)) # 输出 2
print(next(numbers)) # 输出 3

更常见的是用 for 遍历。

def range_numbers(max_number):
    current = 0

    while current < max_number:
        yield current
        current += 1

for number in range_numbers(3):
    print(number)

4. 什么时候用

  • 处理大文件
  • 分批读取数据
  • 惰性计算
  • 流式处理
  • 自定义迭代器

5. yield from

yield from 可以把另一个可迭代对象里的值继续交出去。

def create_all():
    yield from [1, 2, 3]
    yield from ['a', 'b']

for item in create_all():
    print(item)

输出结果:

1
2
3
a
b

这里不是先输出 [1, 2, 3],再输出 ['a', 'b']

yield from [1, 2, 3] 的意思是:把这个列表里的元素一个一个产出。

所以执行过程可以理解成:

yield from [1, 2, 3]
  -> yield 1
  -> yield 2
  -> yield 3

yield from ['a', 'b']
  -> yield 'a'
  -> yield 'b'

它适合用来把多个可迭代对象“摊平”成一个连续的生成器。

十三、async 函数:异步逻辑

1. 前端脑内映射

Python 的 async def 类似 JS 的 async function

2. 一句话记住

async def 定义异步函数,里面通常配合 await 等待异步结果。

3. Python 写法

import asyncio

async def fetch_user():
    await asyncio.sleep(1)
    return {'name': 'Alice'}

async def main():
    user = await fetch_user()
    print(user)

asyncio.run(main())

4. 和 JS 的差异

JS:

async function main() {
  const user = await fetchUser();
  console.log(user);
}

Python:

async def main():
    user = await fetch_user()
    print(user)

5. 容易踩的坑

调用异步函数不会立刻拿到结果,而是得到一个 coroutine。

async def get_data():
    return 1

data = get_data()
print(data) # 不是 1,而是 coroutine 对象

需要 await

async def main():
    data = await get_data()
    print(data)

十四、文档字符串:函数自己的说明书

1. 前端脑内映射

类似 JSDoc,但 Python 约定把说明写在函数体第一行字符串里。

2. Python 写法

def calculate_total(price, count):
    """计算商品总价。"""
    return price * count

多行写法:

def create_user(name, age):
    """
    创建用户字典。

    name: 用户名
    age: 年龄
    """
    return {'name': name, 'age': age}

可以通过 __doc__ 访问。

print(create_user.__doc__)

十五、函数对象的常见属性

函数本身是对象,所以也有属性。

def say_hello(name):
    """返回问候语。"""
    return f'Hello {name}'

print(say_hello.__name__) # 输出 say_hello
print(say_hello.__doc__) # 输出文档字符串

装饰器、框架、调试工具经常会读取这些信息。

十六、常见坑点汇总

1. 不写 return 返回 None

def add(a, b):
    a + b

print(add(1, 2)) # 输出 None

正确写法:

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

2. 可变默认参数会共享

def append_item(item, items=[]):
    items.append(item)
    return items

推荐:

def append_item(item, items=None):
    if items is None:
        items = []

    items.append(item)
    return items

3. 修改外层变量忘记 nonlocal

def create_counter():
    count = 0

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

    return counter

4. async 函数忘记 await

async def get_user():
    return {'name': 'Alice'}

# user = get_user() # 这里只是 coroutine

5. lambda 写复杂逻辑

复杂逻辑直接写 def,不要为了短而牺牲可读性。

6. 函数名覆盖内置函数

不要这样写:

list = [1, 2, 3]
dict = {'name': 'Alice'}
str = 'hello'

这些名字会覆盖 Python 内置函数,后面再调用 list()dict()str() 就容易出问题。