Python 流水账(Java 开发者视角)

摘要:本文面向有 Java 背景的开发者,系统梳理 Python 与 Java 的核心差异,涵盖类型系统、内置类型、类与异常、集合推导式、async/await、dataclass、虚拟环境与包管理,以及 PyTorch/CUDA 的定位。适合转型 AI 应用工程师(LangChain / RAG / FastAPI)时作为入门参考。

标签:Python · Java · AI应用工程师 · LangChain · 转型笔记


前言:为什么 Java 开发者要先学 Python?

AI 应用生态(LangChain、LlamaIndex、OpenAI SDK、向量库客户端)以 Python 为第一语言。如果你已有 Java 对接第三方 API 的经验——HTTP 封装、鉴权、重试、异常处理、分层架构——这些能力可以直接迁移。

你需要补齐的主要是:

  1. Python 语法与 Java 的差异
  2. 动态类型 + 鸭子类型的思维切换
  3. asyncio 异步模型(LLM 流式/并发调用必备)
  4. pip + venv 环境管理

本文不是 Python 零基础教程,而是 「用 Java 知识理解 Python」 的对照式笔记。


一、Java vs Python 总览

1.1 核心差异对照表

特性 Java Python
类型 强类型,编译期检查 动态类型,可选 Type Hints
入口 public static void main if __name__ == "__main__"
集合 List<String> list[str]
空值 null None
接口 interface + implements 无需声明,鸭子类型
包管理 Maven / Gradle pip + venv
并发 Thread / Executor / CompletableFuture asyncio(IO 密集首选)
资源管理 try-with-resources with 语句
DTO Lombok POJO @dataclass / Pydantic

1.2 思维映射(AI 应用方向)

Java 概念 Python / AI 应用
Interface + Impl Protocol / ABC / Duck Typing
Spring @Service 模块级函数 + FastAPI Depends
DTO / VO Pydantic BaseModel
RestTemplate / Feign httpx / openai SDK
线程池异步 asyncio + async/await
Maven 多模块 虚拟环境 + requirements.txt
JUnit pytest

1.3 两种语言的执行模型差异

Java:

写代码 → javac 编译(含类型检查)→ 编译失败则无法运行 → JVM 执行

Python(默认):

写代码 → 解释器加载(仅语法检查)→ 可以启动 → 运行到某行才暴露类型等错误

这是 Java 开发者转 Python 时最需要适应的一点:Python 默认没有 Java 那种编译期类型检查


二、动态类型与鸭子类型

2.1 动态类型(Dynamic Typing)

变量的类型在运行时才知道,不是写代码时就固定死的。

Java(静态类型):

String name = "hello";
name = 123;  // ❌ 编译报错:类型不匹配

Python(动态类型):

name = "hello"   # 此时 name 是 str
name = 123       # ✅ 合法,现在 name 变成 int
name = [1, 2, 3] # ✅ 又变成 list

要点:

  • Python 里变量没有固定类型,类型属于值(对象)
  • 写错类型:Java 编译期发现;Python 运行到那行才报错

示例:

def process(data):
    return data.upper()  # 假设 data 是字符串

process("hello")  # ✅ 正常
process(123)      # ❌ 运行时报错:AttributeError: 'int' object has no attribute 'upper'

2.2 鸭子类型(Duck Typing)

「如果它走起来像鸭子、叫起来像鸭子,那就当它是鸭子。」

意思是:不关心对象是什么类,只关心它有没有你需要的方法/行为

Java 通常要声明接口:

interface Embedder {
    List<float[]> embed(List<String> texts);
}

class OpenAIEmbedder implements Embedder { ... }

void index(Embedder embedder) {
    embedder.embed(...);
}

Python 鸭子类型:

def index(embedder):  # 没有 interface 声明
    return embedder.embed(["文档1", "文档2"])

# 只要对象有 embed 方法,都能传进来
index(OpenAIEmbedder())
index(LocalEmbedder())

更直观的例子:

class Dog:
    def speak(self):
        return "汪汪"

class Cat:
    def speak(self):
        return "喵喵"

