文章目录
- 1. 什么是类 (Class)?
- 1.1 定义一个类
- 2. 什么是对象 (Object) 或实例 (Instance)?
- 2.1 创建对象(实例化)
- 3. 访问属性和调用方法
- 4. 类属性 vs 实例属性
- 5. `self` 的重要性
- 总结
- 练习题
- 练习题答案
前几篇文章我们学习了变量、数据类型、控制流、函数、模块和包。这些都是编写程序的基础,但当程序规模越来越大、逻辑越来越复杂时,我们就需要一种更强大的方式来组织代码和数据。这就是 面向对象编程 (Object-Oriented Programming, OOP) 的核心—— 类 (Class) 和 对象 (Object)。
面向对象编程是一种编程范式,它将数据(属性)和操作数据的方法(行为)封装在一起,形成一个独立的单元。这使得代码更具模块化、可重用性高,并且易于维护。
1. 什么是类 (Class)?
在现实世界中,我们有各种各样的“事物”:汽车、人、动物、书籍等等。它们都有一些共同的特征(比如颜色、大小)和一些能做的事情(比如移动、说话)。
在 Python 中,类就是这样一种“事物”的蓝图 (Blueprint) 或模板 (Template)。它定义了某一类事物的共同属性(数据)和行为(函数)。类本身并不是真实存在的“事物”,它只是一个抽象的定义。
例如,我们可以定义一个 Car
类,它描述了所有汽车共同的特征(如品牌、型号、颜色)和行为(如启动、停止、加速)。
1.1 定义一个类
在 Python 中,使用 class
关键字来定义一个类。
class Car: # 类名通常使用驼峰命名法 (CamelCase),首字母大写"""这是一个 Car 类,用于表示汽车。它定义了汽车的属性和行为。"""pass # pass 语句表示一个空的类体,什么也不做,但结构完整# 定义一个更具体的 Car 类
class Car:"""定义一个简单的汽车类"""# 类属性 (Class Attributes): 属于类本身,所有实例共享的属性# 例如,所有 Car 对象都会有 4 个轮子wheels = 4# 实例方法 (Instance Methods): 属于对象的方法# __init__ 是一个特殊方法,称为构造器或初始化方法# 当创建一个 Car 类的对象(实例)时,这个方法会被自动调用# self 参数是必须的,它指向当前正在创建的对象本身def __init__(self, brand, model, color):"""初始化 Car 对象。Args:brand (str): 汽车品牌。model (str): 汽车型号。color (str): 汽车颜色。"""# 实例属性 (Instance Attributes): 属于每个对象(实例)特有的属性# 每个 Car 对象都会有自己的 brand, model, colorself.brand = brandself.model = modelself.color = colorself.is_running = False # 初始状态为未启动def start(self):"""启动汽车。"""if not self.is_running:self.is_running = Trueprint(f"{self.brand} {self.model} 已启动。")else:print(f"{self.brand} {self.model} 已经在运行了。")def stop(self):"""停止汽车。"""if self.is_running:self.is_running = Falseprint(f"{self.brand} {self.model} 已停止。")else:print(f"{self.brand} {self.model} 已经停止了。")def display_info(self):"""显示汽车信息。"""print(f"品牌: {self.brand}, 型号: {self.model}, 颜色: {self.color}, 轮子数: {Car.wheels}")
关键概念解释:
class Car:
: 定义了一个名为Car
的类。wheels = 4
: 这是一个类属性。它属于Car
类本身,所有由Car
类创建的对象都会共享这个wheels
属性,并且它的值默认为 4。__init__(self, brand, model, color)
: 这是一个特殊的方法,被称为构造器 (Constructor) 或初始化方法。- 当创建一个
Car
类的实例 (Instance) 时,Python 会自动调用这个__init__
方法来初始化新创建的对象。 self
: 这是一个约定俗成的参数名,必须是实例方法的第一个参数。它代表了正在被创建的对象自身。通过self
,你可以在方法内部访问和修改对象的属性。brand
,model
,color
: 这些是初始化方法接收的参数,用于设置新对象的特定属性。
- 当创建一个
self.brand = brand
等: 它们是实例属性 (Instance Attributes)。这些属性是每个对象独有的。例如,一辆奥迪车的brand
是 “Audi”,而另一辆奔驰车的brand
是 “Mercedes”。start(self)
,stop(self)
,display_info(self)
: 这些是实例方法 (Instance Methods)。它们是定义在类中的函数,用于描述对象可以执行的行为。每个方法也必须将self
作为第一个参数。
2. 什么是对象 (Object) 或实例 (Instance)?
对象 (Object) 或实例 (Instance) 是类的具体实现。如果说类是“汽车”这个概念的蓝图,那么对象就是一辆辆真实存在的、有具体品牌、型号和颜色的汽车,比如“我的那辆红色的特斯拉 Model 3”。
你可以从一个类创建任意数量的对象,每个对象都是独立的,拥有自己的一套实例属性值,但它们都遵循类的蓝图。
2.1 创建对象(实例化)
通过在类名后面加上圆括号 ()
,就像调用函数一样,就可以创建一个类的对象。这个过程称为实例化 (Instantiation)。
# 创建 Car 类的对象(实例化)# car1 是 Car 类的一个实例 (对象)
car1 = Car("Tesla", "Model 3", "Red")# car2 是 Car 类的一个独立实例 (对象)
car2 = Car("Mercedes", "C-Class", "Black")
当 Car("Tesla", "Model 3", "Red")
被调用时:
- Python 会在内存中创建一个新的
Car
对象。 - Python 会自动调用
Car
类的__init__
方法,并将新创建的对象作为self
参数传递,同时将"Tesla"
,"Model 3"
,"Red"
作为brand
,model
,color
参数传递进去。 __init__
方法会为car1
这个对象设置brand
,model
,color
和is_running
等实例属性。
3. 访问属性和调用方法
创建对象后,你可以使用点 .
运算符来访问对象的属性和调用对象的方法。
# 访问 car1 的实例属性
print(f"Car1 品牌: {car1.brand}") # 输出: Car1 品牌: Tesla
print(f"Car1 型号: {car1.model}") # 输出: Car1 型号: Model 3
print(f"Car1 颜色: {car1.color}") # 输出: Car1 颜色: Red
print(f"Car1 初始运行状态: {car1.is_running}") # 输出: Car1 初始运行状态: False# 访问类属性 (可以通过类名或实例访问,但通过类名访问更清晰)
print(f"所有 Car 都有轮子数: {Car.wheels}") # 输出: 所有 Car 都有轮子数: 4
print(f"Car1 的轮子数: {car1.wheels}") # 输出: Car1 的轮子数: 4# 调用 car1 的方法
car1.start() # 输出: Tesla Model 3 已启动。
print(f"Car1 当前运行状态: {car1.is_running}") # 输出: Car1 当前运行状态: True
car1.start() # 输出: Tesla Model 3 已经在运行了。
car1.stop() # 输出: Tesla Model 3 已停止。
car1.stop() # 输出: Tesla Model 3 已经停止了。# 调用 car1 的显示信息方法
car1.display_info() # 输出: 品牌: Tesla, 型号: Model 3, 颜色: Red, 轮子数: 4# 访问 car2 的属性和调用方法
print("\n--- car2 的信息 ---")
car2.display_info() # 输出: 品牌: Mercedes, 型号: C-Class, 颜色: Black, 轮子数: 4
car2.start() # 输出: Mercedes C-Class 已启动。
可以看到,car1
和 car2
是两个完全独立的对象,它们各自拥有自己的 brand
, model
, color
和 is_running
属性,并且可以独立地执行 start()
, stop()
等方法。但它们共享 wheels
这个类属性。
4. 类属性 vs 实例属性
再次强调一下它们的区别:
-
类属性 (Class Attributes):
- 定义在类体中,但在任何方法之外。
- 属于类本身,所有该类的实例共享同一个类属性。
- 通常用于存储与类相关但不随实例变化的值,例如常量或者所有实例通用的配置。
- 可以通过
ClassName.attribute_name
或instance_name.attribute_name
访问(尽管通过实例访问时,Python 最终还是会向上查找类属性)。 - 修改类属性时,会影响所有实例。
-
实例属性 (Instance Attributes):
- 在
__init__
方法中通过self.attribute_name = value
定义。 - 属于每个独立的实例,每个实例都有自己的一份。
- 用于存储每个对象特有的数据。
- 只能通过
instance_name.attribute_name
访问。 - 修改实例属性时,只影响当前的实例。
- 在
class Dog:species = "Canis familiaris" # 类属性def __init__(self, name, age):self.name = name # 实例属性self.age = age # 实例属性dog1 = Dog("Buddy", 3)
dog2 = Dog("Lucy", 5)print(f"Dog1 的名字: {dog1.name}, 年龄: {dog1.age}, 物种: {dog1.species}")
print(f"Dog2 的名字: {dog2.name}, 年龄: {dog2.age}, 物种: {dog2.species}")# 改变类属性会影响所有实例 (以及通过类名访问)
Dog.species = "Canis lupus familiaris" # 假设是更精确的学名print(f"Dog1 的新物种: {dog1.species}") # 跟着变化
print(f"Dog2 的新物种: {dog2.species}") # 跟着变化# 改变实例属性只影响当前实例
dog1.name = "Bud"
print(f"Dog1 的新名字: {dog1.name}")
print(f"Dog2 的名字 (未变): {dog2.name}")
输出:
Dog1 的名字: Buddy, 年龄: 3, 物种: Canis familiaris
Dog2 的名字: Lucy, 年龄: 5, 物种: Canis familiaris
Dog1 的新物种: Canis lupus familiaris
Dog2 的新物种: Canis lupus familiaris
Dog1 的新名字: Bud
Dog2 的名字 (未变): Lucy
5. self
的重要性
self
是一个约定俗成的名称,它代表了当前正在操作的实例本身。在类的方法中,任何时候你需要访问或修改当前实例的属性,或者调用当前实例的其他方法,都必须通过 self.
来进行。
Python 会在调用实例方法时自动将实例作为第一个参数传递给 self
。
class Person:def __init__(self, name, age):self.name = nameself.age = agedef introduce(self):# 访问当前实例的 name 和 age 属性print(f"大家好,我叫 {self.name},我今年 {self.age} 岁了。")def celebrate_birthday(self):self.age += 1 # 修改当前实例的 age 属性print(f"{self.name} 庆祝了生日,现在 {self.age} 岁了!")self.introduce() # 调用当前实例的另一个方法p1 = Person("张三", 20)
p1.introduce()
p1.celebrate_birthday()
输出:
大家好,我叫 张三,我今年 20 岁了。
张三 庆祝了生日,现在 21 岁了!
大家好,我叫 张三,我今年 21 岁了。
总结
本篇我们深入探讨了 Python 面向对象编程的基础:
- 类 (Class): 对象的蓝图或模板,定义了对象的属性(数据)和行为(方法)。使用
class
关键字定义。 - 对象 (Object) 或实例 (Instance): 类的具体化,是基于类创建的真实存在的“事物”。通过
ClassName()
来创建。 __init__
方法: 特殊的构造器,在创建对象时自动调用,用于初始化对象的实例属性。self
参数: 实例方法中的第一个参数,指向当前正在操作的对象本身。- 类属性: 属于类本身,所有实例共享。
- 实例属性: 属于每个独立的对象,每个对象独有。
- 通过点
.
运算符访问属性和调用方法。
理解类和对象是迈向编写大型、复杂、可维护的 Python 程序的关键一步。它能帮助你更好地组织代码,模拟现实世界的实体,并构建出更具结构和逻辑的应用程序。
练习题
尝试独立完成以下练习题,并通过答案进行对照:
-
定义一个
Book
类:- 定义一个名为
Book
的类。 - 在类中定义一个类属性
material = "paper"
。 - 定义
__init__
方法,接收title
(书名),author
(作者),pages
(页数) 作为参数,并将其存储为实例属性。 - 定义一个实例方法
get_summary()
,打印书的标题、作者和页数,格式为"《[书名]》 作者: [作者], 共 [页数] 页。"
- 定义一个名为
-
创建和使用
Book
对象:- 创建两个
Book
类的对象:- 一本名为 “Python编程入门” 的书,作者 “A. Programmer”,500 页。
- 一本名为 “数据结构与算法” 的书,作者 “B. Coder”,800 页。
- 分别调用这两个对象的
get_summary()
方法。 - 访问其中一本书的
material
属性,并打印。
- 创建两个
-
修改实例属性和类属性的影响:
- 在
Book
类中,将material
类属性改为"recycled paper"
。 - 再次打印两本书的
material
属性,观察变化。 - 修改其中一本
Book
对象的pages
属性(例如,将页数从 500 改为 550)。 - 再次调用该对象的
get_summary()
方法,观察变化。另一本书的页数是否受影响?
- 在
-
定义一个
Student
类:- 定义一个名为
Student
的类。 - 定义一个类属性
school_name = "Python University"
。 - 定义
__init__
方法,接收name
(学生姓名),student_id
(学号),grades
(一个表示各科分数的列表,默认空列表[]
)。注意默认参数的陷阱! 请使用正确的姿势处理grades
参数的默认值。 - 定义一个实例方法
add_grade(grade)
,用于向学生的grades
列表中添加一个分数。 - 定义一个实例方法
get_average_grade()
,计算并返回学生的平均分。如果grades
列表为空,返回 0。 - 定义一个实例方法
display_student_info()
,打印学生姓名、学号、学校名称和平均分。
- 定义一个名为
-
创建和操作
Student
对象:- 创建两个
Student
对象:- 学生 A: 姓名 “李华”, 学号 “2023001”。
- 学生 B: 姓名 “王明”, 学号 “2023002”,初始分数 [70, 80, 90]。
- 为学生 A 添加分数:85, 92, 78。
- 分别调用两个学生的
display_student_info()
方法。 - 修改
Student
类的school_name
为 “Global Python University”。 - 再次调用学生 A 的
display_student_info()
方法,观察学校名称是否变化。
- 创建两个
练习题答案
1. 定义一个 Book
类:
# 1. 定义一个 Book 类
class Book:"""表示一本书的类。"""material = "paper" # 类属性def __init__(self, title, author, pages):"""初始化 Book 对象。Args:title (str): 书的标题。author (str): 书的作者。pages (int): 书的页数。"""self.title = titleself.author = authorself.pages = pagesdef get_summary(self):"""打印书的标题、作者和页数。"""print(f"《{self.title}》 作者: {self.author}, 共 {self.pages} 页。")
2. 创建和使用 Book
对象:
# 2. 创建和使用 Book 对象
book1 = Book("Python编程入门", "A. Programmer", 500)
book2 = Book("数据结构与算法", "B. Coder", 800)book1.get_summary()
book2.get_summary()# 访问 material 类属性
print(f"Book1 的材质是: {book1.material}")
print(f"Book2 的材质是: {book2.material}")
print(f"Book 类的材质是: {Book.material}")
输出:
《Python编程入门》 作者: A. Programmer, 共 500 页。
《数据结构与算法》 作者: B. Coder, 共 800 页。
Book1 的材质是: paper
Book2 的材质是: paper
Book 类的材质是: paper
3. 修改实例属性和类属性的影响:
# 3. 修改实例属性和类属性的影响
# 修改类属性
Book.material = "recycled paper"
print(f"\n修改 Book.material 后:")
print(f"Book1 的材质: {book1.material}") # 跟着变化
print(f"Book2 的材质: {book2.material}") # 跟着变化
print(f"Book 类的材质: {Book.material}")# 修改实例属性
book1.pages = 550 # 只修改 book1 的页数
print(f"\n修改 book1.pages 后:")
book1.get_summary() # book1 的页数已改变
book2.get_summary() # book2 的页数未受影响
输出:
修改 Book.material 后:
Book1 的材质: recycled paper
Book2 的材质: recycled paper
Book 类的材质: recycled paper修改 book1.pages 后:
《Python编程入门》 作者: A. Programmer, 共 550 页。
《数据结构与算法》 作者: B. Coder, 共 800 页。
4. 定义一个 Student
类:
# 4. 定义一个 Student 类
class Student:"""表示一个学生的类。"""school_name = "Python University" # 类属性def __init__(self, name, student_id, grades=None): # 正确处理可变默认参数"""初始化 Student 对象。Args:name (str): 学生姓名。student_id (str): 学号。grades (list, optional): 学生各科分数的列表。默认为 None。"""self.name = nameself.student_id = student_id# 如果 grades 是 None,则初始化一个空列表;否则使用传入的列表self.grades = [] if grades is None else list(grades) # 使用 list(grades) 避免共享同一个列表def add_grade(self, grade):"""向学生的成绩列表中添加一个分数。"""if isinstance(grade, (int, float)):self.grades.append(grade)else:print("警告: 成绩必须是数字。")def get_average_grade(self):"""计算并返回学生的平均分。"""if not self.grades: # 如果列表为空return 0return sum(self.grades) / len(self.grades)def display_student_info(self):"""打印学生姓名、学号、学校名称和平均分。"""average = self.get_average_grade()print(f"\n--- 学生信息 ---")print(f"姓名: {self.name}")print(f"学号: {self.student_id}")print(f"学校: {Student.school_name}") # 通过类名访问类属性print(f"平均分: {average:.2f}") # 保留两位小数print("-" * 15)
5. 创建和操作 Student
对象:
# 5. 创建和操作 Student 对象
# 学生 A: 姓名 "李华", 学号 "2023001"。
student_a = Student("李华", "2023001")# 学生 B: 姓名 "王明", 学号 "2023002",初始分数 [70, 80, 90]。
student_b = Student("王明", "2023002", [70, 80, 90])# 为学生 A 添加分数
student_a.add_grade(85)
student_a.add_grade(92)
student_a.add_grade(78)# 分别调用两个学生的 display_student_info() 方法
student_a.display_student_info()
student_b.display_student_info()# 修改 Student 类的 school_name
Student.school_name = "Global Python University"
print("\n--- 修改学校名称后 ---")# 再次调用学生 A 的 display_student_info() 方法,观察学校名称是否变化
student_a.display_student_info()
student_b.display_student_info() # 同样也受影响
输出:
--- 学生信息 ---
姓名: 李华
学号: 2023001
学校: Python University
平均分: 85.00
------------------ 学生信息 ---
姓名: 王明
学号: 2023002
学校: Python University
平均分: 80.00
------------------ 修改学校名称后 ------ 学生信息 ---
姓名: 李华
学号: 2023001
学校: Global Python University
平均分: 85.00
------------------ 学生信息 ---
姓名: 王明
学号: 2023002
学校: Global Python University
平均分: 80.00
---------------