卡码笔记-最强八股文
首页
计算机基础
Python
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
大模型Github (opens new window)
代码随想录 (opens new window)
首页
计算机基础
Python
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
大模型Github (opens new window)
代码随想录 (opens new window)
  • 本栏必读

    • Python面试题专栏介绍
    • Python学习路线
    • Python零基础入门课
  • 基础语法

  • 函数、作用域与模块

  • 面向对象

    • Python中的类和对象是什么?
    • 实例属性和类属性有什么区别?
    • 实例方法、类方法和静态方法有什么区别?
    • __new__和__init__有什么区别?
    • Python如何实现封装、继承和多态?
      • 简要回答
      • 详细回答
      • 知识拓展
    • Python多继承的MRO是什么?super()如何工作?
    • Python常见魔术方法有哪些?
  • 进阶语法与语言机制

# Python如何实现封装、继承和多态?

面试官问:"Python如何实现封装、继承和多态?"

这是面向对象的经典三问,很多人会背定义,但说不清Python的实现细节。真正要讲清楚的是:Python的封装靠命名约定而非访问控制符,继承支持单继承和多继承,多态依赖鸭子类型而不强制类型检查。 Python的面向对象比Java、C++更灵活,但也更容易误用。

# 简要回答

封装是把数据和操作数据的方法放在一起,隐藏内部实现细节。Python通过单下划线_attr表示"内部使用",双下划线__attr触发名称改写(name mangling),但没有真正的私有属性。

继承是子类获得父类的属性和方法。Python用class Child(Parent)实现单继承,用class Child(Parent1, Parent2)实现多继承。子类可以重写父类方法,通过super()调用父类方法。

多态是不同类的对象可以响应相同的方法调用。Python采用鸭子类型(duck typing):"如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。" 对象不需要继承共同父类,只要有同名方法就能多态调用。

# 详细回答

# 封装

封装的目的是隐藏实现细节,只暴露必要的接口。 Python没有private、protected关键字,而是用命名约定:

  • 公开属性:普通命名,如self.name,任何地方都能访问。
  • 内部使用:单下划线开头,如self._internal,表示"这是内部实现,外部不应直接使用",但技术上仍可访问。
  • 名称改写:双下划线开头(且不以双下划线结尾),如self.__private,Python会改写成_ClassName__private,外部直接访问obj.__private会报错。
class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # 名称改写
    
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
    
    def get_balance(self):
        return self.__balance

account = BankAccount(1000)
account.deposit(500)
print(account.get_balance())  # 1500

# 直接访问会报错
# print(account.__balance)  # AttributeError

# 但可以通过改写后的名字访问(不推荐)
print(account._BankAccount__balance)  # 1500
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

名称改写不是真正的私有。 它只是把__balance改成了_BankAccount__balance,防止子类意外覆盖父类的私有属性。Python哲学是"我们都是成年人",不强制隐藏,但约定俗成地尊重单下划线和双下划线的语义。

# 继承

子类继承父类的所有属性和方法:

class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return "Some sound"

class Dog(Animal):
    def speak(self):  # 重写父类方法
        return f"{self.name} says: Woof!"

dog = Dog("Buddy")
print(dog.name)    # Buddy,继承了父类的__init__
print(dog.speak()) # Buddy says: Woof!,使用子类的speak
1
2
3
4
5
6
7
8
9
10
11
12
13
14

super()用于调用父类方法:

class Animal:
    def __init__(self, name):
        self.name = name

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # 调用父类的__init__
        self.breed = breed

dog = Dog("Buddy", "Golden Retriever")
print(dog.name)   # Buddy
print(dog.breed)  # Golden Retriever
1
2
3
4
5
6
7
8
9
10
11
12

不用super()也可以直接调用父类方法,但在多继承时会出问题,super()会按MRO(方法解析顺序)调用,避免重复调用。

Python支持多继承:

class Flyable:
    def fly(self):
        return "Flying"

class Swimmable:
    def swim(self):
        return "Swimming"

class Duck(Flyable, Swimmable):
    pass

duck = Duck()
print(duck.fly())   # Flying
print(duck.swim())  # Swimming
1
2
3
4
5
6
7
8
9
10
11
12
13
14

多继承时,方法查找按MRO顺序。如果多个父类有同名方法,第一个父类的方法会被调用。详细见下一篇《Python多继承的MRO是什么?》。