def make_sound(animal):  # 参数类型不限
    print(animal.speak())  # 只要求有 speak()

make_sound(Dog())  # 汪汪
make_sound(Cat())  # 喵喵

2.3 两者如何配合

def batch_embed(embedder, texts: list[str]) -> list[list[float]]:
    return embedder.embed(texts)
  • 动态类型embedder 不必声明成某个具体类
  • 鸭子类型:只要它有 .embed(texts),函数就能工作

LangChain 等框架中,不同 LLM、VectorStore 组件可以互换,很大程度上就是因为这种设计。


三、Python 有编译期类型检查吗?

3.1 结论

Python 解释器默认不做 Java 那种编译期类型检查,但有语法检查

阶段 Java Python(默认)
语法错误 编译失败 启动/导入失败
类型错误 编译失败 运行到才报错

3.2 类型检查发生在什么时候?

以这段代码为例:

def make_sound(animal: str) -> None:
    if not isinstance(animal, str):
        raise TypeError(f"期望 str,实际 {type(animal)}")
    print(animal.upper())

执行顺序:

1. 加载 .py 文件(解析语法)
2. 执行模块顶层代码(import、赋值、def 定义函数…)
3. 有人调用 make_sound(...) 时,才进入函数体
4. 这时才执行 isinstance,不对才 raise TypeError
时机 animal: str(Type Hints) isinstance 检查
启动、加载模块 不检查 不检查
定义 def make_sound(...) 不检查 不检查
调用 make_sound("hello") 不检查 ✅ 通过
调用 make_sound(123) 不检查 ❌ 此时才 TypeError

重要:def 只是登记函数,不会在启动时跑函数体里的校验逻辑。

3.3 如何「提前发现错误」?

工具 作用 类似 Java 的
Type Hints 标注类型,给 IDE 用 方法签名
mypy 静态类型分析 编译期检查
Pyright / Pylance IDE 红线提示 IDEA 警告
Pydantic 运行时校验 API 入参 Bean Validation

工程化建议:

  • 内部逻辑:习惯写 Type Hints
  • 对外 API(FastAPI):用 Pydantic 做运行时强校验
  • CI 流水线:跑 mypy + pytest

四、参数类型如何限制?

Python 默认不强制参数类型,但可以用多种方式表达或校验约束。

4.1 Type Hints(类型注解)— 最常用

def make_sound(animal: str) -> None:
    print(animal.upper())
  • 类似 Java 方法签名
  • 主要给 IDE、mypy 用
  • 运行时 Python 不会自动拦你

4.2 运行时手动校验 — 真正限制住

def make_sound(animal: str) -> None:
    if not isinstance(animal, str):
        raise TypeError(f"期望 str,实际 {type(animal)}")
    print(animal.upper())
  • isinstance(x, str) ≈ Java 的 x instanceof String
  • hasattr(obj, "speak") ≈ 鸭子类型检查:「有没有这个方法」

4.3 Protocol — 鸭子类型 + 类型注解

from typing import Protocol

class Speaker(Protocol):
    def speak(self) -> str: ...

def make_sound(animal: Speaker) -> None:
    print(animal.speak())

# Dog、Cat 不用 implements Speaker,只要有 speak 就行
class Dog:
    def speak(self) -> str:
        return "汪汪"

make_sound(Dog())  # ✅

≈ Java 的 interface,但运行时仍不强制实现接口。

4.4 Pydantic — AI 项目里很常见

from pydantic import BaseModel

class LLMRequest(BaseModel):
    model: str
    prompt: str
    temperature: float = 0.7

# LLMRequest(model="gpt-4", prompt="hi", temperature="高")  # ❌ ValidationError
req = LLMRequest(model="gpt-4", prompt="hi", temperature=0.7)  # ✅

适合 API 入参、配置对象,运行时会真正校验

4.5 对照总结

方式 是否运行时强制 适用场景
无注解 快速脚本、鸭子类型
Type Hints 否(除非配合工具) 日常开发、IDE 提示
isinstance / hasattr 库内部防御性编程
Protocol 静态检查 鸭子类型 + 类型安全
Pydantic BaseModel HTTP API、配置、DTO

