0.5 面向对象

概述

你已经熟悉 ES6 的 classconstructorextendssuper,以及 TypeScript 里的 interface、私有字段 #。Python 的面向对象模型是经典的原生 class 体系,比 JS 的"语法糖包裹原型链"更直接。主要差异在 self__init__、私有约定、property 装饰器和 dataclass

JS/TS ↔ Python 对照表

概念

JavaScript / TypeScript

Python

说明

定义类

class X {}

class X:

冒号 + 缩进

构造函数

constructor(...)

def __init__(self, ...):

双下划线 init

实例引用

this

self(第一个参数,显式传)

Python 手动写 self

创建实例

new X()

X()(无 new)

Python 不需要 new

实例属性

this.name = name

self.name = name

__init__ 里赋值

类方法(静态)

static method()

@staticmethod

装饰器标注

类方法(访问类)

无直接对应

@classmethodcls 参数)

类似 JS 的静态方法但能拿到类

继承

class B extends A

class B(A):

括号里写父类

调用父类

super.method()

super().method()

一致

调用父类构造

super(...)

super().__init__(...)

需显式调用

私有属性

#name(真私有)

_name / __name(约定/名称改写)

都不是真正的强制私有

getter/setter

get / set

@property / @x.setter

装饰器实现

类型标注

interface / type

类型注解 + dataclass

见 0.8 章

数据类

对象字面量

@dataclass

自动生成 __init__

字符串表示

toString()

__str__ / __repr__

双下划线"魔术方法"

比较相等

引用相等(默认)

__eq__

可自定义

方法重载

都靠默认参数/类型判断

instanceof

instanceof

isinstance(x, X)

函数形式

抽象类

abstract class

ABC + @abstractmethod

见下文

核心概念

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,但有两个关键差异:

  1. self 必须显式写为方法的第一个参数,且名字约定叫 self(也可以叫别的,但没人这么干)。

  2. 调用方法时 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_jsonfrom_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__(给用户看)。

前端开发者常见陷阱

  1. 忘了写 self:方法第一个参数必须是 self,调用 u.greet() 时传 0 个参数却报"缺 1 个参数",其实缺的是 self

  2. 忘了 new 的相反错误:Python 里 User("Alice") 就是构造,写成 new User()SyntaxError

  3. this 而非 selfself.name = name,写 this.name 直接报错。

  4. 属性访问用点号想访问实例属性:Python 实例属性必须 self.x,没有 JS 那种"类字段可省略 this"的隐式语法。

  5. _ 私有不是真的私有u._secret 依然能访问,别指望它防住任何事。

  6. __init__ 不是构造函数:不要在里面做"创建对象"之外的重活,也别和 __new__ 混淆。

  7. 继承不调用 super().__init__():父类属性不会初始化,访问时 AttributeError

  8. 多继承的 MRO 顺序:JS 单继承,Python 多继承用 C3 线性化(MRO),super() 行为可能和直觉不同。

  9. @property 需要两个方法:getter 用 @property,setter 用 @x.setter(方法名要和 property 名一致)。

  10. 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 里定义数据类的最佳方式。