深夜的后厨,老主厨把一本泛黄的菜谱拍在桌上:“宫保鸡丁,鸡丁半斤、花生二两、干辣椒一把……”学徒只要照着这本菜谱做,就能端出一盘盘色香味俱全的菜。同一本菜谱,能做出成百上千盘菜——每盘都有自己的装盘、温度和入口的瞬间,但它们都来自同一份配方、同一套工序。写程序也一样。一个订单系统、一个推荐引擎、一套训练管线,如果所有代码都堆在一个文件里,就像把整个后厨的食材和锅铲全倒进一口大锅里,谁也分不清哪是哪。而面向对象编程(OOP)要做的,就是把“菜谱”和“菜”分开:菜谱是类(class),描述“怎么做”;菜是对象(object),是真正落地、带着数据运行的实体。本文会带你逛一圈 Python 的“后厨”:从类与实例的入门,到继承与多态的进阶,再到属性控制与特殊方法,最后对比 Python 和 Java 两位大厨的风格。学完你会发现,把世界装进类,其实是把复杂拆成一张张清晰的菜谱。

岗位关联

面向对象是把 AI 项目从“能跑”推向“能维护、能扩展”的分水岭。掌握类、继承与多态,你才能像搭积木一样组织模型、数据与训练流程,写出真正工程级的 Python 代码。

目录
  1. 可视化演示
  2. 类与实例:菜谱与佳肴
  3. 属性与方法:食材与手艺
  4. 封装与属性:后厨的秘密
  5. 继承机制:菜谱的传承
  6. 多态与鸭子:只要会叫就是鸭
  7. 特殊方法:餐厅的服务仪式
  8. 语言差异:中餐大厨与法餐大厨
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

演示Python面向对象编程的四大核心概念:类与实例、封装与属性、继承与多态、特殊方法,通过交互式可视化展示对象之间的关系和代码组织方式。

本节配有交互动效(进入视口后自动加载,约 3KB)。

类与实例 类是对象的蓝图模板 实例拥有独立属性值 方法在实例间共享 通过class关键字定义类

类与实例:菜谱与佳肴

餐馆灶台上摞着一本菜谱,第一页写着宫保鸡丁:鸡丁半斤、花生二两、干辣椒段,滑油、爆香、翻炒出锅。菜谱本身不能吃,可老主厨每天照它做三十盘。每一盘都独立放在自己的盘子里,有自己的温度和辣度,谁也不会和谁混在一起。

写脚本的日子里,你像个随手炒饭的路边摊:一把锅、一堆料、一股脑倒进去,反正只做一顿。可 AI 项目不是一顿饭——训练、验证、调参、部署,一个模型要反复用上几十次。这时你就需要把“做法”和“成品”分开:做法沉淀成类(class),成品叫对象(object,也叫实例)。类回答“这类东西有什么属性和行为”,对象回答“这一份具体的数据是什么”。

定义类用 class 关键字。类里通常写一个 __init__ 构造方法,它负责“出锅时自动备料”:把传入参数存到 self 绑定的属性上。之后用 Dish('宫保鸡丁', 3) 就像把菜谱交给厨师,Python 会创建并返回一个全新的对象。每调用一次,就得到一份独立的实例,它们各自持有数据,互不干扰。

一句话总结本节:类是图纸,对象是照图纸盖出来的房子。菜谱可以无限复用,做出来的菜却是一盘盘独立的存在。学会区分这两者,你就迈进了面向对象的大门。

对象=类(参数)\displaystyle \text{对象} = \text{类}(\text{参数})
把菜谱递给厨师,再报上具体食材参数,就能出锅一道具体的菜。
  1. 类(菜谱蓝图)类名就像菜谱的标题,Dish 定义了整类菜品的模板。
    菜谱名→类名\displaystyle \text{菜谱名} \to \text{类名}
  2. 参数(食材清单)括号里的参数就是给菜谱补充的具体食材,比如“辣度3”。
    参数={name,spice,…}\displaystyle \text{参数} = \{ \text{name}, \text{spice}, \ldots \}
  3. 对象(出锅的菜)调用返回一个真实存在的实体,拥有自己独立的数据。
    obj=Dish(’宫保鸡丁’,3)\displaystyle \text{obj} = \text{Dish}(\text{'宫保鸡丁'}, 3)