五、Python 基本类型详解

5.1 常用内置类型一览

Python 类型 示例 Java 大致对应 说明
str "hello" String 字符串
int 42 int / long 整数,位数不限
float 3.14 double 浮点数
bool True / False boolean 注意首字母大写
None None null 空值
list [1, 2, 3] ArrayList 有序、可变
tuple (1, 2, 3) 无直接对应 有序、不可变
dict {"a": 1} HashMap 键值对
set {1, 2, 3} HashSet 无序、不重复
bytes b"abc" byte[] 字节序列

查看类型:

type("hello")   # <class 'str'>
type(123)       # <class 'int'>
type(True)      # <class 'bool'>

5.2 有 Java 的 String 类吗?

没有 String 这个名字,Python 字符串类型叫 str

name = "hello"          # 字面量
name = str(123)         # 转成字符串 "123"
name.upper()            # 转大写,类似 Java toUpperCase()
len(name)               # 长度
name[0]                 # 'h',类似 charAt(0)
f"学习 {name}"          # f-string 格式化
特性 Java String Python str
是否可变 不可变 不可变
类型名 String str
单/双引号 只能用 " "hello"'hello' 等价

upper() 方法: 把字母转大写,返回新字符串,原字符串不变。

text = "hello"
result = text.upper()  # HELLO
text                   # hello(没变)

5.3 没有 Java 的基本类型

Java 有 int 基本类型和 Integer 包装类之分;Python 全是对象

x = 10
type(x)        # <class 'int'>
x.bit_length() # int 也有方法
Java Python
int / Integer 只有 int
boolean 只有 bool
null 只有 None

5.4 实体类(POJO)怎么写?

方式一:普通 class

class LLMRequest:
    """类似 Java POJO,Python 用 __init__ 构造"""

    def __init__(self, model: str, prompt: str, temperature: float = 0.7):
        self.model = model
        self.prompt = prompt
        self.temperature = temperature

    def __repr__(self) -> str:
        return f"LLMRequest(model={self.model!r}, prompt={self.prompt!r})"

对照 Java:

Python Java
__init__ 构造方法
self this
__repr__ toString()
默认参数 temperature=0.7 方法重载的简化替代

方式二:dataclass(类似 Lombok)

from dataclasses import dataclass
from typing import Optional

@dataclass
class ChatMessage:
    role: str
    content: str

@dataclass
class ChatResponse:
    content: str
    model: str
    tokens_used: Optional[int] = None

方式三:Pydantic(AI 项目对外 API 常用)

from pydantic import BaseModel

class LLMRequest(BaseModel):
    model: str
    prompt: str
    temperature: float = 0.7

5.5 dict 取值:AI 解析 API 响应必备

raw = {"model": "gpt-4o-mini", "usage": {"total_tokens": 42}}

raw["model"]              # 必须存在,否则 KeyError
raw.get("model")          # 安全取值,不存在返回 None
raw.get("model", "unknown")  # 带默认值
raw.get("usage") or {}    # 若为 None 则用 {}
写法 含义 Java 类比
raw["key"] 必须存在 map.get("key") 但会抛异常
raw.get("key", "default") 安全取值 Optional.ofNullable(...).orElse(...)

完整解析 OpenAI 风格响应示例:

def parse_response(raw: dict) -> ChatResponse:
    usage = raw.get("usage") or {}
    return ChatResponse(
        content=raw["choices"][0]["message"]["content"],
        model=raw.get("model", "unknown"),
        tokens_used=usage.get("total_tokens"),
    )

六、list、tuple、dict、set 深入理解

6.1 list — 可变列表

messages = ["system", "user"]
messages.append("assistant")
messages[0] = "new_system"

≈ Java ArrayList

6.2 tuple — 创建后不可变

「固定长度」的准确含义: 不是事先声明长度,而是 创建后不能增删改元素

# list — 可变
items = [1, 2, 3]
items.append(4)   # ✅
items[0] = 99     # ✅

