面向对象编程(Object Oriented Programming)是现代编程的核心范式之一。本文将系统介绍Python中面向对象编程的基础知识,帮助初学者快速掌握OOP的核心概念和使用方法。
一、面向对象基本概念
1.1 面向过程 vs 面向对象
面向过程编程:
-
关注"怎么做",即解决问题的步骤
-
通过函数封装代码块
-
适合简单任务,但随着需求复杂化代码会变得难以维护
面向对象编程:
-
关注"谁来做",即确定职责分配
-
通过对象封装数据和方法
-
更适合复杂项目开发,提供了更好的组织代码的方式
1.2 核心优势
面向对象编程的主要优势在于:
-
更好的代码组织
-
更高的可重用性
-
更容易应对需求变化
-
更接近现实世界的思维方式
二、类和对象
2.1 基本概念
类(Class):
-
对具有相同特征和行为的事物的抽象描述
-
相当于制造产品的图纸
-
包含属性和方法
对象(Object):
-
类的具体实例
-
根据类创建出来的具体存在
-
相当于根据图纸制造的实际产品
2.2 关系说明
-
类是模板,对象是实例
-
先有类,后有对象
-
一个类可以创建多个对象
-
对象拥有类中定义的所有属性和方法
三、类的设计
设计类时需要关注三个要素:
-
类名:使用大驼峰命名法(如
ClassName
) -
属性:对象的特征
-
方法:对象的行为
3.1 设计技巧
-
名词提炼法:从需求描述中找出名词作为候选类
-
动词识别法:从需求描述中找出动词作为候选方法
3.2 示例练习
需求1:
-
小明今年18岁,身高1.75,每天早上跑步,会去吃东西
-
小美今年17岁,身高1.65,小美不跑步,喜欢吃东西
设计:
class Person:
def __init__(self, name, age, height):
self.name = name
self.age = age
self.height = height
def run(self):
print(f"{self.name}在跑步")
def eat(self):
print(f"{self.name}在吃东西")
需求2:
-
一只黄颜色的狗狗叫大黄
-
看见生人汪汪叫
-
看见家人摇尾巴
设计:
class Dog:
def __init__(self, name, color):
self.name = name
self.color = color
def bark(self):
print(f"{self.name}汪汪叫")
def wag_tail(self):
print(f"{self.name}摇尾巴")
四、面向对象基础语法
4.0 dir函数
dir()
是 Python 的一个非常有用的内置函数,主要用于返回对象的属性列表或当前作用域内的名称列表。
基本用法
-
不带参数调用:返回当前作用域内的名称列表
dir() # 返回当前作用域中的名称列表
-
带参数调用:返回对象的有效属性列表
dir(object) # 返回对象的属性和方法列表
示例
示例1:查看当前作用域
x = 10
y = "hello"
print(dir())
# 输出类似:['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'x', 'y']
示例2:查看模块内容
import math
print(dir(math))
# 输出 math 模块的所有函数和属性
示例3:查看对象属性和方法
s = "hello"
print(dir(s))
# 输出字符串对象的所有方法,如 'capitalize', 'casefold', 'center', 'count', 'encode', 等
示例4:自定义对象
class MyClass:
def __init__(self):
self.x = 10
def method(self):
pass
obj = MyClass()
print(dir(obj))
# 输出包含 'method', 'x' 等属性和方法
注意事项
-
dir()
返回的列表是字母顺序排列的 -
对于不同类型的对象,返回的内容会有所不同
-
它会返回所有可访问的属性,包括特殊方法(以双下划线开头和结尾的方法)
-
当对象定义了
__dir__()
方法时,dir()
会调用该方法并返回其结果
实际应用
dir()
在交互式环境中特别有用,可以快速查看对象可用的方法和属性,是 Python 自省(introspection)的重要工具之一。
# 在交互式环境中快速探索对象
>>> import numpy as np
>>> dir(np) # 查看 numpy 模块的内容
>>> a = np.array([1,2,3])
>>> dir(a) # 查看 numpy 数组的方法
4.1 定义简单类
class ClassName:
def method1(self, parameters):
# 方法实现
pass
def method2(self, parameters):
# 方法实现
pass
4.2 创建对象
object_name = ClassName()
4.3 示例:猫类
class Cat:
def eat(self):
print("小猫爱吃鱼")
def drink(self):
print("小猫在喝水")
tom = Cat()
tom.eat()
tom.drink()
五、方法中的self参数
5.1 self的作用
-
表示当前对象实例
-
通过self可以访问对象的属性和方法
-
Python自动传递,不需要手动传入
5.2 使用示例
class Cat:
def eat(self):
print(f"{self.name}爱吃鱼")
tom = Cat()
tom.name = "Tom"
tom.eat()
六、初始化方法
6.1 __init__
方法
-
创建对象时自动调用
-
用于初始化对象属性
-
可以接收参数进行灵活初始化
6.2 使用示例
class Cat:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name}爱吃鱼")
tom = Cat("Tom")
tom.eat()
七、常用内置方法
7.1 __del__
方法
__del__
是Python中的一个特殊方法(魔术方法),用于定义对象被销毁时的行为。
7.1.1 __del__
方法基本概念
__del__
方法被称为析构函数,当对象即将被垃圾回收时自动调用。
class MyClass:
def __del__(self):
print(f"对象 {self} 正在被销毁")
obj = MyClass()
del obj # 输出: 对象 <__main__.MyClass object at 0x...> 正在被销毁
7.1.2 调用时机
__del__
方法在以下情况会被调用:
-
当对象的引用计数变为0时
-
当垃圾回收器回收对象时
-
程序退出时,所有存活的对象都会被销毁
7.1.3 典型使用场景
7.1.3.1 资源清理
class FileHandler:
def __init__(self, filename):
self.file = open(filename, 'r')
def __del__(self):
self.file.close()
print("文件资源已释放")
7.1.3.2 连接关闭
class DatabaseConnection:
def __init__(self):
self.connection = connect_to_database()
def __del__(self):
self.connection.close()
print("数据库连接已关闭")
7.1.4 重要注意事项
7.1.4.1 不保证立即执行
__del__
的调用时机由垃圾回收器决定,不保证会立即执行:
obj = MyClass()
obj = None # 不保证立即调用__del__
7.1.4.2 循环引用问题
存在循环引用时,__del__
可能永远不会被调用:
class Node:
def __init__(self):
self.ref = None
def __del__(self):
print("节点被删除")
a = Node()
b = Node()
a.ref = b
b.ref = a # 循环引用
del a, b # __del__不会被调用
7.1.4.3 异常处理
__del__
中发生的异常会被忽略,不会向上传播:
class Problematic:
def __del__(self):
raise Exception("Oops!")
p = Problematic()
del p # 异常被静默忽略
7.1.5 最佳实践
-
优先使用上下文管理器:对于资源管理,
with
语句更可靠with open('file.txt') as f: # 使用文件 # 离开with块自动关闭
-
显式清理方法:提供
close()
或cleanup()
方法供用户显式调用 -
避免复杂逻辑:
__del__
中只做必要清理,避免复杂操作 -
弱引用解决循环引用:使用
weakref
模块处理循环引用
7.1.6替代方案
7.1.6.1 上下文管理器
class Resource:
def __enter__(self):
self.acquire_resource()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.release_resource()
7.1.6.2 显式清理方法
class Connection:
def close(self):
# 清理代码
self._closed = True
def __del__(self):
if not getattr(self, '_closed', True):
self.close()
7.1.7 总结
-
__del__
是Python的析构函数,但不应该依赖它进行关键资源清理 -
存在循环引用时可能永远不会被调用
-
对于重要资源,应该提供显式的释放方法
-
文件、网络连接等资源最好使用
with
语句管理
记住:在Python中,显式优于隐式,资源管理最好通过上下文管理器或显式方法调用来实现。
7.2 __str__
方法
__str__
是Python中一个重要的特殊方法(魔术方法),用于定义对象的"非正式"或可打印的字符串表示。
7.2.1__str__
方法基础
7.2.1.1 基本语法
class MyClass:
def __str__(self):
return "对象的字符串表示"
7.2.1.2 调用方式
__str__
方法会在以下情况下自动调用:
-
使用
print()
函数打印对象时 -
使用
str()
函数转换对象时 -
在f-string或字符串格式化中使用对象时
obj = MyClass()
print(obj) # 调用__str__
print(str(obj)) # 调用__str__
print(f"{obj}") # 调用__str__
7.2.2__str__
与__repr__
的区别
特性 | __str__ | __repr__ |
---|---|---|
目的 | 用户友好的输出 | 明确的、无歧义的对象表示 |
调用场景 | print() , str() , 格式化字符串 | 交互式环境、repr() , 调试 |
返回值要求 | 可读性好的字符串 | 应尽可能包含重建对象的信息 |
默认实现 | 如果没有定义,会调用__repr__ | 所有对象都应该有__repr__ 实现 |
示例对比
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point at ({self.x}, {self.y})"
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
p = Point(3, 4)
print(str(p)) # 输出: Point at (3, 4)
print(repr(p)) # 输出: Point(x=3, y=4)
7.2.3 最佳实践
7.2.3.1 设计原则
-
信息丰富:返回的字符串应包含对象的关键信息
-
可读性强:便于人类阅读和理解
-
简洁明了:避免过于冗长的输出
-
一致性:同一类的不同实例应采用相似的格式
7.2.3.2 良好示例
class Book:
def __init__(self, title, author, year):
self.title = title
self.author = author
self.year = year
def __str__(self):
return f"'{self.title}' by {self.author} ({self.year})"
book = Book("Python编程", "Guido van Rossum", 2020)
print(book) # 输出: 'Python编程' by Guido van Rossum (2020)
7.2.3.3 应避免的做法
-
包含复杂计算:
__str__
应该快速执行 -
修改对象状态:
__str__
应该是无副作用的 -
返回非字符串:必须返回字符串类型
-
过于技术性:这是
__repr__
的职责
7.2.4 高级用法
7.2.4.1 多语言支持
class MultiLanguageUser:
def __init__(self, name, lang='en'):
self.name = name
self.lang = lang
def __str__(self):
return {
'en': f"User: {self.name}",
'zh': f"用户: {self.name}",
'ja': f"ユーザー: {self.name}"
}.get(self.lang, f"User: {self.name}")
7.2.4.2 动态信息展示
class Progress:
def __init__(self, total):
self.total = total
self.current = 0
def __str__(self):
percent = (self.current / self.total) * 100
return f"[{'#' * int(percent//10)}{' ' * (10 - int(percent//10))}] {percent:.1f}%"
7.2.5常见问题解答
Q1: 如果没有定义__str__
会怎样?
A1: Python会调用__repr__
作为替代。如果__repr__
也没有定义,会使用默认的对象表示(如<__main__.MyClass object at 0x...>
)。
Q2: __str__
可以返回非字符串吗?
A2: 不可以,__str__
必须返回字符串类型(str),否则会引发TypeError。
Q3: 什么时候应该优先使用__repr__
?
A3: 当字符串表示主要用于调试和开发时,应该优先实现__repr__
。一个好的经验法则是:先实现__repr__
,再根据需要实现__str__
。
7.2.6 实际应用案例
7.2.6.1 电商产品展示
class Product:
def __init__(self, name, price, stock):
self.name = name
self.price = price
self.stock = stock
def __str__(self):
return (f"{self.name}\n"
f"价格: ¥{self.price:.2f}\n"
f"库存: {'有货' if self.stock > 0 else '缺货'}")
7.2.6.2 学生成绩报告
class Student:
def __init__(self, name, scores):
self.name = name
self.scores = scores
def __str__(self):
avg = sum(self.scores) / len(self.scores)
return (f"学生: {self.name}\n"
f"科目数: {len(self.scores)}\n"
f"平均分: {avg:.1f}\n"
f"最高分: {max(self.scores)}\n"
f"最低分: {min(self.scores)}")
7.2.7 总结
-
__str__
用于创建对象的用户友好字符串表示 -
与
__repr__
相比,__str__
更注重可读性而非精确性 -
应该为重要的用户可见类实现
__str__
方法 -
遵循信息丰富、可读性强、简洁明了的设计原则
-
避免在
__str__
中执行复杂操作或产生副作用
正确实现__str__
可以显著提升代码的可读性和用户体验,特别是在调试和日志记录场景中。
八、总结
面向对象编程是Python中非常重要的编程范式,掌握OOP可以让你:
-
写出更结构化的代码
-
更容易维护和扩展程序
-
更好地处理复杂业务逻辑
-
提高代码复用性