Python函数系统与JavaScript存在对应关系:def等同function,return返回None类似undefined,*args和**kwargs对应剩余参数与对象参数。参数系统支持位置、关键字、默认参数及强制规则。闭包、装饰器、生成器、异步函数等特性与JS本质相通。函数作为对象可回调、lambda只能写单表达式,生成器实现惰性数据产出。
Python函数全面总结
时间:2026-06-07
浏览:322
文章目录
这篇文章的目标读者很明确——你已经有了前端开发的基础,现在正在转向学习 Python。如果之前写过 JavaScript 或 TypeScript,那么恭喜你,你已经拿到了理解 Python 函数系统的“钥匙”。这篇不是那种干巴巴的语法指南,而是要把 Python 函数当成一个完整的系统来拆解。

先别急着写代码,不妨先把下面这张“前端 → Python 映射表”过一遍,建立起最直观的直觉:
def:就是 JS 的functionreturn:就是 JS 的returnNone:相当于 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 函数
|
工程写法
|
+-- 文档字符串 / 命名 / 坑点
一、先建立总地图
| 知识点 | 前端脑内映射 | 一句话记住 |
|---|---|---|
def | function | 定义一个函数 |
return | return | 把结果交回调用方 |
不写 return | 返回 undefined | Python 返回 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 用缩进定义函数体
Python 的 def 对应 JS 的 function。最核心的区别在于:JS 用花括号 {} 包裹函数体,而 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 # 先占位,避免语法报错
对比一下 JS 和 Python 的写法,感受差异:
function formatUser(name, age) {
const text = `${name} is ${age} years old`;
return text;
}
def format_user(name, age):
text = f'{name} is {age} years old'
return text
需要特别注意的坑是,缩进本身就是语法,不是单纯为了好看。下面这段代码中,print('hello') 属于函数内部,而 print('done') 已经跑到了函数外部:
def say_hello():
print('hello') # 在函数里面print('done') # 不在函数里面
三、return:返回值和提前结束
Python 的 return 和 JS 的 return 基本一致,都是用来返回结果并立即结束函数。先看一个基础例子:
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 5print(get_discount(120)) # 输出 20
一个值得牢记的差异:JS 函数不写 return 时返回 undefined;Python 函数不写 return 时则返回 None。
def log_user(name):
print(f'user: {name}') # 只打印,不返回result = log_user('Alice')
print(result) # 输出 None
在处理简单的二选一逻辑时,Python 有一种类似 JS 三元表达式的写法,但顺序不同。它叫“条件表达式”:
age = 18
text = '成年' if age >= 18 else '未成年'print(text)
它非常适合非常简单的 if-else 场景:
def get_level(score):
return 'pass' if score >= 60 else 'fail'
不过一定要注意:如果条件分支复杂,或者每个分支里要做多件事情,老老实实用普通的 if-else 结构,可读性远比一行式重要。
四、参数系统:函数最重要的部分
Python 的参数系统比 JS 更精细,因为它同时支持按顺序传、按名字传、收集参数和强制规则。调用函数时,参数大致可以分为两类:位置参数(如 add(1, 2))和关键字参数(如 create_user(name='Alice', age=18))。
位置参数和关键字参数
位置参数就像 JS 的普通实参,完全靠顺序来匹配;关键字参数则像传入一个对象,靠名字来匹配。看个例子:
def create_user(name, age):
return {'name': name, 'age': age}user1 = create_user('Alice', 18) # 位置参数,按顺序传
user2 = create_user(age=20, name='Bob') # 关键字参数,按名字传
这里有两个容易踩的坑。第一,位置参数必须写在关键字参数前面,比如 create_user('Alice', age=18) 是正确的,但 create_user(name='Alice', 18) 会报错。第二,同一个参数不能传两次,比如 create_user('Alice', name='Bob') 会触发类型错误。
默认参数
默认参数和 JS/TS 的默认参数用法类似,通常放在必填参数后面:
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
}
一个经典陷阱:不要用可变对象(如列表或字典)做默认值。下面这个错误写法中,默认参数 [] 只在函数定义时创建一次,导致多次调用时意外共享了同一个列表:
def add_item(item, items=[]):
items.append(item)
return itemsprint(add_item('A')) # 输出 ['A']
print(add_item('B')) # 输出 ['A', 'B'],不是 ['B']
推荐写法是使用 None 作为默认值,然后在函数内部重新创建:
def add_item(item, items=None):
if items is None:
items = [] items.append(item)
return items
*args:可变位置参数
*args 类似 JS 的剩余参数 ...args,用来收集所有多余的、按位置传入的参数。这些参数会被打包成一个元组:
def sum_all(*args):
total = 0 for number in args:
total += number return totalprint(sum_all(1, 2, 3)) # 输出 6
def show_args(*args):
print(args)
print(type(args))show_args('a', 'b') # 输出 ('a', 'b') 和
**kwargs:可变关键字参数
**kwargs 可以理解为 Python 自动把多余的命名参数收集成一个字典。它的本质就是一个 dict 对象。
def create_user(**kwargs):
print(kwargs)
return kwargsuser = create_user(name='Alice', age=18, city='Shanghai')
参数完整顺序
当一个函数需要混合各种参数时,推荐的书写顺序如下:
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 + bprint(add(1, 2)) # 正确# add(a=1, b=2) # 错误,a 和 b 只能按位置传
解包传参:*list 和 **dict
这个功能与 JS 的展开运算符很像。当你有一个列表时,可以用 * 把它拆成多个位置参数:
def add(a, b):
return a + bnumbers = [1, 2]
print(add(*numbers)) # 相当于 add(1, 2)
字典则可以用 ** 拆成关键字参数,注意字典的 key 必须与函数形参名对应:
def create_user(name, age):
return {'name': name, 'age': age}data = {'name': 'Alice', 'age': 18}
user = create_user(**data)
函数传参和可变对象
Python 函数传参时,并不是把对象复制一份传进去,而是让形参这个名字绑定到同一个对象。理解这一点对避免意外修改至关重要:
def add_user(users):
users.append('Alice')names = []
add_user(names)print(names) # 输出 ['Alice']
但如果你只是让形参重新绑定到一个新对象,则不会影响外面的变量:
def reset_users(users):
users = [] # 只是让局部变量 users 指向新列表
users.append('Bob')names = ['Alice']
reset_users(names)print(names) # 输出 ['Alice']
简单记一下:修改可变对象本身(如 append),外部可能受影响;重新给形参赋值,只改函数内部这个局部名字。如果不希望影响外部数据,可以在传入前复制,或在函数内部先复制一份再操作。
五、多返回值:本质是元组
JS 中要返回多个值通常用数组或对象,而 Python 可以直接写多个返回值,本质上是返回一个元组:
def get_position():
return 10, 20x, y = get_position()
等价于显式返回一个元组:
def get_position():
return (10, 20)
如果返回值数量少且含义明确,用多返回值很简洁。但如果字段很多,还是推荐返回字典、数据类或 Pydantic 模型,语义更清晰。
六、作用域:LEGB 查找规则
Python 查找变量时,遵循一个清晰的顺序:
L - Local 函数内部
E - Enclosing 外层函数
G - Global 当前模块
B - Built-in 内置作用域
你可以把它想象成一个“变量查找链”:先找当前函数内部,找不到就去外层函数找,再找当前 .py 文件里的全局变量,最后才去找 print、len、range 这类内置名字。
name = 'global'def outer():
name = 'outer' def inner():
name = 'inner'
print(name) inner()outer() # 输出 inner
局部变量
函数内部定义的变量,默认只在函数内部可用,外部无法访问:
def create_name():
name = 'Alice'
return nameprint(create_name()) # 输出 Alice
# print(name) # 错误,函数外不能访问函数内部变量
global:修改全局变量
如果要在函数里修改全局变量,必须使用 global 关键字声明。注意,只是读取全局变量时不需要 global,只有要给它重新赋值时才需要:
count = 0def add_count():
global count
count += 1add_count()
print(count) # 输出 1
业务代码里不要滥用 global,它会让数据来源变得不清晰,追查问题时很头疼。
nonlocal:修改外层函数变量
在闭包中,如果要修改外层函数的变量,则需要使用 nonlocal 关键字:
def create_counter():
count = 0 def counter():
nonlocal count
count += 1
return count return countercounter = create_counter()
print(counter()) # 输出 1
print(counter()) # 输出 2
同样,读取外层变量不需要 nonlocal,只有要重新赋值时才需要。
七、函数也是对象
这一点和 JS 一致:Python 的函数也是一等公民,可以赋值、作为参数传入、作为返回值返回。
def add(a, b):
return a + bhandler = add
print(handler(1, 2)) # 输出 3
def double(number):
return number * 2def apply(value, handler):
return handler(value)print(apply(10, double)) # 输出 20
作为返回值返回时,就构成了高阶函数的基础:
def create_multiplier(rate):
def multiplier(value):
return value * rate return multiplierdouble = create_multiplier(2)
print(double(10)) # 输出 20
八、lambda:小型匿名函数
lambda 就像 JS 的箭头函数,但更严格——它只能写一个表达式。这个表达式的结果会自动作为返回值,所以不需要写 return:
add = lambda a, b: a + b
print(add(1, 2)) # 输出 3
它最常用的场合是作为 sorted、map、filter 等函数的参数:
users = [
{'name': 'Alice', 'age': 18},
{'name': 'Bob', 'age': 20},
{'name': 'Tom', 'age': 16}
]result = sorted(users, key=lambda user: user['age'])
需要注意:lambda 里不能写 if、for、while 等语句块。如果需要条件判断,只能用条件表达式:
get_text = lambda age: '成年' if age >= 18 else '未成年'
如果逻辑复杂,建议直接用 def 定义普通函数,可读性远胜于强行写成一行的 lambda。
九、常用内置高阶函数
所谓高阶函数,就是能接收另一个函数作为参数,或者返回另一个函数的函数。Python 自带了好几个非常实用的高阶函数。
sorted:排序
users = [
{'name': 'Alice', 'age': 18},
{'name': 'Bob', 'age': 20},
{'name': 'Tom', 'age': 16}
]result = sorted(users, key=lambda user: user['age'])
map:映射
map 会对可迭代对象里的每个元素执行同一个处理函数。它返回的是一个迭代器,需要用 list() 转换为列表:
numbers = [1, 2, 3]
result = list(map(lambda number: number * 2, numbers))print(result) # 输出 [2, 4, 6]
不过日常写简单映射逻辑时,列表推导式通常更直观、更 Pythonic:
result = [number * 2 for number in numbers]
filter:过滤
filter 会保留判断结果为真的元素,同样返回迭代器:
numbers = [1, 2, 3, 4]
result = list(filter(lambda number: number % 2 == 0, numbers))print(result) # 输出 [2, 4]
同样,列表推导式也适用于此场景:
result = [number for number in numbers if number % 2 == 0]
一个小技巧:如果 filter 的第一个参数传 None,它会自动过滤掉所有假值:
values = [0, 1, '', 'hello', None, [], [1]]
result = list(filter(None, values))print(result) # 输出 [1, 'hello', [1]]
reduce:合并
reduce 用于把一组数据不断合并,最终得到一个结果。它需要从 functools 模块导入:
from functools import reducenumbers = [1, 2, 3, 4, 5]result = reduce(lambda total, number: total + number, numbers)print(result) # 输出 15
如果不传初始值,reduce 会把第一个元素当成初始的 total。如果需要指定初始值,把它作为第三个参数传入:
result = reduce(lambda total, number: total + number, numbers, 100)print(result) # 输出 106
日常编码中,求和用 sum() 就够了,reduce 更适合“把多个对象合并成一个对象”这类更复杂的场景。
all / any:判断一组条件
scores = [90, 80, 70]print(all(score >= 60 for score in scores)) # 是否全部及格
print(any(score >= 90 for score in scores)) # 是否有人大于等于 90
十、闭包:内部函数记住外部变量
Python 闭包和 JS 闭包本质完全一样,就是“函数 + 它记住的外层变量”。当外层函数执行结束后,内层函数仍然能访问当时的环境变量:
def create_prefixer(prefix):
def format_text(text):
return f'{prefix}-{text}' return format_textapi_prefixer = create_prefixer('/api')
print(api_prefixer('users')) # 输出 /api-users
每调用一次外层函数,都会创建一份新的闭包状态,它们彼此独立:
def create_counter():
count = 0 def counter():
nonlocal count
count += 1
return count return countercounter1 = create_counter()
counter2 = create_counter()print(counter1()) # 1
print(counter1()) # 2
print(counter2()) # 1
闭包的应用场景很广:保存配置、创建计数器、生成定制函数、装饰器,以及延迟执行逻辑,都会用到它。
十一、装饰器:函数外面套函数
从本质上讲,装饰器就是一个高阶函数:它接收一个函数,返回一个经过增强的新函数。如果从前端的角度理解,它很像下面这个模式:
原函数
|
+-- 传给装饰器函数
|
+-- 装饰器内部创建 wrapper
|
+-- wrapper 里可以在原函数前后加逻辑
|
+-- 最后返回 wrapper
先不用 @ 语法,手动写一个装饰器来理解它的运作方式:
def logger(func):
def wrapper():
print('before') result = func() print('after')
return result return wrapperdef say_hello():
print('hello')say_hello = logger(say_hello)say_hello()
这一行 say_hello = logger(say_hello) 是理解装饰器的核心:右边的 say_hello 是原函数,logger 把它传进去并返回 wrapper,左边的 say_hello 重新指向了 wrapper。所以后面再调用 say_hello(),其实是在调用 wrapper()。
而 @logger 语法糖所做的,就是把这个过程简化成了:
@logger
def say_hello():
print('hello')
在处理带参数的函数时,wrapper 需要能接收任意参数:
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
实际写装饰器时,强烈建议使用 functools.wraps 来保留原函数的元信息(如函数名、文档字符串等):
from functools import wrapsdef logger(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f'call {func.__name__}')
return func(*args, **kwargs) return wrapper
如果装饰器本身也需要参数,就需要再多套一层函数结构:
from functools import wrapsdef 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'
装饰器的常用场景包括:Web 框架路由注册、登录校验、权限校验、日志记录、统计耗时、缓存结果和重试逻辑等。
十二、生成器函数:yield 一次产出一个值
生成器像 JS 的 function*。普通函数用 return 一次性返回结果,而生成器函数用 yield 分批产出结果:
def create_numbers():
yield 1
yield 2
yield 3numbers = create_numbers()print(next(numbers)) # 1
print(next(numbers)) # 2
更常见的用法是配合 for 循环使用:
def range_numbers(max_number):
current = 0 while current < max_number:
yield current
current += 1for number in range_numbers(3):
print(number)
生成器最适合处理大文件、分批读取数据、惰性计算、流式处理以及自定义迭代器。
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,而不是一次性输出两个列表。
十三、async 函数:异步逻辑
Python 的 async def 类似 JS 的 async function,定义异步函数,里面通常配合 await 等待异步结果:
import asyncioasync def fetch_user():
await asyncio.sleep(1)
return {'name': 'Alice'}async def main():
user = await fetch_user()
print(user)asyncio.run(main())
一个容易踩的坑:调用异步函数并不会立刻拿到结果,而是得到一个 coroutine 对象,必须加上 await 才能拿到真正的值。
十四、文档字符串:函数自己的说明书
类似 JSDoc,但 Python 约定把说明写在函数体的第一行字符串中:
def calculate_total(price, count):
"""计算商品总价。"""
return price * count
多行文档字符串一般写作:
def create_user(name, age):
"""
创建用户字典。 name: 用户名
age: 年龄
"""
return {'name': name, 'age': age}
通过 __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 + bprint(add(1, 2)) # None
正确写法一定要有 return。
2. 可变默认参数会共享
def append_item(item, items=[]):
items.append(item)
return items
推荐改用 None 作为默认值,内部重新初始化。
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'}
这些名字会覆盖 Python 内置函数,导致后续调用 list()、dict() 等内置方法时报错,务必避免。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。