# tuple — 不可变
items = (1, 2, 3)
items.append(4)   # ❌ 没有 append
items[0] = 99     # ❌ TypeError
list tuple
能否改元素
长度 可变 创建后固定
写法 [1, 2, 3] (1, 2, 3)

常见用途:

# 1. 函数返回多个值
def min_max(nums):
    return min(nums), max(nums)
low, high = min_max([1, 5, 3])

# 2. 当 dict 的 key(list 不能当 key)
cache = {("gpt-4", 0.7): "某次结果"}

# 3. 表示不应被改的数据
RGB = (255, 128, 0)

6.3 set — 只去重,不排序

s = set()
for x in [3, 1, 2, 5, 2, 1]:
    s.add(x)

print(s)          # 可能是 {1, 2, 3, 5},也可能不是 — 都不代表已排序
print(sorted(s))  # [1, 2, 3, 5] — 这才是从小到大

数字和字符串都不会自动排序:

words = {"banana", "apple", "cherry", "apple"}
# 只去重,不会自动变成 {'apple', 'banana', 'cherry'}
sorted(words)  # ['apple', 'banana', 'cherry']
特性 set
去重
排序
保序
Python Java
set HashSet — 无序
sorted(set(...)) TreeSet — 要排序用这个

6.4 列表推导式 — AI 数据处理高频写法

≈ Java Stream 的 filter + map,一行搞定。

documents = [
    {"id": 1, "title": "RAG 简介", "tokens": 120},
    {"id": 2, "title": "向量检索", "tokens": 200},
    {"id": 3, "title": "Agent 设计", "tokens": 350},
]

# 列表推导式
titles = [doc["title"] for doc in documents if doc["tokens"] < 300]
# ['RAG 简介', '向量检索']

# 字典推导式
token_map = {doc["id"]: doc["tokens"] for doc in documents}
# {1: 120, 2: 200, 3: 350}

Java 等价思路:

documents.stream()
    .filter(doc -> doc.get("tokens") < 300)
    .map(doc -> doc.get("title"))
    .toList();

6.5 解包与 enumerate

# 解包
first, *rest = ["system", "user", "assistant"]
# first = "system", rest = ["user", "assistant"]

# 带索引遍历
for i, doc in enumerate(documents):
    print(f"[{i}] {doc['title']}")

七、函数、异常与程序入口

7.1 函数与类型注解

def build_messages(system: str, user: str) -> list[dict[str, str]]:
    """返回 OpenAI 风格 messages 列表"""
    return [
        {"role": "system", "content": system},
        {"role": "user", "content": user},
    ]
  • list[dict[str, str]]List<Map<String, String>>
  • 缩进即作用域,没有 {}

7.2 异常处理

def safe_divide(a: float, b: float) -> float:
    try:
        return a / b
    except ZeroDivisionError as e:
        raise ValueError("除数不能为零") from e
Java Python
catch (ZeroDivisionException e) except ZeroDivisionError as e
throw new IllegalArgumentException(...) raise ValueError(...)
异常 cause 链 raise ... from e

7.3 程序入口

if __name__ == "__main__":
    req = LLMRequest("gpt-4o-mini", "你好")
    print(req)
  • if __name__ == "__main__":public static void main(String[] args)
  • 直接 python xxx.py 时执行;被 import 时不执行

7.4 *args 与 **kwargs

def log_request(*args, **kwargs):
    print("位置参数:", args)      # tuple
    print("关键字参数:", kwargs)  # dict

log_request("gpt-4", temperature=0.7, stream=True)
# args = ('gpt-4',)
# kwargs = {'temperature': 0.7, 'stream': True}
  • *args:收多余位置参数 ≈ Java Object... args
  • **kwargs:收多余关键字参数

传参时解包:

params = {"temperature": 0.7, "stream": True}
client.chat(model="gpt-4", **params)

7.5 with 语句 ≈ try-with-resources

with open("doc.txt", encoding="utf-8") as f:
    content = f.read()
# 离开 with 块自动 close

async with httpx.AsyncClient() as client:
    resp = await client.get(url)

八、asyncio 异步编程 — LLM 并发必备