属性与方法:食材与手艺

翻开任何一本菜谱,信息总是两类:要备哪些料,要走哪些步骤。“备料”对应属性——一道菜叫什么、辣不辣、多少钱;“步骤”对应方法——这道菜能上桌、能回锅、能改辣度。属性回答“它是什么”,方法回答“它能做什么”。

属性还能分红白两本账:实例属性挂在每个对象自己身上,像每盘菜各自的辣度;类属性挂在类上,像所有川菜共享的“招牌辣味指数”,所有实例都能读到同一份。方法则按着装分三种:最常见的是实例方法,第一个参数写 self,干活时用自己那份数据;加 @classmethod 的类方法,第一个参数收 cls,处理与整个类相关的统计;加 @staticmethod 的静态方法,连 self 都不要,是后厨公用的一把剪刀。

self 是理解实例方法的钥匙。dish.serve() 写起来像“这盘菜自己报菜名”,实际上 Python 在背后把它改写成 Dish.serve(dish)——self 不过是“自己”这个实参的座位。它不需要你手动传,但定义时必须写在第一个参数位。

备料、炒制、公用工具各归其位,后厨才不会乱。把数据和操作捆进同一个类,让对象“自带说明书”,这是封装(encapsulation)思想的雏形,下一节我们就关上后厨的门。

dish.serve()  ⟺  Dish.serve(dish)\displaystyle \text{dish}.\text{serve}() \iff \text{Dish}.\text{serve}(\text{dish})
点号左侧的对象被悄悄塞进第一个参数,self 坐的就是“自己”这个位置。
  1. 实例方法调用obj.m(x) 语法被 Python 自动翻译成“把 obj 传入 m 的第一个参数”。
    obj.m(x)→Class.m(obj,x)\displaystyle \text{obj}.\text{m}(x) \to \text{Class}.\text{m}(\text{obj}, x)
  2. 类方法收 cls@classmethod 让方法的第一个参数收到类本身,可以读写类属性。
    cls→类对象\displaystyle \text{cls} \to \text{类对象}
  3. 静态方法免身位@staticmethod 的方法不接收 self 或 cls,只是借了类的外壳。
    staticmethod(func)=普通函数\displaystyle \text{staticmethod}(\text{func}) = \text{普通函数}

封装与属性:后厨的秘密

老主厨最得意的是一坛“哑巴辣椒油”,配方锁在后厨最里面的柜子里,外人只看见成品、尝到味道,绝不进柜子翻配方。想调整辣度?找前台备注就行。这份“藏起细节、只留安全入口”的自觉,就是封装。封装让内部怎么改都不影响外部,稳定得像锁起的柜门。

Python 里没有 Java 那种“锁死”的私有关键字,靠命名约定表达“请勿外传”:_x 单下划线是保护属性,相当于“内部文件,非请勿入”,是君子协定;__x 双下划线会触发名称改写(name mangling),Python 把属性悄悄改名为 _类名__x,像把瓶子上标签改写一遍,防止亲人误拿。注意,这只是防呆不防贼,不是加密保险箱。

真正优雅的封装姿势是 @property。给“读辣度”配一个 getter,给“改辣度”配一个 setter,外部代码依然写 dish.spice = 3,但赋值的一瞬间会经过 setter 的质检关卡:辣度超出 0~5 直接抛异常。读写入口被上了轨道,数据访问从此有规有矩。

顾客永远只碰菜单,不碰后厨的坛坛罐罐。@property 就是那份菜单——外面看着是普通文字,里面每道工序都被主厨严格控制。把私密藏好,把门面做漂亮,封装让代码经得起换人、改版、被调用一万次。

spice=property(fget,fset)\displaystyle \text{spice} = \text{property}(f_{\text{get}}, f_{\text{set}})
property 像一个自动关卡:读属性时走 getter,写属性时走 setter,外部用法完全不变。
  1. getter 读取外部执行 dish.spice 时,调用被 @property 修饰的函数返回值。
    x=fget(self)\displaystyle x = f_{\text{get}}(\text{self})
  2. setter 写入执行 dish.spice = 5 时,调用 @spice.setter 修饰的函数,先校验再落库。
    fset(self,v),0≤v≤5\displaystyle f_{\text{set}}(\text{self}, v), \quad 0 \le v \le 5
  3. 名称改写__x 实际被改写为 _类名__x,避免被子类意外覆盖。
    __x⇒_Dish__x\displaystyle \text{\_\_x} \Rightarrow \text{\_Dish\_\_x}

