代码语言

知识点思维导图

29 个知识节点

Python(07) - 面向对象

读完后,你应能完成以下任务:

  • 绘制“Python(07) - 面向对象 / 边界一:self 不是 this,它必须"显式出现"”的关键对象与数据流,解释“每个实例方法的第一个参数必须是 self(名字是约定,理论上可改,但永远别改)。 -> 调用时 tom.greet() 会被 Python 自动翻译成 User.greet(tom),tom 被塞进 self。 -> 访问实例属性必须写 self.name,不能像 JS 那样在方法里直接写 name。”,并用源码位置、日志或 Trace 标注证据。
  • 为“Python(07) - 面向对象 / 边界二:类属性 vs 实例属性(最隐蔽的坑)”设计正常与异常输入,验证“前端容易踩坑,是因为 JS 写在 class 体里的 count = 0 默认是实例字段,而 Python 写在 class 体里的是类属性——同样的位置,含义相反。”,输出首个偏差位置与回归测试结果。
  • 实现“Python(07) - 面向对象 / 继承:和 extends 几乎一样,super 略有区别”的最小代码或配置,检验“Python super().init() 要自己手动调,否则父类的 init 不会跑(JS 里如果子类写了 constructor,不调 super 会直接报错,Python 不报错只是不执行,更隐蔽)。”,输出命令、结果与 Diff,并说明不适用边界。

你在前端写过 class extendsconstructorthis。Python 的 OOP 八成长得像,但有几个"看着一样、其实不一样"的坑:self 不是 this、类属性会被所有实例共享、还有一堆 __xxx__ 魔术方法。本篇帮你把 JS 的 class 心智模型平移过来,并划清差异。

一、先给锚点:和 ES6 class 几乎一一对应

你在 React/TS 里写过的 class,Python 里有等价物,连关键字都很像:

// JS / TS
class User {
  constructor(name, age) {
    this.name = name   // 实例属性
    this.age = age
  }
  greet() {            // 实例方法
    return `我是 ${this.name}`
  }
}
const tom = new User("Tom", 18)
tom.greet()

对照表先建立直觉:

JS / TS Python 说明
class User {} class User: 关键字一致
constructor() def __init__(self) 构造函数
this self 当前实例(但 self 要显式写,见下)
new User() User() Python 没有 new
extends Base class Sub(Base) 继承
super() super().__init__() 调父类
static foo() @staticmethod 静态方法

二、边界一:self 不是 this,它必须"显式出现"

这是前端最容易踩的第一个坑。JS 的 this 是隐式的、自动绑定的;Python 的 self 只是个普通参数,规则简单到有点反直觉:

  1. 每个实例方法的第一个参数必须是 self(名字是约定,理论上可改,但永远别改)。
  2. 调用时 tom.greet() 会被 Python 自动翻译成 User.greet(tom)tom 被塞进 self
  3. 访问实例属性必须self.name,不能像 JS 那样在方法里直接写 name
// JS 对比:this 是隐式的,方法里直接写 this.count
class Counter {
  constructor() { this.count = 0 }
  add() { this.count += 1; return this.count }  // 没有显式 this 参数
}

✅ 好消息:因为 self 是显式传入的,Python 没有 JS 那种 this 丢失的问题。你不需要 .bind(this)、不需要箭头函数来"锁定 this"。把方法当回调传出去,self 也跟得牢牢的。


三、边界二:类属性 vs 实例属性(最隐蔽的坑)

JS 里你习惯所有状态都挂在 this 上。Python 多了一层"类属性"——直接写在 class 下、不在 __init__ 里的变量,它被所有实例共享

读的时候没问题,但如果类属性是可变对象(list/dict),所有实例会共改一份数据——这是经典 bug:

正确写法:可变状态一律放进 __init__,让每个实例独立持有。

类比:类属性 ≈ JS 里写在 class 体里的 static 字段(全类共享);实例属性 ≈ 写在 constructor 里挂到 this 上的字段(各实例独立)。前端容易踩坑,是因为 JS 写在 class 体里的 count = 0 默认是实例字段,而 Python 写在 class 体里的是类属性——同样的位置,含义相反。


四、继承:和 extends 几乎一样,super 略有区别

// JS 对比
class Cat extends Animal {
  constructor(name, color) {
    super(name)                 // 同样先调父类
    this.color = color
  }
  speak() { return `${this.name} 喵喵叫` }
}

差异点:

  • Python super().__init__()自己手动调,否则父类的 __init__ 不会跑(JS 里如果子类写了 constructor,不调 super 会直接报错,Python 不报错只是不执行,更隐蔽)。
  • Python 支持多继承 class C(A, B),JS 不支持。新手别急着用,理解单继承即可。

