Skip to content

01 · Python 语言基础与 TS 对照 ​

速览卡 ​

项内容
一句话定位给有 TS/Dart 基础的人看的 Python 迁移手册——只讲会坑到你的差异,不讲语法大全
核心内容三个心智模型差异、语法对照、10 个直觉陷阱、类型注解、asyncio 与 JS 事件循环的区别、环境管理
为什么写AI 生态与后端服务的主力语言;AI 岗 JD 里 Python 的出现率极高 [易变],是前端转 AI 全栈最该先补的一门
读完能回答我写 Python 时会踩哪些坑?为什么不能照搬 JS 的直觉?
相关板块A03 · TypeScript 进阶与 Dart 对照 | 实操 01 · LLM API 实操入门 | G12 · 上下文工程

语法大全网上有的是,本篇不重复。这里只写"你带着 TS 直觉写 Python 一定会翻车"的地方。


一、三个心智模型差异(先换脑子) ​

#TS / JSPython后果
1块级作用域:{} 划作用域,let/const 活在块里函数作用域:缩进只管层级,不产生作用域if 里定义的变量,外面照样能访问
2async 是运行时内置的默认能力,随手 awaitasyncio 是库,必须显式起事件循环在 async 函数里调一个同步阻塞函数,整个 loop 卡死
3单线程 + 事件循环,没有"全局锁"概念有 GIL:同一进程内只有一个线程能执行 Python 字节码多线程跑 CPU 密集任务不会变快

第 3 条决定了 Python 的并发写法:CPU 密集用多进程,IO 密集用 asyncio。 用"多线程加速计算"这个直觉在 Python 里是错的。


二、语法对照速查 ​

只列形态差别大的,其余"长得差不多"的不写。

场景TypeScriptPython
变量声明const x = 1 / let y = 2x = 1(无关键字,赋值即定义)
常量const X = 1X = 1(靠约定:全大写表示常量,语言不拦你)
空值null / undefinedNone(只有一个)
布尔true / falseTrue / False(首字母大写)
逻辑运算&& || !and or not(英文单词)
字符串模板`hi ${name}`f"hi {name}"
数组/列表[1,2,3][1,2,3](同形,但叫 list)
键值对Map / {} 对象{"a": 1}(叫 dict,键必须是可哈希的)
遍历for (const x of arr)for x in arr:
带索引遍历arr.forEach((v,i)=>{})for i, v in enumerate(arr):
同时遍历两个zip 要自己写或 lodashfor a, b in zip(xs, ys):(内置)
区间Array.from({length:n},(_,i)=>i)range(n)
切片arr.slice(1,3)arr[1:3](步长 arr[::-1] 反转)
推导arr.map(f).filter(g)[f(x) for x in arr if g(x)](列表推导)
解构const {a,b} = obja, b = tup(元组解包)
展开...args*args / **kwargs
类class A { constructor(){} }class A: def __init__(self):
this / selfthis(关键字)self(约定名,位置参数,必须写)
私有private x / #x_x(约定)/ __x(name mangling,都不是真私有)
接口interfaceProtocol / ABC(鸭子类型是默认)
模块导入import x from 'y'import y / from y import x
异常处理try{}catch(e){}finally{}try: / except E as e: / finally:

三、10 个会坑到你的直觉差异 ​

① 空列表是 falsy(与 JS 相反) ​

python
if not items:        # [] 会进这个分支
    ...
# JS 里 [] 是 truthy,Python 里 [] 是 falsy

这是最高频的 bug 来源。JS 的 if (arr) 判断"有没有这个对象", Python 的 if items: 判断"这个容器是不是非空"。要判断"是不是 None"必须写 if items is None:。

Falsy 清单:None、False、0、0.0、''、[]、()、{}、set()。 自定义对象默认 truthy,除非定义 __bool__ 或 __len__。

② 可变默认参数只初始化一次 ​

python
def add(x, target=[]):     # ❌ 这个 [] 在函数定义时创建一次,之后共享
    target.append(x)
    return target

add(1)   # [1]
add(2)   # [1, 2]   ← 不是你想要的 [2]
python
def add(x, target=None):   # ✅ 标准写法
    target = target if target is not None else []
    target.append(x)
    return target

③ is 与 == 不是一回事 ​

== 比较值,is 比较是不是同一个对象。

python
a = [1, 2]; b = [1, 2]
a == b      # True  值相等
a is b      # False 不是同一个对象