继承机制:菜谱的传承

学徒想学“宫保鸡丁”,老主厨说:别从零开始。你把“辣子鸡”的菜谱拿过去,鸡换成鸡腿丁,多加一把花生米,滑油、爆香、翻炒全部照旧。五分钟,一份新菜谱诞生了。这就是继承:站在老菜谱的肩膀上改出新菜谱。

被继承的叫父类(基类),新生成的叫子类。子类自动获得父类的所有属性和方法,代码瞬间少写一大半。子类还能重写(override)父类方法——“上菜”的报菜名换套台词;或者用 super() 先调用父类的原版流程,再追加自己的新步骤。在 AI 工程里,MyModel(nn.Module)、MyDataloader(BaseLoader) 全是这个套路。

Python 还允许一个子类有多个父类,叫多重继承。查找方法时按 MRO(方法解析顺序,由 C3 线性化算法算出)逐层寻找。多继承功能强,但也容易让人迷路:深度继承的排查体验,堪比在五层阁楼里找一把菜刀。经验之谈:组合优于继承——“菜里有花生”(组合)通常比“花生是菜的孙子”(深层继承)清晰得多。

继承传的是菜谱,不是厨房。把公共逻辑沉淀进父类,把变化留给子类,再用 super() 保持血脉通畅;同时记住,层级越浅越香,必要时用组合替代继承,大厨房才不乱。

Attr(子类)⊇Attr(父类)∪{新能力}\displaystyle \text{Attr}(\text{子类}) \supseteq \text{Attr}(\text{父类}) \cup \{ \text{新能力} \}
继承的集合论含义:子类的能力集合,是父类能力集合的超集,再加入自己的新能力。
  1. 继承声明class SichuanDish(Dish) 表示“照着 Dish 这份菜谱衍生”。
    class 子类(父类):\displaystyle \text{class 子类}(\text{父类}):
  2. 能力传递父类的公共属性与方法自动出现在子类中,代码复用由此而来。
    Attr(子)⊇Attr(父)\displaystyle \text{Attr}(\text{子}) \supseteq \text{Attr}(\text{父})
  3. super 调用super().serve() 先执行父类的上菜流程,再叠加子类的新台词。
    super()→父类对应方法\displaystyle \text{super()} \to \text{父类对应方法}

多态与鸭子:只要会叫就是鸭

餐厅有条不成文的规矩:只要能端上桌的,服务员就敢报给客人。鸡是菜,鱼是菜,嫩豆腐也是菜。客人只关心“上菜”,从不管背后是煎是炒是炖。同一个动作、不同对象各有各的演法,这叫多态(polymorphism)。

Python 的多态实现靠鸭子类型(duck typing):“如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。”只要对象身上有 serve() 方法,函数就能放心调用它,不管它是 Dish 本尊、还是隔壁摊子端来的远房亲戚。Python 不在编译期检查户口,只在运行期伸手一摸——摸得到方法就干,摸不到就抛 AttributeError。

这也是 Python 没有方法重载的原因:同名方法只有一个,后写的覆盖先写的。想要“必须是真鸭子”的保证?用 abc 模块的抽象基类(ABC)定义必须实现的方法骨架,子类不实现就实例化失败。这像餐厅规定:凡自封“主菜”的,必须会报菜名。

多态让“新增菜品”不必改动点单系统。你只管面向接口编程:调用方永远说“上菜”,每个对象自己决定怎么上。餐厅越开越大,前厅系统却纹丝不动——这就是多态给大型 AI 项目带来的伸缩自由。