Java 里可能用 CompletableFuture.allOf() 做并发 HTTP;Python AI 生态更常见 asyncio

8.1 async def 与 await

import asyncio

async def mock_llm_call(prompt: str, delay: float = 0.5) -> str:
    """模拟异步 LLM 调用"""
    await asyncio.sleep(delay)
    return f"回复: {prompt[:20]}..."
概念 Java Python
异步函数 返回 CompletableFuture<T> async def,返回 coroutine
等待 .get() / .join() await
非阻塞等待 线程阻塞 await asyncio.sleep() 让出控制权

关键规则:

  • async def 定义的函数不能直接当普通函数调用
  • 必须在 async 函数里 await,或在顶层用 asyncio.run(main())

8.2 并发批量调用

async def batch_call(prompts: list[str]) -> list[str]:
    """并发调用多个 prompt(类似 CompletableFuture.allOf)"""
    tasks = [mock_llm_call(p) for p in prompts]
    return await asyncio.gather(*tasks)

async def main() -> None:
    result = await mock_llm_call("什么是 RAG?")
    print(result)

    prompts = ["问题1", "问题2", "问题3"]
    results = await batch_call(prompts)
    for r in results:
        print(r)

if __name__ == "__main__":
    asyncio.run(main())
  • asyncio.gather(*tasks)CompletableFuture.allOf(...)
  • 3 个 prompt 各 0.5s:串行约 1.5s,并发约 0.5s

AI 场景: OpenAI SDK、httpx、FastAPI 都支持 async;流式 async for chunk in response 在后续开发中频繁出现。


九、环境管理与包管理

9.1 venv vs conda

两者都做环境隔离,类似 Java「每个项目一套独立依赖」。

venv conda
来源 Python 标准库自带 Anaconda / Miniconda
主要管什么 Python 包(pip) Python 版本 + 包 + 系统级库
装包命令 pip install conda installpip install
Python 版本切换 不便 conda create -n x python=3.11

venv 用法(AI 应用开发推荐):

python -m venv venv
# Windows 激活
venv\Scripts\activate
# Linux/Mac 激活
source venv/bin/activate

pip install -r requirements.txt
deactivate

什么时候用 conda:

  • 需要固定 Python 3.10 / 3.11 且本机不好装多版本
  • 安装 PyTorch + CUDA、科学计算栈
  • 依赖复杂 binary 库

9.2 Miniconda vs Anaconda

Miniconda Anaconda
体积 小(几十 MB 起) 大(几 GB)
自带内容 conda + Python + 少量基础 大量预装包(numpy、pandas、jupyter…)
适合 轻量、自己控制 开箱即用
Python 世界 Java 世界(近似)
Miniconda 只装 JDK + Maven
Anaconda JDK + Maven + 全家桶

9.3 pip 与 PyPI

pip = Python 的包安装工具,从网上下载并安装第三方库。

pip install openai
pip install -r requirements.txt
pip list
pip uninstall openai
Python Java
pip Maven / Gradle
requirements.txt pom.xml
pip install xxx 添加 dependency 后下载