# 多态

Python的多态不依赖继承关系,只要对象有同名方法,就能多态调用:

class Dog:
    def speak(self):
        return "Woof!"

class Cat:
    def speak(self):
        return "Meow!"

class Car:
    def speak(self):
        return "Beep!"

def make_sound(obj):
    print(obj.speak())  # 不关心obj是什么类型

make_sound(Dog())  # Woof!
make_sound(Cat())  # Meow!
make_sound(Car())  # Beep!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

make_sound不检查对象类型,只要对象有speak方法就行。这就是鸭子类型:"如果它有speak方法,我就能调用它的speak。"

与静态类型语言的区别:

在Java或C++中,多态通常依赖继承和接口。你需要定义一个Animal接口或父类,Dog和Cat都继承它,函数参数声明为Animal类型。

Python不需要这样。函数参数不声明类型,运行时只要对象有对应的方法,就能调用。这种灵活性很强大,但也容易出错:如果传入的对象没有speak方法,会在运行时报AttributeError。

封装、继承、多态的实现机制

# 鸭子类型与协议

Python 3.8+引入了typing.Protocol,可以显式声明鸭子类型的契约:

from typing import Protocol

class Speakable(Protocol):
    def speak(self) -> str:
        ...

def make_sound(obj: Speakable):
    print(obj.speak())

class Dog:
    def speak(self) -> str:
        return "Woof!"

make_sound(Dog())  # 类型检查通过,即使Dog没有继承Speakable
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Protocol是静态类型检查工具(mypy、Pyright)用的,运行时不强制。它让鸭子类型有了显式的接口定义,但不改变Python的动态特性。

# 知识拓展

Q1:Python有没有真正的私有属性?

没有。双下划线只是名称改写,仍然可以通过_ClassName__attr访问。Python认为"私有"是约定,不是语言强制的。如果你非要访问内部属性,你承担后果。

Q2:单下划线和双下划线什么时候用?

  • 单下划线:表示"这是内部实现,外部不应直接用"。如self._cache、def _helper()。
  • 双下划线:避免子类意外覆盖父类的同名属性。如果你的类可能被继承,且有些属性绝对不能被子类覆盖,用双下划线。

大多数情况用单下划线就够了。双下划线只在你明确需要名称改写时用。

Q3:为什么Python多态不需要继承?

因为Python是动态类型语言,运行时才查找方法。调用obj.method()时,Python在obj的类和父类里查找method,找到就调用,不管obj是什么类型。

静态类型语言需要在编译时确定方法,所以需要共同的父类或接口。Python没有这个限制。

Q4:多继承会有什么问题?

最大的问题是钻石继承(菱形继承):

class A:
    def method(self):
        print("A")

class B(A):
    def method(self):
        print("B")

class C(A):
    def method(self):
        print("C")

class D(B, C):
    pass

d = D()
d.method()  # 输出什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Python按MRO解决:D -> B -> C -> A。d.method()会调用B.method()。但如果继承关系复杂,MRO可能不符合直觉,所以多继承要谨慎使用。

Q5:能不能在子类里调用爷爷类的方法,跳过父类?

技术上可以,但不推荐。如果一定要,可以显式调用:

class A:
    def method(self):
        print("A")

class B(A):
    def method(self):
        print("B")

class C(B):
    def method(self):
        A.method(self)  # 跳过B,直接调用A

C().method()  # A
1
2
3
4
5
6
7
8
9
10
11
12
13

但这会破坏继承链,如果有多继承或MRO变化,可能出bug。正确做法是每个类都用super()调用下一个,让MRO自然解析。


上一篇:__new__和__init__有什么区别?
下一篇:Python多继承的MRO是什么?super()如何工作?

Last Updated: 10/13/2026, 5:57:00 PM

← __new__和__init__有什么区别? Python多继承的MRO是什么?super()如何工作? →

评论

验证登录状态...

侧边栏 侧边栏
夜间模式 夜间
卡码简历 卡码简历
代码随想录 代码随想录
卡码投递表 卡码投递表🔥
2026实习校招群 2026群
添加客服微信 2026实习校招客服微信 PS:通过微信后,请发送姓名-学校-年级-2026实习/校招
支持卡码笔记 支持卡码笔记
鼓励/支持/赞赏Carl 卡码笔记赞赏码
1. 如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