duck_test(x)=True  ⟺  ∃ x.serve()\displaystyle \text{duck\_test}(x) = \text{True} \iff \exists \, x.\text{serve}()
一个对象是否合格,取决于它有没有那个方法,而不是它的“户籍”。
  1. 调用统一一个 for 循环可以遍历不同子类对象,逐个调用同名方法。
    ∀x∈集合:  x.serve()\displaystyle \forall x \in \text{集合}: \; x.\text{serve}()
  2. 运行期判断Python 在调用发生时才去对象上找方法,找不到就抛异常。
    getattr(x,’serve’) 是否存在\displaystyle \text{getattr}(x, \text{'serve'}) \text{ 是否存在}
  3. 抽象基类约束ABC 强制子类补齐方法,让“鸭子”确实是能叫的鸭子。
    abstractmethod⇒必须实现\displaystyle \text{abstractmethod} \Rightarrow \text{必须实现}

特殊方法:餐厅的服务仪式

高档餐厅的仪式感藏在细节里:菜单印得精致漂亮(print 出来像样);服务员报出菜名和份量(len 拿得到数字);客人问“这桌和那桌是不是同一道菜”(== 能比)。这些动作不需要客人专门说“请调用 display 函数”,一切自然而然地发生。Python 里让“自然动作”生效的,是特殊方法,也叫魔术方法或双下划线方法(dunder)。

它们的名字被前后两个下划线包住:__str__、__repr__、__len__、__getitem__、__eq__……你一 print(dish),Python 自动去调 dish.str();你一 len(dish),自动去调 len();两个对象一"==",自动去调 eq()。你的类从此接通了 Python 的内置电路。

运算符重载同出一源:+ 走 add,< 走 lt,d[0] 走 getitem。把这些方法写进类,你的对象就好像会读心术,for 循环、print、sorted、dict 键值全都认得它。这就是“把世界装进类之后,世界还照常运转”的秘密——特殊方法是类与 Python 生态之间的通用插座。

仪式感不是花架子。__str__ 让日志可读,__eq__ 让模型对象可比较,__len__ 让数据集对象能数样本数。做 AI 项目时,把打印、比较、长度这些“人味”接口补齐,你的类才算真正接客。

print(x)→x.__str__(),len(x)→x.__len__(),x==y→x.__eq__()(y)\displaystyle \text{print}(x) \to x.\_\_\text{str}\_\_(), \quad \text{len}(x) \to x.\_\_\text{len}\_\_(), \quad x == y \to x.\_\_\text{eq}\_\_()(y)
内置函数是被特殊方法“钩”住的:print、len、== 都转化为对对象特殊方法的调用。
  1. 字符串显示print(x) 时调用 x.str(),定义“这道菜如何报菜名”。
    print(x)→x.__str__()\displaystyle \text{print}(x) \to x.\_\_\text{str}\_\_()
  2. 容器协议len(x) 需要 x.len() 给出整数,下标取值走 getitem(key)。
    len(x)→x.__len__()\displaystyle \text{len}(x) \to x.\_\_\text{len}\_\_()
  3. 相等比较x == y 默认比较内存地址,重写 eq 后比较菜品内容。
    x==y↔x.__eq__()(y)\displaystyle x == y \leftrightarrow x.\_\_\text{eq}\_\_()(y)

语言差异:中餐大厨与法餐大厨

同样开一家餐厅,Python 大厨和 Java 大厨是两种物种。Python 大厨口袋里揣着草稿:“盐少许、油适量”,凭手感下料,边炒边尝,上菜飞快;Java 大厨白手套、计时器、温度计三件套,每道菜精确到克,每道工序持证上岗,不合格食材在进门那刻就被拒之门外。

落到技术上:语法风格上,Python 用缩进定义代码块,追求“写得快、读得懂”;Java 用花括号和强制类型,追求“查得严、跑得稳”。类型系统上,Python 是动态类型,变量像贴纸,运行时才确定贴的是什么;Java 是静态类型,变量声明即锁定,编译期就报错拦截。

访问控制上,Java 靠 public/protected/private 三个关键字由编译器强制执行私有;Python 靠 _x/__x 命名约定,靠团队自觉。接口上,Java 的 interface 是硬契约——没实现就编译失败;Python 的 ABC 抽象基类是软契约——只是运行时提醒。AI 领域常见分工是:Python 快写原型、快速训练,Java 或 Go 承接需要高稳定性的在线服务,各取所长。

中餐大厨的随性成就了上菜速度,法式厨房的严谨保证了出品稳定。语言没有高低,只有合不合场。你学 Python OOP,不是要跟 Java 比谁锁得严,而是要明白:动态与静态、约定与强制,都是工具,场景说了算。