x = 256; y = 256
x is y      # True  ← 但这是「小整数缓存」的巧合,不是规则 [易变]

规则:判断 None 用 is,判断值相等用 ==。别用 is 比较数字或字符串。

④ 闭包是"晚绑定" ​

python
funcs = [lambda: i for i in range(3)]
[f() for f in funcs]     # [2, 2, 2]  ← 不是 [0, 1, 2]

因为 i 在调用时才去查,那时循环已结束。修正:

python
funcs = [lambda i=i: i for i in range(3)]    # 用默认参数固化

⑤ 除法:/ 永远返回 float ​

python
5 / 2     # 2.5   (JS 里 5/2 也是 2.5)
5 // 2    # 2     ← 整除
-5 // 2   # -3    ← 向下取整,不是向零取整

⑥ 列表乘法共享引用 ​

python
m = [[0]] * 3
m[0][0] = 1
# [[1], [1], [1]]   ← 三行是同一个 list
m = [[0] for _ in range(3)]    # ✅

⑦ 没有块级作用域 ​

python
if True:
    x = 1
print(x)     # 1  ← 能访问!TS 里这会报错

for i in range(3):
    pass
print(i)     # 2  ← 循环变量也会泄漏出去

⑧ import 会执行模块代码 ​

Python 的 import 是真执行,不像 ES module 那样只是绑定。 所以模块顶层不要放有副作用的代码,要写成:

python
if __name__ == "__main__":
    main()

这也是循环导入在 Python 里容易出问题的原因。

⑨ dict 的键必须可哈希 ​

python
d = {}
d[[1, 2]] = "x"      # ❌ TypeError: unhashable type: 'list'
d[(1, 2)] = "x"      # ✅ 元组可以

判断标准:可变对象不能做键(list / dict / set),不可变的可以(int / str / tuple / frozenset)。

⑩ __init__ 不是构造函数 ​

__init__ 是初始化,对象在它之前已由 __new__ 创建。 日常写 __init__ 就够了,但要知道这个名字的含义——它对应的是"初始化"而非"构造"。


四、类型注解:Python 也在"TS 化" ​

Python 是动态类型,但类型注解已成为工程标配——它是给工具(mypy / IDE)看的, 运行时不校验。

python
from typing import Optional

def greet(name: str, times: int = 1) -> str:
    return name * times

def find(ids: list[int]) -> Optional[str]:    # 等价于 TS 的 string | null
    ...
TSPython(3.10+)
string | nullOptional[str] 或 str | None
Array<T>list[T]
Record<K,V>dict[K, V]
interface User {...}@dataclass class User: 或 TypedDict
type Alias = ...Alias = ...(直接赋值)或 TypeAlias

⚠️ 注解不校验,是它和 TS 最大的区别。 写了 def f(x: int) 然后传字符串,运行时不会报错。必须靠 mypy / pyright 静态检查。

数据类的三种选择(这是 AI/后端场景天天要用的):

方式适用特点
dataclass内部数据结构标准库,自动生成 __init__/__eq__/__repr__
pydantic.BaseModelAPI 边界、配置运行时校验 + 序列化,FastAPI 的基石
NamedTuple轻量元组内存省,行为像元组

判断规则:数据要进出系统边界(HTTP、配置文件、LLM 输出解析)→ 用 pydantic, 因为它会在运行时拦住脏数据;只在内部传递 → dataclass 就够。


五、异步:asyncio 与 JS 事件循环的关键差异 ​

JSPython
事件循环运行时内置,自动跑需要你显式启动:asyncio.run(main())
await 位置模块顶层(ESM)也能 await只能在 async def 里
并发原语Promise.allasyncio.gather
阻塞的后果阻塞整个线程阻塞整个事件循环(所有协程都停)
python
import asyncio

async def fetch(i):
    await asyncio.sleep(1)
    return i

async def main():
    # ✅ 并发:三个一起跑,总共约 1 秒
    return await asyncio.gather(*[fetch(i) for i in range(3)])

asyncio.run(main())

⚠️ 最常见的性能事故:在 async 函数里调了 requests.get()(同步库) 或 time.sleep(),整个事件循环卡住,并发退化成串行。 规则:async 函数里只能用 async 库(aiohttp / httpx.AsyncClient / asyncio.sleep)。

什么时候根本不需要 asyncio:脚本、数据处理、模型训练——同步代码更简单。 asyncio 只在高并发 IO(爬虫、API 网关、大量并发 LLM 请求)时才值得。


六、环境与包管理:与 npm 的对照 ​

