356. Python 篇三:函数、参数、返回值与模块

2026.08.04

·python

面向 JavaScript / TypeScript 开发者:裁剪非必要解释,对比 Python 与 JS/TS 函数与模块的核心差异、特殊语法与踩坑点。

函数定义与类型签名

Python

python
from collections.abc import Callable

# 函数类型签名:Callable[[参数类型列表], 返回类型]
Formatter = Callable[[str, bool], str]

def format_task(title: str, done: bool = False) -> str:
    # 格式化任务文本
    status = "done" if done else "todo"
    return f"[{status}] {title}"

formatter: Formatter = format_task

Python 使用 Callable[[入参类型列表], 返回类型] 声明函数类型

TypeScript

typescript
// 函数类型签名
type Formatter = (title: string, done?: boolean) => string;

function formatTask(title: string, done: boolean = false): string {
    // 格式化任务文本
    const status = done ? "done" : "todo";
    return `[${status}] ${title}`;
}

const formatter: Formatter = formatTask;

TS 使用 (name: Type) => ReturnType 声明函数类型;对比 PythonCallable,TS 可为参数命名并用 ? 标注可选参数。

补充

  • Callable 与 TS 函数类型对比:
    • Python Callable[[str, bool], str] 仅按顺序约束参数类型,无法命名形参和标注可选;
    • TS (title: string, done?: boolean) => string 支持形参命名与 ? 可选标记。
  • 类型注解纯属约束:
    • Python 的类型提示在运行时会被解释器完全忽略,不会做强类型校验。
  • 一等公民:
    • 两者的函数均为一等公民,可作为参数或返回值传递。

参数默认值陷阱

Python

python
# 错误写法:
def add_item(item: str, target: list[str] = []):

# 正确写法:默认值使用 None
def add_item(item: str, target: list[str] | None = None) -> list[str]:
    # 内部动态初始化空列表
    if target is None:
        target = []
    target.append(item)
    return target

默认参数只在函数定义时创建一次。用 []{} 做默认值,多次调用会共享同一个对象。 即引用一个

JavaScript

javascript
// JS 默认参数在每次调用时独立求值

function addItem(item, target = []) {
    // 每次未传参时自动创建新 Array
    target.push(item);
    return target;
}

JS 默认参数在每次函数调用时独立新建一个变量

补充

  • 坑:可变默认值:默认参数只在函数定义时创建一次
    • 如果用 []{} 当默认值,多次调用会修改同一个对象,导致数据污染。
  • 惯用法:
    • Python 统一用 None 作为可变参数默认值,在函数内部判断并初始化。
  • 差异:
    • JS 默认参数在每次函数调用时重新求值,不存在多调用共享可变对象问题。

① 位置参数② 关键字参数约束

Python

python
# / 左侧必须按位置传参
# * 右侧必须按关键字传参

def configure(mode: str, /, *, verbose: bool = False) -> None:
    print(mode, verbose)

# 正确调用
configure("prod", verbose=True)

# configure(mode="prod")   # 报错:/ 左侧不能写参数名
# configure("prod", True)  # 报错:* 右侧必须写参数名

/ 侧参数强制只能按位置传递
* 侧参数强制只能按关键字传递

