面向对象是 Python 入门里最容易被讲玄的地方。
类、对象、封装、继承、多态、鸭子类型,概念一个接一个,看起来很高级,但很多初学者读完还是不知道什么时候该用。
其实可以先把问题想简单。
当你的程序里出现了稳定的"东西",比如用户、图书、订单、电影、角色,而且这个东西既有数据,又有行为,就可以考虑用类。
为什么需要类
先用字典表示一本书:
python
book = {
"title": "Python 入门",
"author": "课程组",
"is_borrowed": False
}
这没问题。
但如果我们还要处理借书、还书、判断是否可借,逻辑就会散在外面。
python
if book["is_borrowed"]:
print("这本书已经被借走了")
else:
book["is_borrowed"] = True
print("借阅成功")
如果到处都这样写,状态很容易乱。
用类可以把数据和操作数据的方法放在一起。
python
class Book:
def __init__(self, title: str, author: str):
self.title = title
self.author = author
self.is_borrowed = False
def borrow(self) -> bool:
if self.is_borrowed:
return False
self.is_borrowed = True
return True
类定义结构。
对象保存具体数据。
类和对象
python
book_a = Book("Python 入门", "课程组")
book_b = Book("数据分析基础", "课程组")
print(book_a.title)
print(book_b.title)
Book 是类。
book_a 和 book_b 是对象。
它们来自同一个类,但各自保存自己的属性。
关系可以画成这样:
#mermaid-svg-wlKhwACBWr64hEw0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wlKhwACBWr64hEw0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wlKhwACBWr64hEw0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wlKhwACBWr64hEw0 .error-icon{fill:#552222;}#mermaid-svg-wlKhwACBWr64hEw0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wlKhwACBWr64hEw0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wlKhwACBWr64hEw0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wlKhwACBWr64hEw0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wlKhwACBWr64hEw0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wlKhwACBWr64hEw0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wlKhwACBWr64hEw0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wlKhwACBWr64hEw0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wlKhwACBWr64hEw0 .marker.cross{stroke:#333333;}#mermaid-svg-wlKhwACBWr64hEw0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wlKhwACBWr64hEw0 p{margin:0;}#mermaid-svg-wlKhwACBWr64hEw0 g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-wlKhwACBWr64hEw0 g.classGroup text .title{font-weight:bolder;}#mermaid-svg-wlKhwACBWr64hEw0 .cluster-label text{fill:#333;}#mermaid-svg-wlKhwACBWr64hEw0 .cluster-label span{color:#333;}#mermaid-svg-wlKhwACBWr64hEw0 .cluster-label span p{background-color:transparent;}#mermaid-svg-wlKhwACBWr64hEw0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wlKhwACBWr64hEw0 .cluster text{fill:#333;}#mermaid-svg-wlKhwACBWr64hEw0 .cluster span{color:#333;}#mermaid-svg-wlKhwACBWr64hEw0 .nodeLabel,#mermaid-svg-wlKhwACBWr64hEw0 .edgeLabel{color:#131300;}#mermaid-svg-wlKhwACBWr64hEw0 .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-wlKhwACBWr64hEw0 .label text{fill:#131300;}#mermaid-svg-wlKhwACBWr64hEw0 .labelBkg{background:#ECECFF;}#mermaid-svg-wlKhwACBWr64hEw0 .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-wlKhwACBWr64hEw0 .classTitle{font-weight:bolder;}#mermaid-svg-wlKhwACBWr64hEw0 .node rect,#mermaid-svg-wlKhwACBWr64hEw0 .node circle,#mermaid-svg-wlKhwACBWr64hEw0 .node ellipse,#mermaid-svg-wlKhwACBWr64hEw0 .node polygon,#mermaid-svg-wlKhwACBWr64hEw0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wlKhwACBWr64hEw0 .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-wlKhwACBWr64hEw0 g.clickable{cursor:pointer;}#mermaid-svg-wlKhwACBWr64hEw0 g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-wlKhwACBWr64hEw0 g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-wlKhwACBWr64hEw0 .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-wlKhwACBWr64hEw0 .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-wlKhwACBWr64hEw0 .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-wlKhwACBWr64hEw0 .dashed-line{stroke-dasharray:3;}#mermaid-svg-wlKhwACBWr64hEw0 .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-wlKhwACBWr64hEw0 #compositionStart,#mermaid-svg-wlKhwACBWr64hEw0 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-wlKhwACBWr64hEw0 #compositionEnd,#mermaid-svg-wlKhwACBWr64hEw0 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-wlKhwACBWr64hEw0 #dependencyStart,#mermaid-svg-wlKhwACBWr64hEw0 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-wlKhwACBWr64hEw0 #dependencyStart,#mermaid-svg-wlKhwACBWr64hEw0 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-wlKhwACBWr64hEw0 #extensionStart,#mermaid-svg-wlKhwACBWr64hEw0 .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-wlKhwACBWr64hEw0 #extensionEnd,#mermaid-svg-wlKhwACBWr64hEw0 .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-wlKhwACBWr64hEw0 #aggregationStart,#mermaid-svg-wlKhwACBWr64hEw0 .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-wlKhwACBWr64hEw0 #aggregationEnd,#mermaid-svg-wlKhwACBWr64hEw0 .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-wlKhwACBWr64hEw0 #lollipopStart,#mermaid-svg-wlKhwACBWr64hEw0 .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-wlKhwACBWr64hEw0 #lollipopEnd,#mermaid-svg-wlKhwACBWr64hEw0 .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-wlKhwACBWr64hEw0 .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-wlKhwACBWr64hEw0 .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wlKhwACBWr64hEw0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wlKhwACBWr64hEw0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wlKhwACBWr64hEw0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Book
+title
+author
+is_borrowed
+borrow()
+return_book()
book_a
book_b
Mermaid 里这样画只是为了帮助理解,真实代码里对象不是用继承箭头创建的。
重点是,类是一套模板,对象是具体实例。
__init__ 和 self
__init__ 是初始化方法。
创建对象时自动执行。
python
class Student:
def __init__(self, name: str, score: float):
self.name = name
self.score = score
student = Student("小明", 88)
print(student.name)
print(student.score)
self 表示当前对象自己。
当你写:
python
self.name = name
意思是,把传进来的 name 保存到当前对象的 name 属性上。
每个对象都有自己的 self。
实例属性和类属性
实例属性属于某个具体对象:
python
class Student:
def __init__(self, name: str):
self.name = name
类属性属于类本身,多个对象共享:
python
class Student:
school = "Python 学院"
def __init__(self, name: str):
self.name = name
使用:
python
student_a = Student("小明")
student_b = Student("小红")
print(student_a.school)
print(student_b.school)
类属性适合保存所有对象共享的信息。
但不要把可变列表随便写成类属性:
python
class Team:
members = []
这会让多个对象共享同一个列表,容易出问题。
更安全:
python
class Team:
def __init__(self):
self.members = []
封装,不是把代码藏起来
封装的核心是边界清楚。
外部调用对象的方法,不直接乱改内部状态。
python
class Book:
def __init__(self, title: str, author: str):
self.title = title
self.author = author
self.is_borrowed = False
def borrow(self) -> str:
if self.is_borrowed:
return "这本书已经被借走了"
self.is_borrowed = True
return "借阅成功"
def return_book(self) -> str:
if not self.is_borrowed:
return "这本书还没有被借走"
self.is_borrowed = False
return "还书成功"
调用:
python
book = Book("Python 入门", "课程组")
print(book.borrow())
print(book.return_book())
外部不需要知道 is_borrowed 怎么变化。
它只需要调用 borrow() 和 return_book()。
Python 没有强制私有属性,但约定 _name 这种前导下划线表示内部使用。
python
class Account:
def __init__(self):
self._balance = 0
这不是绝对禁止访问,而是在提醒别人,这个属性不应该随便从外部改。
继承,表达"是一种"的关系
python
class User:
def __init__(self, name: str):
self.name = name
def login(self) -> str:
return f"{self.name} 登录成功"
class Admin(User):
def add_book(self, title: str) -> str:
return f"管理员 {self.name} 添加了图书:{title}"
使用:
python
admin = Admin("小王")
print(admin.login())
print(admin.add_book("Python 入门"))
Admin 继承了 User,所以可以使用 login()。
继承适合表达"管理员是一种用户"。
不要为了复用两行代码就继承。继承表达的是概念关系,不只是代码复用。
方法重写
子类可以重写父类方法。
python
class User:
def role(self) -> str:
return "普通用户"
class Admin(User):
def role(self) -> str:
return "管理员"
print(User().role())
print(Admin().role())
同样的方法名,不同类给出不同实现。
这为多态打基础。
多态和鸭子类型
多态就是不同对象可以用同一种方式调用。
python
class EmailSender:
def send(self, message: str) -> None:
print(f"发送邮件:{message}")
class SmsSender:
def send(self, message: str) -> None:
print(f"发送短信:{message}")
def notify(sender, message: str) -> None:
sender.send(message)
notify(EmailSender(), "借书成功")
notify(SmsSender(), "借书成功")
notify() 不关心 sender 是邮件发送器还是短信发送器。
它只关心对象有没有 send() 方法。
这就是 Python 常说的鸭子类型。
组合优于继承
继承不是唯一方式。
很多时候,组合更清楚。
图书馆拥有很多书,这不是继承关系。
图书馆不是一种书。
所以应该组合:
python
class Library:
def __init__(self):
self.books: list[Book] = []
def add_book(self, book: Book) -> None:
self.books.append(book)
判断标准:
如果是"是一种",考虑继承。
如果是"拥有一个",考虑组合。
完整案例,图书管理系统
python
class Book:
def __init__(self, title: str, author: str):
self.title = title
self.author = author
self.is_borrowed = False
def borrow(self) -> bool:
if self.is_borrowed:
return False
self.is_borrowed = True
return True
def return_book(self) -> bool:
if not self.is_borrowed:
return False
self.is_borrowed = False
return True
class Library:
def __init__(self):
self.books: list[Book] = []
def add_book(self, book: Book) -> None:
self.books.append(book)
def find_book(self, title: str) -> Book | None:
for book in self.books:
if book.title == title:
return book
return None
def list_books(self) -> None:
for book in self.books:
status = "已借出" if book.is_borrowed else "可借"
print(f"{book.title},{book.author},{status}")
library = Library()
library.add_book(Book("Python 入门", "课程组"))
library.add_book(Book("数据分析基础", "课程组"))
book = library.find_book("Python 入门")
if book and book.borrow():
print("借阅成功")
else:
print("借阅失败")
library.list_books()
对象关系:
#mermaid-svg-27Q2akzf5d0gG5HD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-27Q2akzf5d0gG5HD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-27Q2akzf5d0gG5HD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-27Q2akzf5d0gG5HD .error-icon{fill:#552222;}#mermaid-svg-27Q2akzf5d0gG5HD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-27Q2akzf5d0gG5HD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-27Q2akzf5d0gG5HD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-27Q2akzf5d0gG5HD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-27Q2akzf5d0gG5HD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-27Q2akzf5d0gG5HD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-27Q2akzf5d0gG5HD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-27Q2akzf5d0gG5HD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-27Q2akzf5d0gG5HD .marker.cross{stroke:#333333;}#mermaid-svg-27Q2akzf5d0gG5HD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-27Q2akzf5d0gG5HD p{margin:0;}#mermaid-svg-27Q2akzf5d0gG5HD g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-27Q2akzf5d0gG5HD g.classGroup text .title{font-weight:bolder;}#mermaid-svg-27Q2akzf5d0gG5HD .cluster-label text{fill:#333;}#mermaid-svg-27Q2akzf5d0gG5HD .cluster-label span{color:#333;}#mermaid-svg-27Q2akzf5d0gG5HD .cluster-label span p{background-color:transparent;}#mermaid-svg-27Q2akzf5d0gG5HD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-27Q2akzf5d0gG5HD .cluster text{fill:#333;}#mermaid-svg-27Q2akzf5d0gG5HD .cluster span{color:#333;}#mermaid-svg-27Q2akzf5d0gG5HD .nodeLabel,#mermaid-svg-27Q2akzf5d0gG5HD .edgeLabel{color:#131300;}#mermaid-svg-27Q2akzf5d0gG5HD .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-27Q2akzf5d0gG5HD .label text{fill:#131300;}#mermaid-svg-27Q2akzf5d0gG5HD .labelBkg{background:#ECECFF;}#mermaid-svg-27Q2akzf5d0gG5HD .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-27Q2akzf5d0gG5HD .classTitle{font-weight:bolder;}#mermaid-svg-27Q2akzf5d0gG5HD .node rect,#mermaid-svg-27Q2akzf5d0gG5HD .node circle,#mermaid-svg-27Q2akzf5d0gG5HD .node ellipse,#mermaid-svg-27Q2akzf5d0gG5HD .node polygon,#mermaid-svg-27Q2akzf5d0gG5HD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-27Q2akzf5d0gG5HD .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-27Q2akzf5d0gG5HD g.clickable{cursor:pointer;}#mermaid-svg-27Q2akzf5d0gG5HD g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-27Q2akzf5d0gG5HD g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-27Q2akzf5d0gG5HD .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-27Q2akzf5d0gG5HD .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-27Q2akzf5d0gG5HD .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-27Q2akzf5d0gG5HD .dashed-line{stroke-dasharray:3;}#mermaid-svg-27Q2akzf5d0gG5HD .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-27Q2akzf5d0gG5HD #compositionStart,#mermaid-svg-27Q2akzf5d0gG5HD .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-27Q2akzf5d0gG5HD #compositionEnd,#mermaid-svg-27Q2akzf5d0gG5HD .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-27Q2akzf5d0gG5HD #dependencyStart,#mermaid-svg-27Q2akzf5d0gG5HD .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-27Q2akzf5d0gG5HD #dependencyStart,#mermaid-svg-27Q2akzf5d0gG5HD .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-27Q2akzf5d0gG5HD #extensionStart,#mermaid-svg-27Q2akzf5d0gG5HD .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-27Q2akzf5d0gG5HD #extensionEnd,#mermaid-svg-27Q2akzf5d0gG5HD .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-27Q2akzf5d0gG5HD #aggregationStart,#mermaid-svg-27Q2akzf5d0gG5HD .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-27Q2akzf5d0gG5HD #aggregationEnd,#mermaid-svg-27Q2akzf5d0gG5HD .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-27Q2akzf5d0gG5HD #lollipopStart,#mermaid-svg-27Q2akzf5d0gG5HD .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-27Q2akzf5d0gG5HD #lollipopEnd,#mermaid-svg-27Q2akzf5d0gG5HD .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-27Q2akzf5d0gG5HD .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-27Q2akzf5d0gG5HD .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-27Q2akzf5d0gG5HD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-27Q2akzf5d0gG5HD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-27Q2akzf5d0gG5HD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} manages
1
*
Library
+books
+add_book(book)
+find_book(title)
+list_books()
Book
+title
+author
+is_borrowed
+borrow()
+return_book()
Library 管理一批 Book。
Book 自己负责借阅状态。
状态归属清楚,代码才不乱。
常见错误
忘记 self
text
class Student:
def introduce():
print("hello")
实例方法第一个参数应该是 self。
把实例属性写成类属性
如果每个对象都应该有自己的列表,放进 __init__。
过度继承
继承层级太深,代码会难以理解。
入门阶段优先使用简单类和组合。
类没有状态
如果一个类只是函数集合,没有保存任何状态,可能模块函数就够了。
练习
扩展图书管理系统:
- 增加
Member类,表示会员。 - 会员可以借多本书。
- 借书成功后,把书加入会员的借阅列表。
提示结构:
python
class Member:
def __init__(self, name: str):
self.name = name
self.borrowed_books: list[Book] = []
def borrow_book(self, book: Book) -> None:
if book.borrow():
self.borrowed_books.append(book)
参考资料
- Python 官方类教程:https://docs.python.org/3/tutorial/classes.html