这是前端最容易困惑的一块——为什么 Python 要有虚拟环境。

npm 生态Python 生态说明
package.jsonpyproject.toml依赖声明
package-lock.jsonuv.lock / poetry.lock锁定版本
node_modules/(每个项目一份)venv/(每个项目一个隔离环境)关键差异 ↓
npm installuv sync / pip install
npm run devuv run main.py / python main.py

为什么需要 venv:npm 把依赖装在项目目录里,天然隔离; 而 pip install 默认装到全局 site-packages,多个项目依赖冲突会互相打架。 venv 就是给每个项目造一个"独立的世界"。

工具选型(2026 现状 [易变]):

工具定位建议
venv + pip标准库自带够用,但锁版本要手动 pip freeze
uvRust 写的,极快,统一了安装/锁版本/运行新项目首选
poetry成熟,依赖解析严谨已有项目继续用
conda科学计算,能管非 Python 依赖只在需要 CUDA 等场景

给前端的一句话建议:把 uv 当成 pnpm 用——uv init / uv add / uv run, 心智负担最低。


七、AI 场景的最小必备 ​

学 Python 多半是为了用 AI/后端,这几个是高频出现的:

python
# 1. 环境(等价于 package.json + node_modules)
# uv init && uv add openai numpy

# 2. 调 LLM(与 JS SDK 形态几乎一致)
from openai import OpenAI
client = OpenAI()
resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "hi"}],
)
print(resp.choices[0].message.content)

# 3. FastAPI:类型注解即校验即文档
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float

@app.post("/items")
def create(item: Item) -> Item:      # 自动校验请求体、自动生成 OpenAPI
    return item
库干什么前端类比
numpy多维数组与向量运算没有直接对应;别用 Python 循环做数值计算
pandas表格数据处理类似 SQL + Excel
pydantic运行时数据校验类似 zod
FastAPIWeb 框架类似 Express + zod + Swagger 三合一
httpxHTTP 客户端类似 axios(有同步与异步两种 API)

numpy 的心智模型:用数组整体运算代替逐元素循环。 arr * 2 是对整个数组乘 2,不是把数组重复两遍(这点与 Python list 相反!)。


八、自检清单 ​

  • [ ] 能说出 falsy 清单,并解释为什么 if items: 和 if items is None: 不等价
  • [ ] 写函数时不用可变对象做默认参数
  • [ ] 知道 is 只该用于 None
  • [ ] 理解 / 与 // 的区别,且知道 // 是向下取整
  • [ ] 知道 async 函数里不能调同步阻塞库
  • [ ] 能解释 GIL,并说出 CPU 密集场景该用什么(多进程)
  • [ ] 会建虚拟环境,能说清为什么需要它
  • [ ] 知道类型注解运行时不校验
  • [ ] 能在 dataclass 与 pydantic 之间做选择
  • [ ] 能独立写出一个 FastAPI 接口 + 一次 LLM 调用

九、常见误区 ​

误区事实
"Python 简单,看两天就会"语法简单,但作用域、可变性、GIL、导入机制各有坑,照搬 JS 直觉必翻车
"有类型注解就等于 TS"注解运行时不校验,不跑 mypy 等于没写
"多线程能加速计算"GIL 挡着,CPU 密集要用多进程
"async 一定更快"只在高并发 IO 时成立;脚本用 async 是给自己找麻烦
"Python 慢,不能做生产"热点路径常用 C 实现(numpy 等);真正的瓶颈通常在 IO 与架构
"pip install 装哪都行"不隔离环境迟早冲突,用 venv / uv
"self 是关键字"只是约定名,换成别的也能跑(但别这么做)
"Python 没有私有" 就等于"随便访问"下划线是强约定,破坏它后果自负

十、动手练习 ​

练习 A:找出 bug ​

python
def append_to(item, target=[]):
    target.append(item)
    return target

print(append_to(1))
print(append_to(2))

预期是什么?实际是什么?怎么改?

练习 B:把一段 JS 直觉翻译成 Python ​

js
const rows = Array.from({length: 3}, () => [0]);
rows[0][0] = 1;

用 Python 写出来。注意别踩第 ⑥ 条。

练习 C:判断该不该用 asyncio ​

给三个场景(批量调 LLM API 1000 次、训练一个模型、处理一个 10MB 的 CSV), 分别说明用不用 asyncio、为什么。


相关文档 ​

本区 ​

前端区(对照的另一侧) ​

AI 区(学 Python 的主要去处) ​