前端转 AI 第 4 天:面向对象,class 和 JS class 的异同
昨日回顾
Day 3 把函数这块啃完了,*args / **kwargs 也理解了,作用域也搞清楚了 global 和 nonlocal。
今天进入面向对象。前端看到 class 应该不陌生,ES6 之后我们写 React 组件、写工具类都是 class。所以一开始我以为今天会很轻松,结果——Python 的 class 比 JS 复杂得多,但也强得多。
Day 4 目标:搞懂 class 基本写法,能吃透 self、继承、魔法方法。
一、最基础的 class
先看 JS:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `我是${this.name},今年${this.age}岁`;
}
}
const u = new User("前端", 25);
console.log(u.greet());
Python:
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
return f"我是{self.name},今年{self.age}岁"
u = User("前端", 25)
print(u.greet())
逐行对比:
| 对比项 | JavaScript | Python |
|---|---|---|
| 定义 | class User {} | class User: |
| 构造函数 | constructor() | __init__() |
| 实例引用 | this | self(显式写在参数里) |
| 创建实例 | new User() | 直接 User(),不用 new |
| 方法定义 | greet() {} | def greet(self): |
| 属性赋值 | this.x = x | self.x = x |
三个必须记住的点
1. self 是显式的,而且必须是第一个参数
JS 里 this 是隐式的、上下文决定的,会跟着调用方式变。
Python 里 self 就是第一个参数,写死在方法签名里:
class User:
def greet(self): # self 就是"调用这个方法的那个实例"
return self.name
u = User()
u.greet()
# 等价于 User.greet(u)
换句话说,u.greet() 和 User.greet(u) 是同一个东西。这个认知很重要。
2. 创建实例不用 new
u = User("前端", 25) # ✅
u = new User(...) # ❌ 语法错误
Python 里 User(...) 这种"调用类"的操作,会自动帮你 new 一个实例、调 __init__、返回对象。不用你操心。
3. 属性必须在 __init__ 里初始化
JS 里可以随便 this.x = 1,Python 里建议所有属性都在 __init__ 里赋值一次,否则后面访问会 AttributeError。
class User:
def __init__(self, name):
self.name = name
u = User("前端")
print(u.age) # ❌ AttributeError: 'User' object has no attribute 'age'
JS 会给你
undefined,Python 直接报错。Python 更严格,也更省心——错误早暴露比晚暴露好。
二、继承:和 JS 差不多,但有细节
JS:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} 发出了声音`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
return `${super.speak()}:汪汪`;
}
}
Python:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} 发出了声音"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
def speak(self):
return f"{super().speak()}:汪汪"
几乎一模一样,区别:
- JS 是
class Dog extends Animal - Python 是
class Dog(Animal):,父类写在括号里
多继承:Python 支持,JS 不支持
这是 Python 的一个大杀器:
class Flyable:
def fly(self):
return "我在飞"
class Swimmable:
def swim(self):
return "我在游泳"
class Duck(Flyable, Swimmable):
pass
d = Duck()
print(d.fly()) # 我在飞
print(d.swim()) # 我在游泳
JS 里这个要靠 mixin 手搓,Python 原生支持。
但别高兴太早,多继承有个坑叫 MRO(方法解析顺序)。后面用到再说,先记住有这回事:多个父类有同名方法时,Python 会按 MRO 顺序找,可以用
Duck.__mro__查看。
三、魔法方法:Python 最迷人的地方
JS 里想自定义对象的行为,比如 toString,你得写 toString() 方法;想让它可迭代,得实现 Symbol.iterator。
Python 用双下划线方法(俗称 dunder method)统一了这套机制。
1. __str__:打印对象时显示什么
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"User({self.name}, {self.age})"
u = User("前端", 25)
print(u) # User(前端, 25)
不用 __str__ 的话,打印出来是 <__main__.User object at 0x...>,根本看不懂。
JS 类比: toString(),但 JS 的 console.log 不调它,Python 的 print 会调。
2. __repr__:开发调试时显示什么
class User:
def __init__(self, name):
self.name = name
def __repr__(self):
return f"User(name={self.name!r})"
区别:__str__ 给用户看,__repr__ 给开发者看。
如果只写一个,写 __repr__,因为 print 时找不到 __str__ 会 fallback 到 __repr__。
3. __len__:让对象支持 len()
class Playlist:
def __init__(self, songs):
self.songs = songs
def __len__(self):
return len(self.songs)
p = Playlist(["歌1", "歌2", "歌3"])
print(len(p)) # 3
JS 类比: JS 里没有这种能力,arr.length 是属性不是方法。
4. __call__:让实例可以像函数一样调用
class Adder:
def __init__(self, n):
self.n = n
def __call__(self, x):
return x + self.n
add5 = Adder(5)
print(add5(10)) # 15
这个在 Agent 开发里很有用——把"有状态的函数"封装成一个对象,但它又长得像函数。很多 LangChain 的工具就是这么写的。
5. __eq__:自定义相等
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2) # True(不写 __eq__ 会 False)
注意:写了
__eq__后,对象就不可哈希了,不能放进 set / dict 的 key。如果还想可哈希,要补一个__hash__。
6. 常用魔法方法速查表
| 魔法方法 | 作用 | JS 类比 |
|---|---|---|
__init__ | 构造 | constructor |
__str__ | print 显示 | toString |
__repr__ | 调试显示 | inspect |
__len__ | len() | 无 |
__call__ | 实例可调用 | 无 |
__eq__ | == | equals 思路 |
__lt__ | < | valueOf |
__getitem__ | obj[key] | Proxy 思路 |
__iter__ | for...in | Symbol.iterator |
__enter__ / __exit__ | with 语句 | 无 |
这张表不用背,但要知道有它们存在。 后面遇到的时候再回来看。
四、@property:把方法当属性用
JS 里有 getter:
class User {
constructor(first, last) {
this.first = first;
this.last = last;
}
get fullName() {
return `${this.first} ${this.last}`;
}
}
Python 用 @property:
class User:
def __init__(self, first, last):
self.first = first
self.last = last
@property
def full_name(self):
return f"{self.first} {self.last}"
u = User("张", "三")
print(u.full_name) # 张三,注意不加括号!
为什么用? 计算属性 + 数据校验:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("半径不能为负")
self._radius = value
c = Circle(5)
print(c.radius) # 5
c.radius = 10 # 走 setter
# c.radius = -1 # ❌ ValueError
_radius前面加下划线是 Python 的约定:表示"私有属性,别直接碰"。注意只是约定,不是强制的。
五、实战练习:ChatMessage 类
用今天的知识写一个模拟对话数据结构的类。这个类在后面的 Agent 开发里会反复出现。
新建 day04/chat_message.py:
from datetime import datetime
class ChatMessage:
"""一条对话消息"""
def __init__(self, role: str, content: str):
if role not in ("system", "user", "assistant", "tool"):
raise ValueError(f"不支持的 role: {role}")
self.role = role
self.content = content
self.timestamp = datetime.now()
def __str__(self):
t = self.timestamp.strftime("%H:%M:%S")
return f"[{t}] {self.role}: {self.content}"
def __repr__(self):
return f"ChatMessage(role={self.role!r}, content={self.content!r})"
def __eq__(self, other):
if not isinstance(other, ChatMessage):
return False
return self.role == other.role and self.content == other.content
def to_dict(self):
"""转成 OpenAI API 需要的格式"""
return {"role": self.role, "content": self.content}
class Conversation:
"""一个对话,由多条 ChatMessage 组成"""
def __init__(self, system_prompt: str = ""):
self.messages = []
if system_prompt:
self.messages.append(ChatMessage("system", system_prompt))
def add_user(self, content: str):
self.messages.append(ChatMessage("user", content))
return self
def add_assistant(self, content: str):
self.messages.append(ChatMessage("assistant", content))
return self
def __len__(self):
return len(self.messages)
def __iter__(self):
return iter(self.messages)
def __str__(self):
return "\n".join(str(m) for m in self.messages)
def to_payload(self):
"""转成 API 请求体"""
return [m.to_dict() for m in self.messages]
# ============ 测试 ============
if __name__ == "__main__":
conv = Conversation(system_prompt="你是一个简洁的助手")
conv.add_user("你好").add_assistant("你好,有什么可以帮你?")
conv.add_user("介绍一下 Python")
print("===== 对话内容 =====")
print(conv)
print("\n===== 消息数量 =====")
print(len(conv))
print("\n===== 遍历 =====")
for msg in conv:
print(f" {msg.role}: {msg.content}")
print("\n===== API 载荷 =====")
print(conv.to_payload())
运行结果:
===== 对话内容 =====
[14:23:05] system: 你是一个简洁的助手
[14:23:05] user: 你好
[14:23:05] assistant: 你好,有什么可以帮你?
[14:23:05] user: 介绍一下 Python
===== 消息数量 =====
4
===== 遍历 =====
system: 你是一个简洁的助手
user: 你好
assistant: 你好,有什么可以帮你?
user: 介绍一下 Python
===== API 载荷 =====
[{'role': 'system', 'content': '你是一个简洁的助手'},
{'role': 'user', 'content': '你好'},
{'role': 'assistant', 'content': '你好,有什么可以帮你?'},
{'role': 'user', 'content': '介绍一下 Python'}]
这个类用到了今天所有知识点:
__init__初始化__str__/__repr__自定义显示__eq__自定义相等__len__/__iter__支持len()和for- 继承(
ChatMessage换成别的角色也能用) - 方法链(
add_user返回self)
重点:这个类不是练手,是真正的 Agent 项目起点。 后面调大模型 API 时,请求体就是这个
to_payload()的样子。
六、今日踩坑
- 忘写
self→def greet():报错,方法必须def greet(self): - 调用方法忘加括号 →
u.greet拿到的是函数对象,u.greet()才是执行 __eq__写了但对象不能哈希 → 想放进 set 要补__hash____init__里没赋值的属性 → 后面访问报AttributeError- 类变量和实例变量搞混:
class A: x = 1 # 类变量,所有实例共享 def __init__(self): self.y = 2 # 实例变量,每个实例一份
七、今日总结
今天最关键的三件事:
self是显式的第一个参数,不是 this。方法调用u.greet()等于User.greet(u)- 魔法方法让对象能像内置类型一样被使用:能
print、能len、能for、能() @property把方法当属性用,比 JS getter 更强大(能带校验)
一个感受:
JS 的 class 是"语法糖",Python 的 class 是"真的一等公民"。
前者更多是为了兼容 Java 用户的习惯,后者是语言核心的一部分。
后面写 Agent 时你会发现,几乎所有框架(LangChain、LlamaIndex)都是围绕 class 组织起来的。今天这课打好底子,后面会省很多力。
Day 4 完成度:✅
明日预告(Day 5)
- 文件读写:
open/with/ 编码处理 - 异常处理:
try/except/finally/ 自定义异常 - 路径操作:
os/pathlib - 小练习:写一个日志记录器(写文件 + 异常处理)
最后
如果这篇对你有用,点个赞让我知道有人在看。
有问题评论区见,我们第 5 天继续。
100 天,第 5 天见。
附:系列目录(持续更新)
| 天 | 标题 |
|---|---|
| 01 | 从切图仔出发,100 天走向 Agent 开发 ✅ |
| 02 | list / dict / 推导式,Python 比 JS 爽在哪 ✅ |
| 03 | 函数、参数、作用域:别再写 function 了 ✅ |
| 04 | 面向对象:class 和 JS class 的异同 ✅ |
| 05 | 文件读写与异常处理 |
| 06 | pip / conda / 虚拟环境,彻底搞懂依赖管理 |
| 07 | requests 爬虫实战:抓一个 API |
| 08 | 异步 asyncio:Promise 的另一种形态 |
| … | … |
| 31 | FastAPI 入门:写第一个接口 |
| 46 | 调通第一个大模型 API |
| 61 | RAG 实战:让模型读懂你的文档 |
| 81 | LangChain Agent:让模型自己调工具 |
| 100 | 上线我的第一个 AI Agent 应用 |
1303



被折叠的 条评论
为什么被折叠?