五、静态方法与类方法:对应 JS 的 static

JS 只有一种 static,Python 分两种,用装饰器区分(装饰器先当"标签"理解,第 10 篇细讲):

// JS 对比:只有 static 一种,静态和"工厂"都靠它
class User {
  static isAdult(age) { return age >= 18 }
  static fromString(text) {
    const [name, age] = text.split(",")
    return new User(name, Number(age))
  }
}
User.isAdult(20)

记忆:@staticmethod ≈ JS 的纯 static(不碰类/实例);@classmethod 是 Python 特有,拿得到类本身 cls,最常见用途是提供"工厂方法"(如 User.from_json(data))。


六、魔术方法(dunder):定制对象的"内置行为"

带双下划线的方法 __xxx__(dunder = double underscore)由 Python 在特定时机自动调用,类似 JS 里 toString()Symbol.iteratorvalueOf() 这类"被引擎隐式调用的钩子"。

魔术方法 何时被调用 JS 类比
__init__ User() 创建实例时 constructor
__repr__ 打印/调试时显示 toString()(偏调试)
__str__ str(obj) / print(obj) toString()(偏展示)
__eq__ a == b 比较时 重写相等逻辑
__len__ len(obj) .length getter
__getitem__ obj[key] 取值时 索引/proxy get

边界提醒:JS 不支持运算符重载a + b 行为固定)。Python 的 __add__ 这类能力比 JS 强,但别滥用——只在"语义天然"的地方用(金额、向量、坐标),否则代码会很迷惑。日常 80% 场景你只需要会写 __init____repr__


七、一个易混淆的小语法:没有 private,用下划线"约定"

Python 没有真正的 private。访问控制靠命名约定:

  • _balance:纯君子协定,等于 TS 里加了 private 但没编译检查——能访问,只是"你不该访问"。
  • __pin:会被改名为 _Account__pin,算是"防误碰",但不是安全机制。

新手记住一条:看到单下划线开头的属性/方法,就当它是内部的,别在外面调。


八、总结

  • 边界一:self 不是 this,它必须"显式出现":每个实例方法的第一个参数必须是 self(名字是约定,理论上可改,但永远别改)。 -> 调用时 tom.greet() 会被 Python 自动翻译成 User.greet(tom),tom 被塞进 self。 -> 访问实例属性必须写 self.name,不能像 JS 那样在方法里直接写 name。
  • 边界二:类属性 vs 实例属性(最隐蔽的坑):前端容易踩坑,是因为 JS 写在 class 体里的 count = 0 默认是实例字段,而 Python 写在 class 体里的是类属性——同样的位置,含义相反。
  • 继承:和 extends 几乎一样,super 略有区别:Python super().init() 要自己手动调,否则父类的 init 不会跑(JS 里如果子类写了 constructor,不调 super 会直接报错,Python 不报错只是不执行,更隐蔽)。
  • 静态方法与类方法:对应 JS 的 static:@classmethod 是 Python 特有,拿得到类本身 cls,最常见用途是提供"工厂方法"(如 User.from_json(data))。
  • 魔术方法(dunder):定制对象的"内置行为":边界提醒:JS 不支持运算符重载(a + b 行为固定)。
  • 一个易混淆的小语法:没有 private,用下划线"约定":_balance:纯君子协定,等于 TS 里加了 private 但没编译检查——能访问,只是"你不该访问"。

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“面向对象”中,需要同时满足“先给锚点:和 ES6 class 几乎一一对应”与“边界一:self 不是 this,它必须"显式出现"”。给定正文约束“你在 React/TS 里写过的 class,Python 里有等价物,连关键字都很像。”,哪些判断保持了原有处理机制?多选
2“面向对象”出现偏差:“在“面向对象 / 边界二:类属性 vs 实例属性(最隐蔽的坑)”中,即使不满足“前端容易踩坑,是因为 JS 写在 class 体里的 count = 0 默认是实例字段,而 Python 写在 class 体里的是类属性——同样的位置,含义相反”,结果与副作用仍会保持不变。”已成为实际行为。围绕“边界二:类属性 vs 实例属性(最隐蔽的坑)”与“继承:和 extends 几乎一样,super 略有区别”,哪些判断能定位被改变的职责或边界?多选
3评审“面向对象”方案时,验收条件包含“@classmethod 是 Python 特有,拿得到类本身 cls,最常见用途是提供"工厂方法"(如 User.fromjson(data))。”。关于“静态方法与类方法:对应 JS 的 static”与“魔术方法(dunder):定制对象的"内置行为"”的哪些决策符合正文机制?多选