0.5 面向对象
概述
你已经熟悉 ES6 的 class、constructor、extends、super,以及 TypeScript 里的 interface、私有字段 #。Python 的面向对象模型是经典的原生 class 体系,比 JS 的"语法糖包裹原型链"更直接。主要差异在 self、__init__、私有约定、property 装饰器和 dataclass。
JS/TS ↔ Python 对照表
核心概念
1. class vs JS class
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hi, ${this.name}`;
}
}
const u = new User("Alice");
class User:
def __init__(self, name):
self.name = name
def greet(self):
return f"Hi, {self.name}"
u = User("Alice") # 注意:没有 new
类的定义用
class X:(冒号 + 缩进),方法之间用空行分隔(不是大括号)。创建实例不需要
new,直接User("Alice")。
2. self vs this
self 就是 JS 里的 this,但有两个关键差异:
self必须显式写为方法的第一个参数,且名字约定叫self(也可以叫别的,但没人这么干)。调用方法时
self自动传入,不用手动传。
class User:
def __init__(self, name):
self.name = name # self.name 是实例属性
def greet(self): # self 是约定参数
return f"Hi, {self.name}" # 访问属性也要用 self.
类比:JS 里 this 由调用方式决定(动态绑定,箭头函数会改变 this),Python 里 self 始终是实例本身,没有 this 的绑定问题(不存在 self 丢失,因为它是显式参数)。
3. init vs constructor
constructor(name, age) {
this.name = name;
this.age = age;
}
def __init__(self, name, age):
self.name = name
self.age = age
__init__是"初始化方法",不是严格意义的构造函数——Python 真正创建对象的是__new__(极少用)。双下划线
__开头结尾的方法叫魔术方法(dunder),如__init__、__str__、__repr__、__eq__、__len__。
4. 继承与 super vs extends
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
constructor(name) {
super(name);
}
speak() { return `${this.name} barks`; }
}
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
class Dog(Animal): # 括号里写父类,不是 extends
def __init__(self, name):
super().__init__(name) # 调用父类初始化
def speak(self):
return f"{self.name} barks"
继承语法:
class Dog(Animal),父类写在小括号里(类似extends)。super()用法一致,但父类__init__必须显式调用,不会自动执行(JS 的派生类构造器里也必须调用super(),这点类似)。Python 支持多继承
class C(A, B):,JS 的class不支持多继承(Mixin 需手动模拟)。
5. 私有约定 _ / __ vs #private
JS 有真正的私有字段 #name,Python 没有强制私有,靠约定和名称改写:
class User {
#secret = "hidden"; // 真私有,外部无法访问
}
class User:
def __init__(self):
self._secret = "约定私有" # 单下划线:约定"别碰我"
self.__hidden = "名称改写" # 双下划线:触发名称改写
# 外部仍可访问:
u._secret # 能访问,但约定不该访问
u._User__hidden # 双下划线被改写成 _类名__hidden,仍可绕开
单下划线
_name:约定私有,IDE 和 lint 会提示,但不阻止访问。双下划线
__name:触发名称改写(name mangling),变成_ClassName__name,目的是避免子类属性冲突,不是安全措施。理念差异:Python 崇尚"我们都是成年人",约定 > 强制。
6. 静态方法与类方法
class MathUtil {
static add(a, b) { return a + b; }
}
MathUtil.add(1, 2);
class MathUtil:
@staticmethod
def add(a, b): # 无 self,类似 JS static
return a + b
@classmethod
def from_config(cls, cfg): # cls 指代类本身
return cls(cfg["value"])
MathUtil.add(1, 2)
@staticmethod≈ JS 的static,不需要self。@classmethod是 Python 特色:第一个参数是类本身cls,常用于替代构造器(如from_json、from_config这类工厂方法),JS 没有直接对应。
7. property vs getter/setter
class Temperature {
constructor(celsius) { this._c = celsius; }
get fahrenheit() { return this._c * 9 / 5 + 32; }
set fahrenheit(v) { this._c = (v - 32) * 5 / 9; }
}
class Temperature:
def __init__(self, celsius):
self._c = celsius
@property # getter
def fahrenheit(self):
return self._c * 9 / 5 + 32
@fahrenheit.setter # setter
def fahrenheit(self, v):
self._c = (v - 32) * 5 / 9
t = Temperature(25)
print(t.fahrenheit) # 77.0,像属性一样访问,无需加括号
t.fahrenheit = 100 # 触发 setter
@property让方法像属性一样被访问(t.fahrenheit而非t.fahrenheit())。典型用途:惰性计算、校验赋值、向后兼容(先做成属性,后续改成 property 不影响调用方)。
8. dataclass vs interface / 对象字面量
dataclass 是 Python 3.7+ 的语法糖,自动生成 __init__、__repr__、__eq__ 等样板代码,类似 TS 里"interface + 自动实现的类":
// TS:interface 定义形状,但需要手动实现类
interface Point { x: number; y: number; }
# Python:dataclass 自动生成构造器和常用方法
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
p = Point(1, 2) # 自动生成 __init__
print(p) # Point(x=1, y=2),自动生成 __repr__
p2 = Point(1, 2)
p == p2 # True,自动生成 __eq__(值相等)
注意
dataclass是"可变的数据类";要不可变加@dataclass(frozen=True)。它本质是类,不是 interface——但常被当作"带默认值的数据容器"用,配合类型标注(见 0.8 章)。
代码示例
综合例子:几何图形继承体系
// JS
class Shape {
constructor(name) { this.name = name; }
area() { throw new Error("未实现"); }
}
class Circle extends Shape {
constructor(r) { super("circle"); this.r = r; }
area() { return Math.PI * this.r ** 2; }
}
const c = new Circle(3);
console.log(c.area());
# Python
import math
class Shape:
def __init__(self, name):
self.name = name
def area(self):
raise NotImplementedError("未实现")
class Circle(Shape):
def __init__(self, r):
super().__init__("circle")
self.r = r
def area(self):
return math.pi * self.r ** 2
c = Circle(3)
print(c.area())
字符串表示 __str__ vs toString
class User {
constructor(name) { this.name = name; }
toString() { return `User(${this.name})`; }
}
class User:
def __init__(self, name):
self.name = name
def __str__(self):
return f"User({self.name})"
def __repr__(self):
return f"User(name={self.name!r})"
最佳实践
数据容器优先用
@dataclass,别手写__init__/__repr__样板。属性校验/计算用
@property,别直接暴露裸属性再手动校验。工厂方法(从 JSON/dict 构造)用
@classmethod,语义清晰。私有属性用单下划线
_name即可,别滥用双下划线(除非要防子类冲突)。子类必须调用
super().__init__(),否则父类属性缺失(常见 bug)。打印调试靠
__repr__(给开发者看),用户展示靠__str__(给用户看)。
前端开发者常见陷阱
忘了写
self:方法第一个参数必须是self,调用u.greet()时传 0 个参数却报"缺 1 个参数",其实缺的是self。忘了
new的相反错误:Python 里User("Alice")就是构造,写成new User()会SyntaxError。用
this而非self:self.name = name,写this.name直接报错。属性访问用点号想访问实例属性:Python 实例属性必须
self.x,没有 JS 那种"类字段可省略 this"的隐式语法。_私有不是真的私有:u._secret依然能访问,别指望它防住任何事。__init__不是构造函数:不要在里面做"创建对象"之外的重活,也别和__new__混淆。继承不调用
super().__init__():父类属性不会初始化,访问时AttributeError。多继承的 MRO 顺序:JS 单继承,Python 多继承用 C3 线性化(MRO),
super()行为可能和直觉不同。@property需要两个方法:getter 用@property,setter 用@x.setter(方法名要和 property 名一致)。dataclass 可变默认值:dataclass 字段默认值不能用
[](同样的可变默认参数陷阱),要用field(default_factory=list)。
总结
class X:定义类,__init__(self, ...)初始化,self是显式的this。创建实例不用
new,继承是class Dog(Animal),super()用法一致但要显式调父类__init__。私有靠约定:
_name单下划线;双下划线__name触发名称改写,不是真私有。@property实现 getter/setter,@staticmethod/@classmethod对应静态方法(后者还能拿到类)。@dataclass自动生成__init__/__repr__/__eq__,是 Python 里定义数据类的最佳方式。
0.5-面向对象
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法