PyPI = Python Package Index(https://pypi.org),Python 的公共包仓库

Python Java
PyPI Maven Central
pip install openai pom 里写 dependency

流程:

pip install openai → pip 去 PyPI 下载 → 装到当前 venv/conda 环境 → import openai

conda 与 pip 别搞混:

工具 仓库 常见用途
pip PyPI LangChain、FastAPI、OpenAI SDK
conda install conda-forge 等 Python 版本、numpy、CUDA

十、PyTorch 与 CUDA 是什么?我需要学吗?

10.1 PyTorch

Python 主流深度学习框架,用于构建神经网络、训练推理模型。

import torch
x = torch.tensor([1.0, 2.0, 3.0])
y = x * 2

10.2 CUDA

NVIDIA 的 GPU 并行计算平台,让程序能用 NVIDIA 显卡做大规模矩阵运算。

概念 说明
CPU 通用计算,适合业务逻辑、API
GPU 大量并行,适合矩阵、模型计算
CUDA 让 PyTorch 等框架「会用 NVIDIA GPU」

10.3 为什么常一起提「PyTorch + CUDA」?

装 PyTorch 时要选是否 GPU 版、对应哪个 CUDA 版本。没有 GPU/CUDA 时 PyTorch 只能跑 CPU,大模型会很慢。conda 在这种场景里常能把 Python + PyTorch + CUDA 相关库一起配好。

10.4 AI 应用工程师需要吗?

方向 需要什么 典型技术
AI 应用工程师 API、向量库、Prompt OpenAI SDK、LangChain、FastAPI
算法/训练工程师 本地 GPU、大数据 PyTorch + CUDA
本地部署开源模型 GPU + 推理框架 PyTorch、llama.cpp、vLLM
概念 近似理解
调 OpenAI API 像调第三方支付 API,算力在对方
PyTorch 本地推理 像在自己机房部署清算引擎

结论:转型 AI 应用工程师、以 LangChain/RAG/Agent 为主,通常不需要先装 PyTorch + CUDA。


十一、动手练习

练习 1 — 类与函数

# 1. 给 LLMRequest 增加 max_tokens: int = 2048
# 2. build_messages 支持传入多个 user 消息

练习 2 — 集合推导式

documents = [
    {"id": 1, "title": "RAG 简介", "tokens": 120},
    {"id": 2, "title": "向量检索", "tokens": 200},
    {"id": 3, "title": "Agent 设计", "tokens": 350},
]

# 求 tokens 总和
total = sum(doc["tokens"] for doc in documents)

# 求 tokens 最大的文档 title
max_doc = max(documents, key=lambda d: d["tokens"])
print(max_doc["title"])

练习 3 — 异步

# 对比串行 vs 并发耗时
import asyncio
import time

async def main():
    prompts = ["问题1", "问题2", "问题3"]

    start = time.time()
    for p in prompts:
        await mock_llm_call(p)
    print(f"串行: {time.time() - start:.2f}s")

    start = time.time()
    await batch_call(prompts)
    print(f"并发: {time.time() - start:.2f}s")

练习 4 — DTO

# 新增 ChatRequest dataclass: model, messages: list[ChatMessage]
# 写一个 to_openai_payload() -> dict 方法

十二、学习检查清单

  • 能解释动态类型与鸭子类型的区别
  • 能解释 Python 为何没有 Java 那种编译期类型检查
  • 知道 Type Hints、isinstance、Pydantic 各自何时报错
  • 能解释 *args / **kwargs 的含义
  • 能写出带类型注解的函数签名
  • 理解 async defawait 的调用关系
  • 能使用列表推导式替代简单 for 循环
  • 理解 with 语句(类似 Java try-with-resources)
  • 知道 strlistdicttupleset 的区别与用途
  • 知道 tuple 不可变、set 只去重不排序
  • 知道 venv、pip、PyPI、conda 分别是什么
  • 知道 AI 应用路线暂不需要 PyTorch + CUDA

十三、总结

主题 核心要点
类型系统 动态类型 + 鸭子类型;无默认编译期检查
类型约束 Type Hints 提示;Pydantic 强校验
基本类型 str 即字符串;无 String 类名
容器 list 可变;tuple 不可变;set 去重不排序
异步 async/await + asyncio.gather 做 LLM 并发
DTO @dataclass 或 Pydantic BaseModel
环境 AI 应用开发优先 venv + pip + PyPI
PyTorch 训练/本地推理才需要,调 API 不需要

Java 开发者转型 AI 应用工程师,HTTP 对接、异常处理、分层架构经验可以直接复用。重点补齐 Python 语法、异步模型和 pip 生态,即可顺利进入 Prompt Engineering、LLM API 封装、RAG 等后续阶段。


附录:推荐阅读顺序

  1. 本文笔记(建立 Java → Python 映射)
  2. Prompt Engineering(System/User Prompt、Few-shot)
  3. LLM API 客户端封装(复用 Java 对接经验)
  4. LangChain → RAG → Agent → FastAPI 服务化

本文为个人学习笔记,欢迎交流指正。

更多推荐