每天学习一点Python——面向对象编程(OOP)入门详解
面向对象编程(OOP)是一种将相关属性和行为打包到单个对象中的编程方法。就像工厂的装配线,每个步骤处理一些材料,最终将原材料转化为成品。
今天我们将通过Python语言,从零开始学习OOP的核心概念:类、对象、属性和方法,以及继承。
一、什么是类和对象?
想象你要记录公司员工的信息。如果用列表来表示每个员工:
kirk = ["James Kirk", 34, "Captain", 2265]
spock = ["Spock", 35, "Science Officer", 2254]
mccoy = ["Leonard McCoy", "Chief Medical Officer", 2266]
这样做有几个问题:
- 1. 可读性差:如果代码多,你很难记住
kirk[0] 是名字还是年龄。 - 2. 容易出错:如果某个员工信息不全(比如
mccoy少了年龄),就会导致数据错位。
为了解决这个问题,我们可以使用类来定义一种结构化的数据类型。
1.1 定义类
类就像一张蓝图,它规定了某个事物应该有哪些属性和行为,但它本身不包含具体数据。
对象是根据类创建的具体实例,包含真实数据。
定义一个最简单的类:
class Dog:
pass
- •
Dog 是类名,Python约定类名使用大驼峰命名法(每个单词首字母大写)。 - •
pass 是占位符,表示暂时什么都不写,避免语法错误。
1.2 添加属性:__init__ 方法
我们希望每只狗都有名字和年龄,这可以通过 __init__ 方法来实现:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
- •
__init__ 是一个特殊方法,在创建对象时自动调用,用于初始化对象属性。 - •
self.name = name 表示将传入的 name 赋值给该实例的 .name 属性。
💡 提示:self 不是Python关键字,你可以用别的词,但约定俗成用 self,清晰易懂。
1.3 类属性 vs 实例属性
- • 实例属性:每个对象特有的属性(如每只Dog的名字、年龄)。
- • 类属性:所有对象共享的属性(如所有Dog都是同一个物种)。
class Dog:
species = "Canis familiaris" # 类属性(所有狗共享)
def __init__(self, name, age):
self.name = name # 实例属性(每只狗特有)
self.age = age # 实例属性(每只狗特有)
二、创建对象(实例化)
有了类,我们就可以创建具体的Dog对象了:
buddy = Dog("Buddy", 9)
miles = Dog("Miles", 4)
- •
Dog("Buddy", 9) 调用 __init__ 方法,创建一只名为Buddy、年龄9岁的狗。
print(buddy == miles) # 输出: False
访问属性:
print(buddy.name) # 输出: Buddy
print(miles.age) # 输出: 4
print(buddy.species) # 输出: Canis familiaris
三、添加方法
方法是定义在类中的函数,用于描述对象的行为:
class Dog:
species = "Canis familiaris"
def __init__(self, name, age):
self.name = name
self.age = age
def description(self):
return f"{self.name} is {self.age} years old"
def speak(self, sound):
return f"{self.name} says {sound}"
使用:
miles = Dog("Miles", 4)
print(miles.description()) # 输出: Miles is 4 years old
print(miles.speak("Woof Woof")) # 输出: Miles says Woof Woof
3.1 为什么description()不用传参,而speak("Woof Woof")需要传参?
这是一个关于Python类方法参数的重要问题。区别在于这两个方法的定义方式不同:
1. description(self) 方法
def description(self):
return f"{self.name} is {self.age} years old"
- • 方法内部使用
self.name 和 self.age 访问实例属性 - • 调用时不需要传入任何参数,因为Python会自动传递
self
2. speak(self, sound) 方法
def speak(self, sound):
return f"{self.name} says {sound}"
对比表格:
| | | |
|---|
description(self) | | | self |
speak(self, sound) | self | "Woof Woof" | self |
3.2 美化输出:__str__ 方法
直接打印对象会显示内存地址,不友好:
print(miles) # 输出: <__main__.Dog object at 0x...>
我们可以定义 __str__ 方法,自定义打印内容:
class Dog:
# ... 其他代码不变
def __str__(self):
return f"{self.name} is {self.age} years old"
现在再打印:
print(miles) # 输出: Miles is 4 years old
💡 提示:__init__、__str__ 这类前后带双下划线的方法称为"dunder方法"(double underscores),用于实现类的特殊行为。
3.3 description() 与 __str__() 的区别
def description(self):
return f"{self.name} is {self.age} years old"
def __str__(self):
return f"{self.name} is {self.age} years old"
主要区别:
| description() | __str__() |
|---|
| 方法类型 | | |
| 调用方式 | | |
| 用途 | | |
| 示例调用 | print(miles.description()) | print(miles) |
| 字符串转换 | | str(miles) |
| 在列表/容器中 | | |
实际使用中的差异:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def description(self):
return f"{self.name} is {self.age} years old"
def __str__(self):
return f"Dog: {self.name}, Age: {self.age}"
# 创建实例
miles = Dog("Miles", 4)
两种方法的调用对比:
使用 description()
print("=== 使用 description() ===")
print(miles.description()) # 需要显式调用方法
# 输出: Miles is 4 years old
使用 __str__()
print("\n=== 使用 __str__() ===")
print(miles) # 直接打印对象,自动调用 __str__
# 输出: Dog: Miles, Age: 4
更多 __str__ 的优势
print(str(miles)) # 使用 str() 函数
# 输出: Dog: Miles, Age: 4
# 在 f-string 中
print(f"My dog: {miles}")
# 输出: My dog: Dog: Miles, Age: 4
# 在列表中
dogs = [miles, Dog("Buddy", 2)]
print(dogs)
# 输出: [Dog: Miles, Age: 4, Dog: Buddy, Age: 2]
为什么需要 __str__?
- • 很多Python函数和操作会自动调用
__str__
# 有了 __str__ 后,使用更简洁自然
print(miles) # ✅ 直接打印
print(f"Info: {miles}") # ✅ 在f-string中
print("Dog info: " + str(miles)) # ✅ 字符串拼接
# 没有 __str__ 时,需要更多手动操作
print(miles.description()) # 🔴 总是要加 .description()
四、继承:创建子类
假设我们想区分不同品种的狗,比如杰克罗素梗、腊肠犬、斗牛犬。它们都是狗,但叫声不同。我们可以通过继承来创建子类:
class JackRussellTerrier(Dog):
pass
class Dachshund(Dog):
pass
class Bulldog(Dog):
pass
4.1 重写方法
不同品种的狗叫声不同,我们可以在子类中重写 speak 方法:
class JackRussellTerrier(Dog):
def speak(self, sound="Arf"):
return f"{self.name} says {sound}"
现在创建一只JackRussellTerrier:
miles = JackRussellTerrier("Miles", 4)
print(miles.speak()) # 输出: Miles says Arf
print(miles.speak("Grrr")) # 输出: Miles says Grrr
要点总结:
- 1. 默认参数:
sound="Arf" 设置了默认值
miles.speak() # 使用默认值 → "Arf"
miles.speak("Grrr") # 使用传入值 → "Grrr"
- 3. 原理:如果不传参数,Python自动使用默认值"Arf";如果传参,则覆盖默认值
对比父类:
- • 父类
Dog.speak(self, sound):必须传参 - • 子类
JackRussellTerrier.speak(self, sound="Arf"):可不传(用默认),也可传参覆盖
默认参数让方法调用更灵活。
4.2 使用 super() 调用父类方法
如果希望在子类中保留父类方法的行为,又想做些扩展,可以使用 super():
class JackRussellTerrier(Dog):
def speak(self, sound="Arf"):
return super().speak(sound)
这样,父类 Dog 的 speak 方法被调用,格式保持一致。
两种方式对比:
1. 不用 super()(自己重写):
def speak(self, sound="Arf"):
return f"{self.name} says {sound}" # 完全自己重写
- • 问题:如果父类改了(比如改成
shouts:),这里不会自动更新
2. 用 super()(复用父类):
def speak(self, sound="Arf"):
return super().speak(sound) # 调用父类方法
举个例子:
初始情况:
class Dog:
def speak(self, sound):
return f"{self.name} says {sound}" # 父类版本1
class JackRussellTerrier(Dog):
def speak(self, sound="Arf"):
return super().speak(sound) # 调用父类方法
测试:
dog = JackRussellTerrier("Miles", 4)
print(dog.speak("汪")) # 输出: Miles says 汪
父类修改后:
class Dog:
def speak(self, sound):
# 产品经理要求:所有狗说话都要加表情!
return f"{self.name} says: {sound} 🐶" # 父类版本2(修改了!)
看看子类的变化:
情况1:用 super() 的子类
dog = JackRussellTerrier("Miles", 4)
print(dog.speak("汪"))
# 输出: Miles says: 汪 🐶 ← 自动继承父类新格式!
# super() 自动调用新版的 Dog.speak()
情况2:不用 super() 的子类
class BadJackRussellTerrier(Dog):
def speak(self, sound="Arf"):
# 自己重写的,没用 super()
return f"{self.name} says {sound}"
bad_dog = BadJackRussellTerrier("Miles", 4)
print(bad_dog.speak("汪"))
# 输出: Miles says 汪 ← 还是旧格式!没有表情!
# 需要手动修改代码才能更新
对比表格:
| | | |
|---|
| 初始 | "{name} says {sound}" | Miles says 汪 | Miles says 汪 |
| 父类修改后 | "{name} says: {sound} 🐶" | Miles says: 汪 🐶 | Miles says 汪 |
| 需要手动修改吗? | | 不需要 | 需要 |
3. 用 super() 添加功能(最常见用法):
def speak(self, sound="Arf"):
print("摇尾巴!") # 1. 自己加的动作
result = super().speak(sound) # 2. 父类的核心逻辑
print("跳起来!") # 3. 自己加的动作
return result
现实类比:
- • 用
super():大家在我的代码上搭建新功能,我更新基础代码时,大家的扩展会自动升级 - • 不用
super():大家把我的代码复制一份自己改,我更新时,大家的复制版不会自动更新
总结要点:
4.3 检查对象类型
print(type(miles)) # 输出: <class '__main__.JackRussellTerrier'>
print(isinstance(miles, Dog)) # 输出: True
print(isinstance(miles, Bulldog)) # 输出: False
- •
isinstance() 检查对象是否属于某个类或其子类。
五、练习巩固
练习1:为Dog类添加颜色属性
class Dog:
def __init__(self, name, age, coat_color):
self.name = name
self.age = age
self.coat_color = coat_color
philo = Dog("Philo", 5, "brown")
print(f"{philo.name}'s coat is {philo.coat_color}.")
输出:
Philo's coat is brown.
练习2:创建Car类
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
blue_car = Car("blue", 20000)
red_car = Car("red", 30000)
print(f"The {blue_car.color} car has {blue_car.mileage:,} miles.")
print(f"The {red_car.color} car has {red_car.mileage:,} miles.")
输出:
The blue car has 20,000 miles.
The red car has 30,000 miles.
练习3:为Car类添加行驶方法
class Car:
def __init__(self, color, mileage):
self.color = color
self.mileage = mileage
def drive(self, miles):
self.mileage += miles
my_car = Car("white", 0)
my_car.drive(100)
print(my_car.mileage) # 输出: 100
六、总结
今天我们学习了:
- 2. 属性:实例属性(每对象特有)和类属性(所有对象共享)
- 3. 方法:定义对象的行为,
self 参数自动传递 - 4. 特殊方法:
__init__(初始化)、__str__(字符串表示) - 6.
super():调用父类方法,提高代码复用性和维护性
请记住:面向对象编程的核心思想是将数据和操作数据的方法封装在一起,让代码更加模块化、可复用、易维护。
📦 资源获取提示
关注「码农自习室」,后台回复关键词 Python学习,即可获取本文完整代码,一起动手掌握高效编程的核心技巧!
❤️ 支持我们
如果觉得本文对你有帮助,欢迎点赞 + 关注,您的支持是我们持续创作优质内容的最大动力!
📚 学习资源说明
本文内容是基于《Python Basics: A Practical Introduction to Python 3》(Real Python)一书的学习笔记整理。
这本书是一本非常优秀的Python入门教材,推荐给所有想要系统学习Python的朋友们。
这本书的特点:
跟着这本书学习,配合我的笔记整理,相信你能更快掌握Python编程!
让我们一起坚持学习,每天进步一点点!💪