Python: 运行期检查类型vsJava: 编译期检查类型\displaystyle \text{Python: 运行期检查类型} \quad \text{vs} \quad \text{Java: 编译期检查类型}
一个“先上车后补票”,一个“先体检再上车”。没有绝对好坏,只有场景适配。
  1. 动态类型Python 的变量只是名字贴纸,运行时才绑定具体类型。
    名字→运行期类型\displaystyle \text{名字} \xrightarrow{\text{运行期}} \text{类型}
  2. 静态类型Java 的变量声明即锁定容器,编译期做类型检查。
    声明→编译期类型\displaystyle \text{声明} \xrightarrow{\text{编译期}} \text{类型}
  3. 访问控制Java 靠编译器拦私有访问,Python 靠命名约定告知“请自重”。
    private≈__x+君子协定\displaystyle \text{private} \approx \text{\_\_x} + \text{君子协定}

AI 应用场景

机器学习模型的类封装

阶段:模型训练与推理管线

解决的问题:脚本式训练代码全局变量满天飞,换一组超参数像拆零件,复现实验全靠缘分。把模型封装成类后,训练、评估、预测各成方法,超参数变成属性,换参数只是换一份“菜谱”。

具体案例:一个算法团队把三种候选模型各自封装成类,统一提供 load / train / predict 接口。跑对比实验时,一个 for 循环遍历所有模型子类即可,新增模型零改动主流程。PyTorch 的 nn.Module 就是这个思想的工业级实现——你的网络只需继承它,前向传播写进 forward。

特征工程的 Transformer 类

阶段:数据预处理与特征工程

解决的问题:特征处理代码散落在各个脚本里,训练时学的均值、方差,上线时忘了重算,导致线上线下数据分布不一致。按类封装后,训练集上学到的统计量被保存在对象内部,预测时调用同一个 transform 接口。

具体案例:做用户画像项目时,把缺失值填充、归一化、独热编码封装成一系列“转换器”类,流水线一条龙复用,训练和线上推理始终用同一套逻辑。scikit-learn 的 TransformerMixin 正是这一模式的标准化形态。

多模型统一推理接口

阶段:服务化部署与推理

解决的问题:不同来源的模型(本地训练、云端 API、第三方服务)推理方式五花八门,服务端写满 if/else 分支。用抽象基类定死 predict 接口,各模型实现子类,调用方只面向基类编程,多态自动分发。

具体案例:推荐系统把规则模型、LR 模型、深度排序模型各封装一个子类,请求进来统一调用 predict_score,新增模型时主服务一行不改,只加一个子类文件。这就是“面向接口编程”在 AI 工程里的日常形态。

Python 演示

python56 行
class Dish:    """菜谱: 一份菜的蓝图"""     def __init__(self, name, spice, price):        self.name = name        self._spice = spice          # 保护属性, 请走 property 入口        self.price = price     @property    def spice(self):        """getter: 读辣度时自动调用"""        return self._spice     @spice.setter    def spice(self, level):        """setter: 写辣度时先质检再落库"""        if 0 <= level <= 5:            self._spice = level        else:            raise ValueError("辣度必须是 0 到 5")     def serve(self):        return "{} 上桌, 辣度 {}, 价格 {} 元".format(self.name, self._spice, self.price)     def __str__(self):        return "【{}】辣{}/{}元".format(self.name, self._spice, self.price)     def __eq__(self, other):        return self.name == other.name and self.price == other.price  class SichuanDish(Dish):    """川菜: 继承 Dish, 再加一味麻"""     def __init__(self, name, spice, price, numbing):        super().__init__(name, spice, price)        self.numbing = numbing     def serve(self):        return super().serve() + ", 麻度 {}".format(self.numbing)  def duck_test(thing):    """鸭子类型: 只要有 serve 方法就能上桌"""    return thing.serve()  if __name__ == "__main__":    kungpao = SichuanDish("宫保鸡丁", 3, 28, 4)    mapo = Dish("麻婆豆腐", 5, 22)    print(duck_test(kungpao))    print(duck_test(mapo))    print(len(duck_test(kungpao)) > 0)    print(kungpao == SichuanDish("宫保鸡丁", 3, 28, 4))    kungpao.spice = 4                # 走 setter 入口    print(kungpao)
目的:一段可运行的“后厨演示”,把菜谱、继承、多态、封装、特殊方法全部串起来。
  1. class Dish:定义菜谱模板,所有菜品共享这份蓝图。
  2. def __init__(self, name, spice, price):构造方法,出锅时自动备料。
  3. self._spice = spice单下划线保护属性,提醒外部别直闯。
  4. @property def spice(self):getter,让 dish.spice 看起来像普通属性。
  5. @spice.setter def spice(self, level):setter 质检,辣度超范围直接拒绝。
  6. class SichuanDish(Dish):继承父类,子类自动拿到全部能力。
  7. super().__init__(name, spice, price)先让父类完成初始化,再补川菜自己的麻度。
  8. def duck_test(thing): return thing.serve()鸭子类型,不查户口只查手艺。
  9. kungpao == SichuanDish("宫保鸡丁", 3, 28, 4)触发 __eq__,按菜名和价格比对。

