【Python】面向对象(三)
目录
- 面向对象
- 抽象
- 接口
- 封装
- 内部类
- 匿名类和匿名对象
面向对象
抽象
什么是抽象(Abstraction)?
抽象是面向对象编程(OOP)的核心原则之一。
简单来说:只暴露必要的信息,隐藏实现细节。
举个现实例子:
- 当你开车时,只需要知道方向盘、油门、刹车的使用方法。
- 你不需要知道发动机内部是如何点火、活塞如何运转的。
这就是抽象:对外提供接口(操作方法),内部细节封装起来。
Python 中的抽象类型
-
数据抽象(Data Abstraction)
- 数据的内部表示对用户是隐藏的。
- 用户通过数据结构(类、对象、方法)访问数据,但无需关心底层实现。
例如:
class BankAccount:def __init__(self, balance):self.__balance = balance # 私有变量(外部无法直接访问)def deposit(self, amount):self.__balance += amountdef withdraw(self, amount):if amount <= self.__balance:self.__balance -= amountelse:print("余额不足")def get_balance(self):return self.__balanceaccount = BankAccount(1000) account.deposit(500) print(account.get_balance()) # ✅ 输出 1500 print(account.__balance) # ❌ AttributeError
用户只知道有
deposit
、withdraw
,但不知道余额是如何存储的。 -
过程抽象(Process Abstraction)
- 隐藏具体的实现过程,只提供统一的接口。
- 用户关心“做什么”,而不是“怎么做”。
例如:
def sort_numbers(nums):return sorted(nums) # 内部用 Timsort 算法,但用户不需要知道print(sort_numbers([5, 2, 9, 1]))
用户只知道
sort_numbers
会排序,但不用关心它内部用的是快速排序还是归并排序。
抽象类(Abstract Class)
在 Python 里:
- 普通类可以直接实例化。
- 抽象类不能直接实例化,只能作为基类(父类)存在。
- 抽象类的目的是:强制子类实现某些方法。
创建抽象类
Python 使用 abc
模块(Abstract Base Class):
from abc import ABC, abstractmethodclass Demo(ABC): # 继承 ABC@abstractmethoddef method1(self): # 抽象方法(必须在子类里实现)passdef method2(self): # 普通方法(子类可直接用)print("concrete method")# obj = Demo() # ❌ 报错:Can't instantiate abstract class
@abstractmethod
:标记方法为抽象方法。
抽象方法的覆盖(Overriding)
子类必须实现父类的抽象方法,否则也不能实例化。
class DemoClass(ABC):@abstractmethoddef method1(self):passdef method2(self):print("concrete method")class ConcreteClass(DemoClass):def method1(self): # ✅ 必须实现print("override method1")obj = ConcreteClass()
obj.method1() # override method1
obj.method2() # concrete method
抽象的意义
- 代码复用:抽象类可以有具体方法,子类可直接使用。
- 统一接口:强制子类实现某些方法,保证风格一致。
- 解耦:调用方只关心抽象接口,而不用关心实现细节。
抽象 vs 封装 vs 多态
- 抽象(Abstraction):隐藏实现,只暴露接口(“做什么”)。
- 封装(Encapsulation):把数据和方法打包在一起,并控制访问权限(“保护细节”)。
- 多态(Polymorphism):不同对象对同一方法调用,表现出不同的行为(“不同实现”)。
区别总结:
- 抽象:告诉你要做什么。
- 封装:保护细节,防止被乱改。
- 多态:不同对象的不同实现。
接口 vs 抽象类
在 Python 里没有“接口”关键字,但可以用抽象类来模拟接口。
- 如果一个抽象类里全是抽象方法,它就相当于接口。
- 如果一个抽象类里既有抽象方法又有具体方法,它就是抽象基类。
例如:
from abc import ABC, abstractmethodclass Drawable(ABC): # 相当于接口@abstractmethoddef draw(self):passclass Circle(Drawable):def draw(self):print("画一个圆")class Rectangle(Drawable):def draw(self):print("画一个矩形")shapes = [Circle(), Rectangle()]
for s in shapes:s.draw()
这样保证所有图形类都有 draw()
方法。
在 Python 中,抽象通过 抽象类 + 抽象方法 实现,本质是“只告诉你能做什么,而不告诉你怎么做”,让代码更清晰、可维护、可扩展。
接口
什么是接口 (Interface)?
在面向对象编程(OOP)中,接口(Interface)是一种约束,它定义了一组方法,但不提供具体实现。
接口的核心思想:
- “只规定要做什么,不规定怎么做”。
- 谁实现了接口,就必须提供接口里声明的方法的具体实现。
比如:
- USB 接口规定了插口的形状、传输协议,但不关心具体是鼠标、键盘还是硬盘。
- 在代码里,接口就是一种“契约”,确保类中必须实现某些方法。
在 Java、Go 等语言里有关键字 interface
,但 Python 没有内置关键字 interface
。
Python 里通过 抽象基类(Abstract Base Class, 简称 ABC) 来实现接口。
接口与抽象类的区别
特点 | 抽象类 (Abstract Class) | 接口 (Interface) |
---|---|---|
方法 | 可以有抽象方法 + 普通方法 | 只能有抽象方法 |
属性 | 可以有属性、构造方法 | 一般只定义方法,不定义属性 |
目的 | 提供部分实现,供子类继承和扩展 | 提供“契约”,强制实现方法 |
Python 实现 | 使用 ABC + @abstractmethod | 同样使用 ABC ,但接口里只写抽象方法 |
总结:
- 抽象类是“半成品”(有一些实现)。
- 接口是“纯规范”(只有约定,不提供实现)。
Python 接口的实现方式
-
正式接口(Formal Interface)
使用
abc
模块中的ABC
和@abstractmethod
。步骤:
- 定义一个继承自
ABC
的类,作为接口。 - 在其中定义抽象方法(使用
@abstractmethod
标记)。 - 任何实现接口的类,必须重写接口里的所有方法,否则无法实例化。
示例:
from abc import ABC, abstractmethod# 定义接口 class DemoInterface(ABC):@abstractmethoddef method1(self):pass@abstractmethoddef method2(self):pass# 实现接口的类 class ConcreteClass(DemoInterface):def method1(self):print("实现了 method1")def method2(self):print("实现了 method2")# 创建对象 obj = ConcreteClass() obj.method1() obj.method2()
输出:
实现了 method1 实现了 method2
特点:如果
ConcreteClass
没有实现method1
或method2
,会报错:TypeError: Can't instantiate abstract class ConcreteClass with abstract method methodX
- 定义一个继承自
-
非正式接口(Informal Interface)
Python 是动态语言,支持 鸭子类型(Duck Typing):
“如果它走路像鸭子,叫声像鸭子,那它就是鸭子”。非正式接口就是:
- 定义一个普通类,里面写一些需要子类实现的方法(可以是空的
pass
)。 - 子类只要写了相应的方法,就相当于“实现”了接口。
- Python 不会强制检查,只要方法存在就能调用。
示例:
# 非正式接口 class DemoInterface:def displayMsg(self):pass# 实现接口 class NewClass(DemoInterface):def displayMsg(self):print("这是我的消息")obj = NewClass() obj.displayMsg()
输出:
这是我的消息
特点:
- 灵活但不严谨:Python 不会强制检查子类是否实现了接口里的方法。
- 优点:简单、快速,符合 Python “鸭子类型”哲学。
- 缺点:容易遗漏实现,运行时才会报错。
- 定义一个普通类,里面写一些需要子类实现的方法(可以是空的
Python 接口的规则
-
接口类不能被实例化。
obj = DemoInterface() # 会报错
-
实现接口的类必须实现接口的所有方法(Formal Interface)。
否则该类本身也必须是抽象类,不能实例化。 -
接口类中的方法必须是抽象方法。
- 抽象类允许有普通方法,但接口一般不允许。
-
非正式接口 不会强制检查实现,依赖程序员自觉。
接口的应用场景
- 插件系统
例如:音频播放器插件接口,所有插件必须实现play()
和stop()
方法。 - 统一规范
比如写一个支付系统,定义一个PaymentInterface
:pay(amount)
refund(transaction_id)
微信支付、支付宝支付、银行卡支付都必须实现这两个方法。
- 解耦合
使用接口编程,可以替换底层实现,而不影响调用代码。
多接口实现(多继承)
Python 支持 多继承,所以一个类可以实现多个接口。
from abc import ABC, abstractmethodclass Interface1(ABC):@abstractmethoddef methodA(self):passclass Interface2(ABC):@abstractmethoddef methodB(self):passclass ConcreteClass(Interface1, Interface2):def methodA(self):print("实现了方法 A")def methodB(self):print("实现了方法 B")obj = ConcreteClass()
obj.methodA()
obj.methodB()
输出:
实现了方法 A
实现了方法 B
封装
什么是封装(Encapsulation)?
封装是面向对象编程(OOP)的三大支柱之一(封装、继承、多态)。
它的核心思想是:
- 把属性(数据)和方法(操作数据的逻辑)绑定在一个单元(类)中;
- 同时 限制外部直接访问对象的内部数据,而是通过类的方法来操作数据。
类比:
你去银行存钱时,并不会直接操作数据库(余额表),而是通过“柜员/ATM”的接口(方法)来完成存取。
这样做的好处:安全、可控,避免外部随意改数据。
Python 封装 vs C++/Java 封装
在 C++/Java 中,类成员可以通过 访问修饰符 来控制访问权限:
public
:公有,任何地方都能访问。protected
:受保护,只能在类和子类中访问。private
:私有,只能在类内部访问。
但是 Python 没有真正的访问修饰符关键字。
在 Python 里:
- 默认所有变量和方法都是 公有的(public)。
- 通过 命名约定(前缀
_
或__
)来模拟访问控制。
Python 的封装实现
-
公有成员(Public Members)
默认情况下,类里的变量和方法都是公有的,可以直接访问:
class Student:def __init__(self, name="Rajaram", marks=50):self.name = name # 公有属性self.marks = marks # 公有属性s1 = Student() s2 = Student("Bharat", 25)print(s1.name, s1.marks) # ✅ 可直接访问 print(s2.name, s2.marks)
输出:
Rajaram 50 Bharat 25
缺点:外部可以随便修改
marks
,破坏了封装性。 -
受保护成员(Protected Members)
在 Python 中,约定俗成:
- 属性或方法名前加一个下划线
_
,表示“受保护成员”。 - 外部仍然可以访问,但程序员约定上应该把它当作“半私有”,不要随意访问。
class Student:def __init__(self, name="Rajaram", marks=50):self._name = name # 受保护属性self._marks = marks # 受保护属性s = Student() print(s._name) # ✅ 能访问,但不推荐
- 属性或方法名前加一个下划线
-
私有成员(Private Members)
在 Python 中:
- 属性或方法名前加两个下划线
__
,表示“私有成员”。 - Python 会进行 名称重整(Name Mangling),使得外部不能直接访问。
class Student:def __init__(self, name="Rajaram", marks=50):self.__name = name # 私有属性self.__marks = marks # 私有属性def studentdata(self):print(f"Name: {self.__name}, Marks: {self.__marks}")s1 = Student() s1.studentdata() # ✅ 内部方法可以访问print(s1.__name) # ❌ AttributeError: 'Student' object has no attribute '__name'
外部无法直接访问
__name
或__marks
,实现了数据隐藏。 - 属性或方法名前加两个下划线
Name Mangling(名字改编)
虽然 Python 把 __变量名
处理成私有的,但其实它只是改了名字。
- 内部会变成
_类名__变量名
。 - 所以理论上你依然可以访问,但不推荐这么做。
print(s1._Student__name) # ✅ Rajaram
print(s1._Student__marks) # ✅ 50
Python 的理念:信任程序员,不做强制限制,只靠约定。
不像 Java 那样“硬性禁止”,Python 更灵活,但也要求程序员自律。
方法的封装
不仅属性可以封装,方法也可以封装。
- 公有方法(默认):对外暴露接口。
- 受保护方法:约定只在类和子类中使用。
- 私有方法:只能在类内部调用。
class Student:def __init__(self, name):self.__name = namedef display(self): # 公有方法self.__show_name() # 内部调用私有方法def _protected_method(self):print("受保护的方法")def __show_name(self): # 私有方法print(f"Name: {self.__name}")s = Student("Alice")
s.display() # ✅ Name: Alice
s._protected_method() # ⚠️ 可访问,但不推荐
s.__show_name() # ❌ AttributeError
封装的意义
- 数据安全:防止外部随意篡改对象的属性。
- 抽象接口:对外只暴露必要的方法(API),内部逻辑隐藏。
- 易维护:修改内部实现时,外部调用方式不受影响。
- 模块化:把数据和操作封装在一起,更符合 OOP 思想。
封装 + Getter/Setter
在 Python 中,如果你想控制属性的读取/修改,可以使用 属性方法(property):
class Student:def __init__(self, name, marks):self.__name = nameself.__marks = marks@propertydef name(self): # getterreturn self.__name@name.setterdef name(self, value): # setterif len(value) > 0:self.__name = valueelse:print("名字不能为空")s = Student("Tom", 90)
print(s.name) # ✅ Tom
s.name = "Jerry" # ✅ 修改
print(s.name) # ✅ Jerry
s.name = "" # ❌ 名字不能为空
用 @property
可以把方法当成属性调用,实现优雅的封装。
Python 的封装并不像 Java/C++ 那样“硬性约束”,而是通过 命名约定 + name mangling 来实现,更灵活、更“成人化”,它相信程序员不会乱来。
内部类
什么是 Inner Class(内部类)
在 Python 中,一个类定义在另一个类里面,就叫 内部类(Inner Class),也叫 嵌套类(Nested Class)。
特点:
- 内部类的对象通常作为外部类的属性存在。
- 内部类拥有外部类的作用域,可以更方便地表示“某些类与外部类紧密相关”。
- 从组织结构上,内部类让代码更清晰(属于某个整体的类被放在一起)。
基本语法
class Outer:def __init__(self):passclass Inner:def __init__(self):pass
这里:
Outer
是外部类(Outer Class)。Inner
是定义在外部类里的内部类(Inner Class)。
示例
class Student:def __init__(self):self.name = "Ashish"self.subs = self.Subjects() # 内部类的实例,作为外部类的属性def show(self):print("Name:", self.name)self.subs.display()# 定义内部类class Subjects:def __init__(self):self.sub1 = "Physics"self.sub2 = "Chemistry"def display(self):print("Subjects:", self.sub1, self.sub2)s1 = Student()
s1.show()
输出:
Name: Ashish
Subjects: Physics Chemistry
这里 Subjects
内部类 变成了 Student 的一个属性,所以 s1.show()
调用时,也能顺便访问 Subjects
的内容。
除了通过外部类属性访问,你也可以 独立创建内部类对象:
Student().Subjects().display()
输出:
Subjects: Physics Chemistry
注意:这种方式等价于 Outer.Inner()
,语法是 外部类名.内部类名()
。
为什么要用内部类?
- 逻辑分组:
内部类表示“从属于外部类的结构”,例如:学生(外部类)和科目(内部类)。 - 作用域隔离:
内部类的作用范围被限制在外部类中,不会轻易被外部其他代码引用。 - 结构清晰:
比如在组织(Organization)里定义部门(Department),部门里再定义小组(Team),结构一目了然。
内部类的类型
-
Multiple Inner Class(多个内部类)
一个外部类里可以有多个互不干扰的内部类。
class Organization:def __init__(self):self.dep1 = self.Department1()self.dep2 = self.Department2()def showName(self):print("Organization Name: Tutorials Point")class Department1:def display(self):print("In Department 1")class Department2:def display(self):print("In Department 2")org = Organization() org.showName() org.dep1.display() org.dep2.display()
输出:
Organization Name: Tutorials Point In Department 1 In Department 2
外部类
Organization
相当于“容器”,内部类Department1
、Department2
表示不同的部门。 -
Multilevel Inner Class(多级嵌套类)
内部类里还可以再定义内部类 → 多层嵌套。
class Organization:def __init__(self):self.dep = self.Department()def showName(self):print("Organization Name: Tutorials Point")class Department:def __init__(self):self.team = self.Team1()def displayDep(self):print("In Department")class Team1:def displayTeam(self):print("Team 1 of the department")org = Organization() org.showName() org.dep.displayDep() org.dep.team.displayTeam()
输出:
Organization Name: Tutorials Point In Department Team 1 of the department
层级结构:
Organization
(组织)Department
(部门)Team1
(小组)
非常适合描述 树形/层级关系 的场景。
内部类 vs 普通类
特性 | 内部类 | 普通类 |
---|---|---|
定义位置 | 外部类内部 | 顶层 |
作用域 | 外部类作用域内 | 全局 |
访问方式 | Outer.Inner() | 直接用类名 |
用途 | 表示逻辑上隶属于外部类的结构 | 独立存在 |
注意事项
- 内部类并不会自动继承外部类,只是写在里面,属于“包含”关系,而不是“继承”。
- 如果要访问外部类的属性,需要通过外部类的实例引用。
- 内部类也可以单独实例化,不一定非要依附外部类。
匿名类和匿名对象
type()
函数
在 Python 里,一切皆对象。类本身也是对象,类的“类”就是 type
。
class MyClass:def __init__(self):self.myvar = 10obj = MyClass()print("class of int:", type(int))
print("class of list:", type(list))
print("class of dict:", type(dict))
print("class of MyClass:", type(MyClass))
print("class of obj:", type(obj))
输出:
class of int <class 'type'>
class of list <class 'type'>
class of dict <class 'type'>
class of MyClass <class 'type'>
class of obj <class '__main__.MyClass'>
说明:
- 内置类(
int
、list
、dict
)和用户自定义类(MyClass
)的类型都是type
。 obj
是MyClass
的实例,所以type(obj)
显示<class '__main__.MyClass'>
。
type()
的三参数形式
type()
除了能查看对象类型,还能 动态创建类。
语法:
newclass = type(name, bases, dict)
参数解释:
- name:新类的名字(字符串),会成为类的
__name__
属性。 - bases:父类的元组(可空,默认继承
object
)。 - dict:类的命名空间字典(属性、方法)。
示例:动态创建类
MyDynamicClass = type("MyDynamicClass", # 类名(object,), # 父类{"x": 10, "show": lambda self: print("x =", self.x)} # 属性 & 方法
)obj = MyDynamicClass()
obj.show()
输出:
x = 10
这就相当于写了:
class MyDynamicClass(object):x = 10def show(self):print("x =", self.x)
匿名类(Anonymous Class)
所谓“匿名类”,就是用 type()
创建时不给类起名字。
anon = type('', (object,), {})
''
→ 类名为空字符串(匿名)。(object,)
→ 继承object
。{}
→ 目前没有属性或方法。
匿名对象(Anonymous Object)
创建匿名类的实例:
anon = type('', (object,), {})
obj = anon()
print("type of obj:", type(obj))
输出:
type of obj: <class '__main__.'>
可以看到类名是空的 → 匿名类,obj
就是匿名对象。
匿名类 + 动态属性 & 方法
匿名类强大之处在于 可以直接定义属性和方法,不用写 class
关键字。
def getA(self):return self.aobj = type('', # 匿名(object,), # 继承 object{'a': 5, 'b': 6, 'c': 7,'getA': getA, # 普通函数'getB': lambda self: self.b # lambda 匿名函数}
)()print(obj.getA(), obj.getB())
输出:
5 6
等价于:
class _:a = 5b = 6c = 7def getA(self): return self.adef getB(self): return self.bobj = _()
为什么用匿名类?
- 快速定义小类,避免写一大堆
class
模板。 - 动态生成类,适合框架/库开发(如 ORM、元类编程、动态代理)。
- 数据临时封装,像结构体一样快速组合数据和方法。
匿名类 vs 普通类
特性 | 匿名类 | 普通类 |
---|---|---|
定义方式 | type('', bases, dict) | class MyClass: ... |
是否有名字 | 没有(__name__='' ) | 有名字 |
适用场景 | 临时快速用 | 长期维护、清晰结构 |
可读性 | 差(别人不容易理解) | 好(常规方式) |