补充

  • /* 作用:
    • / 强制位置传参(防止库作者改形参名破坏调用);
    • * 强制显式写出参数名(提升调用易读性)。

变长参数与解包

Python

python
# *args 收集多余位置参数 (tuple)
# **kwargs 收集多余关键字参数 (dict)

def create_node(name: str, *tags: str, **meta: str) -> dict:
    return {"name": name, "tags": tags, "meta": meta}

tag_list = ("python", "fastapi")
meta_dict = {"env": "prod"}

# * 解包列表/元组,** 解包字典
res = create_node("app", *tag_list, **meta_dict)
# 输出: {"name": "app", "tags": ("python", "fastapi"), "meta": {"env": "prod"}}

*args 收集多余位置实参为 tuple
**kwargs 收集关键字实参为 dict
调用时 * 解包序列
** 解包字典

补充

  • *args vs ...rest
    • Python *args 收集到的参数是不可变元组 tuple
    • JS ...rest 收集到的是数组 Array
  • **kwargs 特性:
    • Python 原生支持 **kwargs 捕获任意命名参数;
    • JS 无对应语法,需要手动传递配置对象。
  • 解包区别:
    • Python 用 * 解包位置序列
    • ** 解包字典关键字;
    • JS 统一用 ... 处理数组解包与对象展开。

返回值与空值

Python

python
# return 多值本质上是返回 tuple

def parse_coordinate(text: str) -> tuple[int, int] | None:
    if "," not in text:
        # 显式返回 None
        return None
    x, y = text.split(",")
    # 返回多值(元组)
    return int(x), int(y)

# 解包赋值
result = parse_coordinate("10,20")
if result is not None:
    x, y = result

return a, b 本质是返回 tuple 元组;未显式 return 时返回 None,判空须用 is None

补充

  • 多返回值:Python 的 return a, b 语法糖本质是返回一个 tuple;JS 必须用 return [a, b]return {a, b}
  • 隐式空值:Python 函数无显式 return 时返回 None;JS 返回 undefined
  • 判空坑:Python 判空必须使用 is Noneis not None。若使用 if not res:,当返回值为 0""[] 时会被误判为假值。

作用域与变量赋值

Python

python
# 变量只有 def/class/lambda 作用域,没有块级作用域

def scope_demo():
    if True:
        temp = "hello"
    print(temp)  # 输出: "hello"(if 块外部可见)

scope_demo()

# 修改全局变量须声明 global
count = 0

def increment():
    global count
    count += 1

print(count)  # 输出: 0
increment()
print(count)  # 输出: 1

Python 仅有 def/class/lambda 划分作用域(无块级作用域);在函数内部修改全局变量须显式声明 global

JavaScript

JS 的 let/const 具备 {} 块级作用域,内部可直接沿作用域链修改外层变量。

补充

  • 块级作用域:
    • Python 没有 {} 块级作用域。
      • iffor 中定义的变量在外层函数内部依然能被访问;
    • JS 的 let/const 则受 {} 作用域限制。
  • 赋值陷阱:
    • 在 Python 函数内部给变量赋值(如 x = 1),默认创建局部变量并遮蔽外层同名变量。若要修改全局变量,必须显式声明 global
  • 按引用传递:
    • Python 与 JS 传参语义一致,都是传递“对象引用的副本”。
    • 修改可变对象(如 list.append())外部可见,重新给形参赋值(如 items = [])不影响外部。

模块与入口保护

Python

python
# module.py

def main() -> None:
    print("模块主逻辑")

# 入口保护
if __name__ == "__main__":
    # 仅当直接运行 python module.py 时执行
    # 被 import 时忽略
    main()

一个 .py 文件即一个模块;if __name__ == "__main__": 用于区分是直接运行脚本还是被 import 导入。

JavaScript

javascript
// module.js (CommonJS / Node.js)

function main() {
    console.log("模块主逻辑");
}

// 判断是否为主模块直接运行
if (require.main === module) {
    main();
}

export { main };

ES Module 使用 import/export 导出;Node.js 中通常使用 require.main === module 判断直接运行。

补充

  • 模块定义:
    • Python 中每一个 .py 文件就是一个模块,使用 import module_name 导入。
  • 入口保护:
    • Python 依靠 if __name__ == "__main__": 区分“直接运行脚本”与“作为模块导入”,防止导入时误触发测试或运行逻辑。
  • JS 对照:
    • ES Module 规范中无该机制,在 Node.js 环境中常使用 require.main === module 达到相同效果。

补充

  • 参数定义顺序(硬规则,写反就语法错):
    • 位置参数 → / → 带默认值参数 → *args → 仅关键字参数 → **kwargs
    • 例:def f(a, /, b, c=1, *args, d, e=2, **kwargs): ...
    • JS 没有这套分隔符;TS 可选参数也只能“后面可选”,不能用 /* 强制传参方式。
  • lambda vs 箭头函数
    • lambda x: x * 2 只能写单个表达式,不能写语句块、不能多行 return
    • 需要语句、分支、多行逻辑时一律用 def
      • 别把 () => { ... } 的习惯硬搬过来。
  • 嵌套函数改外层变量:用 nonlocal
    • 函数里赋值默认当局部;要改上一层函数的变量,写 nonlocal name
    • 改模块级全局才用 global;JS 闭包直接改外层 let,没有这两个关键字。
  • import 常见写法(相对 JS 的对照):
    • import osimport * as os from "os"(整模块命名空间)
    • from pathlib import Pathimport { Path } from "pathlib"
    • from pathlib import Path as Pimport { Path as P } from "pathlib"
    • 避免 from x import *:污染当前命名空间,且静态分析难跟踪。
  • 包与模块:
    • 单个 .py 是模块;含 __init__.py 的目录是包(现代 Python 也支持无 __init__.py 的命名空间包,日常项目仍常见显式包)。
    • 包内相对导入用 from . import sibling / from ..pkg import name,对应前端 monorepo 里的相对路径,但语法是包语义不是文件路径字符串。
  • 空函数体:
    • Python 不能写空缩进块;
    • JS 可以 function f() {}

参考