常见误区

误区我定义了一个 `class Dish`,然后直接写 `Dish.name` 取数据,Python 报错说没有这个属性,是不是我的写法有问题?

正解类是一张菜谱,菜谱上当然没有“这一盘”的辣度。只有照着菜谱做出来的对象(实例)才有具体数据。想吃菜,先“下锅”:Dish("宫保鸡丁", 3, 28)。类名负责描述结构,实例才承载真实数据,两者各司其职。

为何常见:新手看到类名大写、像个“东西”,本能地把它当成可操作的对象;加上不少教程只说“类就是模板”却没说清“模板不能当成品用”。

误区self 是 Python 的保留字,必须叫 self,换成别的名字就会报错,少写一个 self 就语法错误。

正解self 只是惯例。它是实例方法第一个参数的约定俗成名字,你可以改成 this、me 甚至 anything,只要它是第一个参数。真正起作用的是:调用 obj.m() 时,Python 自动把 obj 传进这个参数位。少写 self 报错,是因为参数个数对不上,不是因为“关键字缺失”。

为何常见:几乎所有教程都统一写 self,且每个方法都要显式声明它,这种“全员一致”很容易让人误以为是语言层面的强制关键字。

误区属性前面加两个下划线就能藏得严严实实,别人绝对访问不到,和 Java 的 private 一样安全。

正解Python 的 __x 只是名称改写(name mangling),被悄悄变成 _类名__x,防的是“误拿”,不是“恶意访问”。用 obj._Dish__x 照样能读到。Python 的哲学是“我们都是成年人了”——安全靠设计约定和团队纪律,不靠语言强制。

为何常见:双下划线看起来像“加密符号”,加上部分文章夸大它的作用,让人误以为 Python 提供了真正的私有屏障。

误区为了复用代码,我把类一层一层往下继承,这样可以省掉大量重复代码,层级越深代表设计越高级。

正解过深的继承会让 MRO 变得极其绕,排查一个方法从哪来,堪比在五层阁楼里找一把菜刀。Python 社区更推崇组合和扁平结构:“有一个”优于“是一个”——菜里有花生(组合属性),而不是造一个“花生的孙子”类。父类沉淀公共逻辑,一层或两层足矣。

为何常见:教科书总是强调继承带来的复用红利,但工程里的灾难恰恰来自过度继承,宣传和现实的偏差让新手一上来就“深挖坑”。

面试题

Q1. 在 Python 中,“类”最准确的理解是?

单选题 · easy

  • A.一份存储数据的文件
  • B.一张可复用的图纸← 正确答案
  • C.一个真实存在的对象
  • D.一种特殊的函数

答案:B。类是和对象相对的概念,它定义结构和行为,本身不携带具体数据,就像菜谱之于菜。A 把类窄化成“存数据的文件”,C 把类和对象混为一谈,D 则完全偏离了类的本质。

Q2. __init__ 方法的主要职责是?

单选题 · easy

  • A.删除对象时自动触发回调
  • B.用来定义这个类包含的所有方法
  • C.用于初始化新创建的实例对象← 正确答案
  • D.用于返回类的字符串表达形式

