前言
函数是代码的最小复用单位,也是理解装饰器、类方法、异步编程的基础。
Python 的函数整体和 JS 非常相似——都是一等公民,都可以作为参数传递,都支持匿名函数。但有几个 Python 特有的特性,比如 *args、**kwargs、关键字参数,是 JS 里没有直接对应物的,理解它们是迈向 Python 进阶的关键一步。
这篇文章覆盖:
- 函数定义与调用(对比 JS function 和箭头函数)
一、函数定义与调用
1.1 基本语法
# Python 函数定义def greet(name): return f"你好,{name}!"result = greet("Alice")print(result) # 你好,Alice!
对比 JS:
// JS 函数声明function greet(name) { return `你好,${name}!`}// JS 箭头函数const greet = (name) => `你好,${name}!`
关键差异:
- Python 用 def 关键字,JS 用 function
- Python 的 f-string f"..." 对应 JS 的模板字符串 `...`
- Python 没有类似 JS 箭头函数的单行简写(但有 Lambda,后面讲)
1.2 多返回值
Python 函数可以直接返回多个值,这是 JS 没有的原生特性:
def get_min_max(numbers): return min(numbers), max(numbers)low, high = get_min_max([3, 1, 4, 1, 5, 9, 2, 6])print(low, high) # 1 9
本质上是返回一个元组,然后自动解包。JS 要实现类似效果需要返回数组或对象:
// JS 模拟多返回值const getMinMax = (arr) => ({ min: Math.min(...arr), max: Math.max(...arr) })const { min, max } = getMinMax([3, 1, 4, 1, 5, 9])
二、默认参数
def greet(name, greeting="你好"): return f"{greeting},{name}!"print(greet("Alice")) # 你好,Alice!print(greet("Bob", "早上好")) # 早上好,Bob!
和 JS 完全一致:
const greet = (name, greeting = "你好") => `${greeting},${name}!`
⚠️ 经典踩坑:默认参数不要用可变对象
# ❌ 错误写法:默认参数是列表(可变对象),会被所有调用共享def add_item(item, lst=[]): lst.append(item) return lstprint(add_item("a")) # ['a']print(add_item("b")) # ['a', 'b'] ← 不是 ['b']!# ✅ 正确写法:用 None 作为默认值def add_item(item, lst=None): if lst is None: lst = [] lst.append(item) return lst
这个坑 JS 不会踩,因为 JS 每次调用都会重新求值默认参数。
三、关键字参数
Python 调用函数时,可以用"参数名=值"的方式传参,这叫关键字参数:
def create_user(name, age, city="北京"): return f"{name},{age}岁,来自{city}"# 位置参数(按顺序)print(create_user("Alice", 28))# 关键字参数(指定参数名,顺序可以随意)print(create_user(age=28, name="Alice"))print(create_user("Bob", city="上海", age=30))
关键字参数让函数调用更清晰,尤其是参数多时:
# 不用猜每个参数的含义send_email( to="alice@example.com", subject="通知", body="你好,...", cc="boss@example.com", is_html=True)
JS 通常用对象参数解构来达到类似效果:
sendEmail({ to: "alice@example.com", subject: "通知", body: "你好..." })
四、*args:可变位置参数
当你不确定会传入多少个参数时,使用 *args:
def sum_all(*args): print(args) # 这是一个元组 return sum(args)print(sum_all(1, 2, 3)) # 6print(sum_all(1, 2, 3, 4, 5)) # 15
对比 JS 的 rest 参数:
const sumAll = (...args) => { console.log(args) // 这是一个数组 return args.reduce((a, b) => a + b, 0)}
语法几乎相同,区别是:
- Python 里 *args 是元组(tuple,不可变)
在函数调用时,* 也可以用来解包:
numbers = [1, 2, 3, 4, 5]# 解包列表作为位置参数传入print(sum_all(*numbers)) # 15# 对比 JS 的 spreadMath.max(...numbers)
五、**kwargs:可变关键字参数
**kwargs 接收任意数量的关键字参数,打包成字典:
def print_info(**kwargs): print(kwargs) # 这是一个字典 for key, value in kwargs.items(): print(f"{key}: {value}")print_info(name="Alice", age=28, city="北京")# {'name': 'Alice', 'age': 28, 'city': '北京'}# name: Alice# age: 28# city: 北京
完整参数顺序:
# 参数顺序规则:普通参数 → *args → 关键字参数 → **kwargsdef complex_func(a, b, *args, keyword_only, **kwargs): print(f"a={a}, b={b}") print(f"args={args}") print(f"keyword_only={keyword_only}") print(f"kwargs={kwargs}")complex_func(1, 2, 3, 4, keyword_only="必须用关键字", extra="额外参数")
实战场景:装饰器中几乎必用 *args, **kwargs:
def log_decorator(func): def wrapper(*args, **kwargs): # 接收任意参数 print(f"调用函数:{func.__name__}") result = func(*args, **kwargs) # 原样转发给原函数 return result return wrapper
六、Lambda 表达式
Lambda 是 Python 的匿名函数,语法是:lambda 参数: 表达式
# 普通函数def square(x): return x ** 2# 等价的 lambdasquare = lambda x: x ** 2print(square(5)) # 25
对比 JS 箭头函数:
const square = (x) => x ** 2
Lambda 的限制:只能写一个表达式,不能有多行逻辑。复杂逻辑还是要用 def。
Lambda 最常用的场景是作为 sort、map、filter 的参数:
students = [ {"name": "Alice", "score": 95}, {"name": "Bob", "score": 87}, {"name": "Charlie", "score": 92}]# 按 score 从高到低排序sorted_students = sorted(students, key=lambda s: s["score"], reverse=True)# [{'name': 'Alice', 'score': 95}, {'name': 'Charlie', 'score': 92}, ...]
对比 JS:
students.sort((a, b) => b.score - a.score)
七、高阶函数:map、filter、sorted
Python 内置了几个经典高阶函数,前端开发者一定眼熟:
7.1 map()
numbers = [1, 2, 3, 4, 5]# 每个数平方squared = list(map(lambda x: x ** 2, numbers))print(squared) # [1, 4, 9, 16, 25]
对比 JS:
const squared = numbers.map(x => x ** 2)
💡 Python 的 map() 返回的是迭代器,需要用 list() 转换。不过大多数情况下,Python 推荐用列表推导式代替 map(),更 Pythonic:
squared = [x ** 2 for x in numbers]
7.2 filter()
numbers = [1, 2, 3, 4, 5, 6, 7, 8]# 过滤出偶数evens = list(filter(lambda x: x % 2 == 0, numbers))print(evens) # [2, 4, 6, 8]# 推荐写法(列表推导式)evens = [x for x in numbers if x % 2 == 0]
7.3 函数作为参数传递
def apply_twice(func, value): return func(func(value))def double(x): return x * 2print(apply_twice(double, 3)) # 12(3 → 6 → 12)
八、类型提示(Type Hints)
Python 3.5+ 支持为函数参数和返回值添加类型提示,提升代码可读性,也方便 IDE 做智能提示:
def add(a: int, b: int) -> int: return a + bdef greet(name: str, times: int = 1) -> str: return f"你好,{name}!" * timesdef process(items: list[str]) -> dict[str, int]: return {item: len(item) for item in items}
类型提示在运行时不做强制检查(Python 是动态语言),但 VS Code 等编辑器会根据类型提示提供自动补全和错误提醒,写大型项目时强烈建议加上。
小结
| | |
|---|
| def func(x): | function func(x) {} |
| def f(x, y=0): | const f = (x, y = 0) => |
| *args | ...args |
| **kwargs | |
| lambda x: x*2 | x => x*2 |
| return a, b | |
记住这几条:
- 默认参数不要用可变对象(列表/字典),用 None 代替
- *args 收到的是元组,**kwargs 收到的是字典
- Lambda 只适合简单表达式,复杂逻辑还是用 def
- 大多数情况下,列表推导式比 map()/filter() 更 Pythonic
下篇预告
第 07 篇:Python 文件操作 + JSON 处理:前后端数据交换全攻略
下一篇我们进入实战场景:用 Python 读写文件、处理 JSON 数据。前端每天和 JSON 打交道,后端操作文件是基本功,这两个技能合并一篇讲清楚。