0.4 函数与作用域
概述
你已经熟悉 JS 的 function、箭头函数、默认参数、rest 参数、闭包、高阶函数(HOC)。Python 的函数机制高度相似,但参数系统更强大:支持位置参数、关键字参数、默认参数、*args / **kwargs。装饰器是 Python 里比 JS 高阶函数更常见的用法。这一章逐一对照。
JS/TS ↔ Python 对照表
核心概念
1. def vs function / 箭头函数
function add(a, b) {
return a + b;
}
const double = (x) => x * 2;
def add(a, b):
return a + b
double = lambda x: x * 2 # lambda 只用于极简场景
def定义命名函数,lambda定义匿名函数(只能是一个表达式,不能有语句)。类比:
def≈function,lambda≈ 箭头函数,但lambda能力更弱(不能写多行、不能有赋值)。
2. 位置参数、关键字参数、默认参数
Python 调用函数时可以按参数名传参(JS 只能用对象解构模拟):
// JS:用对象模拟关键字参数
function greet({ name, greeting = "Hello" }) {
return `${greeting}, ${name}`;
}
greet({ name: "Alice" });
# Python:原生支持关键字参数
def greet(name, greeting="Hello"):
return f"{greeting}, {name}"
greet("Alice") # 位置参数
greet(name="Alice") # 关键字参数
greet(greeting="Hi", name="Alice") # 关键字可乱序
调用时
f(a=1)就叫关键字参数,这是 JS 没有的、非常实用的特性。定义时
def f(a=1)的a=1是默认参数。
3. *args / **kwargs vs rest 参数
// JS:rest 收集所有参数
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
# Python:*args 收集位置参数
def sum_all(*nums):
return sum(nums)
sum_all(1, 2, 3) # 6
# **kwargs 收集关键字参数(JS 没有直接对应)
def print_info(**info):
for k, v in info.items():
print(k, v)
print_info(name="Alice", age=30)
*args把多余位置参数打包成 tuple,类似 JS 的...args(打包成数组)。**kwargs把关键字参数打包成 dict,是 JS 没有的。
4. lambda vs 箭头函数
const nums = [1, 2, 3];
const squares = nums.map(x => x * x);
nums = [1, 2, 3]
squares = list(map(lambda x: x * x, nums))
# 但更 Pythonic 的是推导式:
squares = [x * x for x in nums]
lambda只能写单个表达式,不能包含if/for语句(lambda里可用条件表达式)。实际项目中,能用推导式就用推导式,
lambda主要作为sorted、map、filter的简短回调。
5. 作用域 LEGB vs 词法作用域
JS 有词法作用域(函数作用域 + 块级作用域)。Python 用 LEGB 规则查找变量名:
Local:当前函数内部
Enclosing:外层函数(闭包)
Global:模块全局
Built-in:内置名字(如
len、print)
// JS
const x = "global";
function outer() {
const x = "enclosing";
return function inner() {
return x; // "enclosing"
};
}
# Python
x = "global"
def outer():
x = "enclosing"
def inner():
return x # "enclosing"
return inner
关键差异:在函数内给变量赋值,Python 默认会创建局部变量,而不是修改外部变量:
let count = 0;
function increment() {
count++; // 修改外部变量,OK
}
count = 0
def increment():
global count # 必须声明,否则报错
count += 1
def outer():
n = 0
def inner():
nonlocal n # 修改外层函数变量需 nonlocal
n += 1
读外部变量不需要声明,但赋值/修改外部变量需要
global(模块级)或nonlocal(闭包外层)。
6. 闭包
闭包机制和 JS 几乎一致:函数内定义函数,内层函数捕获外层变量。
function counter() {
let count = 0;
return () => ++count;
}
def counter():
count = 0
def inc():
nonlocal count # 修改捕获的变量需要 nonlocal
count += 1
return count
return inc
7. 装饰器 vs 高阶函数 / HOC
你已经熟悉 React 的 HOC:一个函数接收组件、返回增强后的组件。Python 的装饰器本质相同——接收函数、返回新函数,但有语法糖 @:
// JS:高阶函数手动包装
function withLogging(fn) {
return function (...args) {
console.log("调用", fn.name);
return fn(...args);
};
}
const wrapped = withLogging(add);
# Python:装饰器 + @ 语法糖
def with_logging(fn):
def wrapper(*args, **kwargs):
print("调用", fn.__name__)
return fn(*args, **kwargs)
return wrapper
@with_logging # 相当于 add = with_logging(add)
def add(a, b):
return a + b
@decorator就等价于fn = decorator(fn),只是写在定义处更清晰。类比 React 的
@connect(...)、@withRouter(...)(TS 里也有类似装饰器语法)。
8. 纯函数与可变默认参数陷阱
Python 的默认参数只在函数定义时求值一次,可变默认参数会被共享,这是经典陷阱:
// JS:每次调用都会新建数组
function append(item, arr = []) {
arr.push(item);
return arr;
}
append(1); // [1]
append(2); // [2] ← 每次都是新数组
# Python:默认参数只求值一次,共享同一个 list!
def append_item(item, arr=[]):
arr.append(item)
return arr
append_item(1) # [1]
append_item(2) # [1, 2] ← 出错!被共享了
正确写法:用 None 作哨兵值:
def append_item(item, arr=None):
if arr is None:
arr = []
arr.append(item)
return arr
代码示例
带默认值、可变参数、关键字参数的完整例子
// JS
function createUser(name, { age = 18, ...rest } = {}) {
return { name, age, ...rest };
}
createUser("Alice", { age: 30, city: "北京" });
# Python
def create_user(name, age=18, **extra):
return {"name": name, "age": age, **extra}
create_user("Alice", age=30, city="北京")
排序回调对比
// JS
const sorted = users.sort((a, b) => a.age - b.age);
# Python
sorted_users = sorted(users, key=lambda u: u["age"])
# 或更清晰:用 operator
from operator import itemgetter
sorted_users = sorted(users, key=itemgetter("age"))
最佳实践
参数多的函数优先用关键字参数调用,提升可读性:
f(user=u, force=True)。可变默认参数用
None哨兵模式,绝不用[]/{}作默认值。短回调用
lambda,稍复杂就写具名def,别硬塞进lambda。需要"增强函数"时用装饰器,跨切面逻辑(日志、计时、缓存、鉴权)尤其合适。
修改外部变量时明确写
global/nonlocal,不要依赖隐式行为。返回多个值直接
return a, b(自动打包 tuple),调用方a, b = f()解包。
前端开发者常见陷阱
忘写
return:Python 函数没有 return 时返回None,和 JS 的undefined类似但名字不同,容易在调试时困惑。缩进定义函数体:
def后面冒号,函数体缩进,忘了缩进直接IndentationError。lambda能力有限:不能写多行逻辑,别像箭头函数那样随便塞大段代码。可变默认参数被共享:这是 Python 转过来最常见的坑,务必用
None哨兵。函数内赋值不修改外部变量:以为闭包能像 JS 那样
count++,实际需要nonlocal/global。把
**和*用混:*args打包位置参数(tuple),**kwargs打包关键字参数(dict)。装饰器执行时机:
@decorator在定义时执行一次,不是每次调用执行,别在里面放"每次调用才想做的事"。没有函数提升:JS 有函数提升,Python 必须先定义后调用,顺序写反会
NameError。默认参数在定义时求值:如果默认值依赖运行时状态,会得到"过期"的值。
总结
def定义函数,lambda是弱化版箭头函数(单表达式)。Python 参数系统更强:关键字参数、
*args、**kwargs都是 JS 没有的利器。作用域遵循 LEGB 规则,修改外部变量需
global/nonlocal(与 JS 隐式捕获不同)。装饰器 = 高阶函数 +
@语法糖,等价于fn = decorator(fn)。可变默认参数会被共享,永远用
None哨兵模式替代[]/{}默认值。
0.4-函数与作用域
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法