答案:C。__init__ 在对象创建后自动执行,负责把参数存进实例属性,相当于“出锅时自动备料”。A 描述的是析构逻辑,B 是类体的作用,D 是 __str__ 的职责。

Q3. Python 方法定义中第一个参数 self 表示?

单选题 · mid

  • A.指类对象自身这个整体
  • B.指实例对象自身这个整体← 正确答案
  • C.指模块所拥有的命名空间
  • D.指父类所拥有的命名空间

答案:B。调用 obj.method() 时,Python 把 obj 自动传入第一个参数位,所以 self 永远指向“当前这个实例”,而不是类、模块或父类。类对象要由 cls 或 ClassName 来指代,模块则是另一个命名空间。

Q4. 关于 @property 装饰器的作用,正确的是?

单选题 · mid

  • A.把方法变成可读取的属性← 正确答案
  • B.可以让方法支持并发执行
  • C.把私有属性变成公共属性
  • D.让类能够被多次继承使用

答案:A。@property 把无参方法包装成属性访问,外部写 obj.spice 就能拿到返回值,还能搭配 setter 做校验。它不涉及并发、不改变属性的可见性,也与继承次数无关。

Q5. 关于 _x 与 __x 两种私有写法,说法正确的有?

多选题 · mid

  • A.二者都无法真正拦截访问← 正确答案
  • B.双下划线会发生名称改写← 正确答案
  • C.单下划线表示约定的保护属性← 正确答案
  • D.二者都会触发语法错误报错

答案:ABC。Python 没有真正的私有,_x 是“请勿外传”的君子协定,__x 会被改写成 _类名__x 防误拿,但都可被显式访问。D 错误,因为私有写法是合法语法,不会报错。

Q6. 子类中调用父类方法,推荐写法是?

单选题 · mid

  • A.使用父类的类名调用对应方法
  • B.借助super函数调用父类方法← 正确答案
  • C.构造一个父类对象再去执行
  • D.把父类的代码复制到子类中

答案:B。super() 能自动沿 MRO 找到正确的父类实现,写起来简洁且天然支持多重继承。A 的“父类名。方法”在单继承下能跑但绕路且维护差,C 是错误建模,D 直接制造重复代码。

Q7. Python 鸭子类型的典型特征包括?

多选题 · hard

  • A.关注对象有无对应的方法← 正确答案
  • B.编译期检查类型是否一致
  • C.运行期才判断能否完成调用← 正确答案
  • D.要求必须继承某个指定父类

答案:AC。鸭子类型看的是“有没有这个行为”,而不是“户籍是什么”;Python 不在编译期做类型检查,调用发生在运行期。B 是静态语言特征,D 是 ABC 的强制约束,不是鸭子类型的必要条件。

Q8. 多重继承时,方法查找顺序由什么决定?

单选题 · hard

  • A.由C3线性化算法计算得出← 正确答案
  • B.由子类创建对象的数量决定
  • C.由属性在类中的书写顺序决定
  • D.由父类源文件的行数大小决定

答案:A。Python 用 C3 线性化算法计算 MRO,保证每个父类只出现一次且子类优先。对象数量、书写顺序或文件行数都与查找顺序无关,B/C/D 都是干扰项。

Q9. 关于特殊方法 __len__ 和 __getitem__,说法正确的有?

多选题 · easy

  • A.让自定义类能调用len函数← 正确答案
  • B.让自定义对象支持下标取值← 正确答案
  • C.两者皆是Python的特殊方法← 正确答案
  • D.两者都必须返回字符串

答案:ABC。len(obj) 会去调 __len__,obj[i] 会去调 __getitem__,它们让自定义类接入内置语法。D 错误:__len__ 必须返回整数,__getitem__ 返回任意值。

Q10. 关于 Python 与 Java 的封装差异,正确的说法是?

单选题 · mid

  • A.Python无真正的私有属性关键字← 正确答案
  • B.Java依靠解释器约束成员访问
  • C.Python的私有由编译期强制拦截
  • D.Java与Python的封装机制等同

答案:A。Python 靠命名约定和名称改写表达私有,没有 Java 那种编译器强制的 private 关键字。B 错误,Java 靠编译期而非解释器;C 错误,Python 的私有不是编译期拦截;D 错在两种语言机制并不等同。