Python 基础语法与数据类型(十三) - 实例方法、类方法、静态方法
文章目录
- 1. 实例方法 (Instance Methods)
- 1.1 特点与语法
- 1.2 实例方法示例
- 2. 类方法 (Class Methods)
- 2.1 特点与语法
- 2.2 类方法示例
- 3. 静态方法 (Static Methods)
- 3.1 特点与语法
- 3.2 静态方法示例
- 4. 三种方法的对比总结
- 总结
- 练习题
- 练习题答案
创作不易,请各位看官顺手点点关注,不胜感激 。
本篇将深入探讨 Python 类中不同类型的方法:实例方法 (Instance Methods)、类方法 (Class Methods) 和静态方法 (Static Methods)。理解这三者之间的区别和适用场景是掌握 Python 面向对象编程的关键。
1. 实例方法 (Instance Methods)
实例方法是我们最常用、最熟悉的方法类型。它们操作的是对象(实例) 的数据。当你调用一个实例方法时,Python 会自动把该方法所属的实例(对象本身)作为第一个参数传递给它。
1.1 特点与语法
- 特点:
- 第一个参数必须是
self
(约定俗成),它指向当前实例。 - 可以访问和修改实例的属性 (
self.attribute
)。 - 可以访问和修改类的属性 (
ClassName.attribute
或self.attribute
)。 - 是最常见的方法类型,用于定义对象的行为。
- 第一个参数必须是
- 语法:
class MyClass:def instance_method(self, arg1, arg2):# 可以访问 self.some_instance_attribute# 可以访问 MyClass.some_class_attributepass
1.2 实例方法示例
让我们用一个 Robot
类来演示实例方法:
class Robot:"""一个简单的机器人模拟类"""population = 0 # 类属性:机器人总数量def __init__(self, name, model):"""初始化机器人实例"""self.name = name # 实例属性:机器人名称self.model = model # 实例属性:机器人型号self.energy = 100 # 实例属性:初始能量Robot.population += 1 # 每创建一个实例,类属性 population 增加def greet(self): # 实例方法"""机器人问候,使用实例属性 name"""if self.energy > 10:print(f"你好!我是机器人 {self.name},型号 {self.model}。")self.energy -= 5 # 问候消耗能量else:print(f"{self.name} 能量不足,无法问候。")def charge(self, amount): # 实例方法"""为机器人充电"""self.energy = min(100, self.energy + amount) # 能量不超过100print(f"{self.name} 已充电 {amount} 单位,当前能量: {self.energy}")def get_info(self): # 实例方法"""获取机器人信息"""print(f"[{self.name}] 型号: {self.model}, 能量: {self.energy}, 机器人总数: {Robot.population}")# 创建机器人实例
robot1 = Robot("Wall-E", "Cleaner-Bot")
robot2 = Robot("Eve", "Explorer-Bot")# 调用实例方法
robot1.greet()
robot2.greet()
robot1.charge(20)
robot1.greet()robot1.get_info()
robot2.get_info()
输出:
你好!我是机器人 Wall-E,型号 Cleaner-Bot。
你好!我是机器人 Eve,型号 Explorer-Bot。
Wall-E 已充电 20 单位,当前能量: 115
你好!我是机器人 Wall-E,型号 Cleaner-Bot。
[Wall-E] 型号: Cleaner-Bot, 能量: 110, 机器人总数: 2
[Eve] 型号: Explorer-Bot, 能量: 95, 机器人总数: 2
在这个例子中,greet
, charge
, get_info
都是实例方法,它们都接收 self
参数,并利用 self.name
、self.energy
等实例属性来执行特定操作。
2. 类方法 (Class Methods)
类方法是绑定到类而不是实例的方法。它们主要用于操作类属性,或者创建新的类实例(作为工厂方法)。
2.1 特点与语法
- 特点:
- 用
@classmethod
装饰器标识。 - 第一个参数必须是
cls
(约定俗成),它指向类本身(而不是实例)。 - 可以访问和修改类属性 (
cls.attribute
)。 - 不能直接访问实例属性,因为它们与特定实例无关(但可以通过
cls
创建新实例)。 - 常用于定义替代的构造方法(工厂方法)。
- 用
- 语法:
class MyClass:class_attribute = "..."@classmethoddef class_method(cls, arg1, arg2):# 可以访问 cls.class_attribute# 可以通过 cls() 创建新实例pass
2.2 类方法示例
继续使用 Robot
类,我们添加一个类方法来记录机器人总数,或者从不同格式的数据创建机器人:
class Robot:"""一个简单的机器人模拟类"""population = 0 # 类属性:机器人总数量known_models = ["Cleaner-Bot", "Explorer-Bot", "Worker-Bot"]def __init__(self, name, model):self.name = nameself.model = modelself.energy = 100Robot.population += 1# ... (实例方法与上面相同) ...def greet(self):if self.energy > 10:print(f"你好!我是机器人 {self.name},型号 {self.model}。")self.energy -= 5else:print(f"{self.name} 能量不足,无法问候。")def charge(self, amount):self.energy = min(100, self.energy + amount)print(f"{self.name} 已充电 {amount} 单位,当前能量: {self.energy}")def get_info(self):print(f"[{self.name}] 型号: {self.model}, 能量: {self.energy}, 机器人总数: {Robot.population}")@classmethod # 声明这是一个类方法def get_robot_population(cls): # cls 指向类本身 (Robot)"""返回当前所有机器人实例的总数量。"""print(f"当前机器人总数是: {cls.population}") # 访问类属性return cls.population@classmethod # 声明这是一个类方法,作为工厂方法def create_from_string(cls, robot_string):"""从 'name-model' 格式的字符串创建机器人实例。Args:robot_string (str): 格式为 'name-model' 的字符串。Returns:Robot: 新创建的 Robot 实例,如果模型未知则返回 None。"""parts = robot_string.split('-')if len(parts) == 2:name, model = parts[0], parts[1]if model in cls.known_models: # 检查模型是否已知,使用了类属性return cls(name, model) # 使用 cls() 来创建新的实例else:print(f"警告: 未知机器人型号 '{model}'。无法创建。")return Noneelse:print("错误: 字符串格式不正确,应为 'name-model'。")return None# 调用类方法
Robot.get_robot_population() # 通过类名调用robot3 = Robot.create_from_string("Optimus-Worker-Bot") # 通过类名调用工厂方法
if robot3:robot3.greet()Robot.create_from_string("Unknown-Model") # 尝试创建未知模型
Robot.create_from_string("JustOnePart") # 尝试创建格式错误Robot.get_robot_population() # 再次查看机器人总数
输出:
当前机器人总数是: 2
正在初始化机器人 Optimus,型号 Worker-Bot...
你好!我是机器人 Optimus,型号 Worker-Bot。
警告: 未知机器人型号 'Unknown-Model'。无法创建。
错误: 字符串格式不正确,应为 'name-model'。
当前机器人总数是: 3
在 get_robot_population
中,我们使用 cls.population
来访问类属性。在 create_from_string
方法中,cls(name, model)
相当于 Robot(name, model)
,这意味着我们可以通过 cls
这个引用来创建类的实例。这在子类化时非常有用,因为 cls
将动态地指向实际调用的那个类(子类或父类)。
3. 静态方法 (Static Methods)
静态方法是与类相关但不需要访问类或实例数据的函数。它们本质上就是放在类命名空间下的普通函数,与类或实例状态无关。
3.1 特点与语法
- 特点:
- 用
@staticmethod
装饰器标识。 - 不接收
self
或cls
作为第一个参数。 - 不能访问实例属性或类属性(除非你明确地传递它们作为参数)。
- 通常用于与类逻辑相关,但又不依赖于特定实例或类状态的辅助功能。
- 用
- 语法:
class MyClass:@staticmethoddef static_method(arg1, arg2):# 不涉及 self 或 clspass
3.2 静态方法示例
继续 Robot
类,我们添加一个静态方法来验证机器人名称的有效性:
import re # 导入正则表达式模块class Robot:"""一个简单的机器人模拟类"""population = 0known_models = ["Cleaner-Bot", "Explorer-Bot", "Worker-Bot"]def __init__(self, name, model):self.name = nameself.model = modelself.energy = 100Robot.population += 1# ... (实例方法和类方法与上面相同) ...def greet(self):if self.energy > 10:print(f"你好!我是机器人 {self.name},型号 {self.model}。")self.energy -= 5else:print(f"{self.name} 能量不足,无法问候。")def charge(self, amount):self.energy = min(100, self.energy + amount)print(f"{self.name} 已充电 {amount} 单位,当前能量: {self.energy}")def get_info(self):print(f"[{self.name}] 型号: {self.model}, 能量: {self.energy}, 机器人总数: {Robot.population}")@classmethoddef get_robot_population(cls):print(f"当前机器人总数是: {cls.population}")return cls.population@classmethoddef create_from_string(cls, robot_string):parts = robot_string.split('-')if len(parts) == 2:name, model = parts[0], parts[1]if model in cls.known_models and cls.is_valid_name(name): # 在这里使用静态方法return cls(name, model)else:print(f"警告: 型号 '{model}' 未知或名称 '{name}' 无效。无法创建。")return Noneelse:print("错误: 字符串格式不正确,应为 'name-model'。")return None@staticmethod # 声明这是一个静态方法def is_valid_name(name): # 不接收 self 或 cls 参数"""检查机器人名称是否有效(只包含字母、数字和连字符,长度在 3-20 之间)。Args:name (str): 要检查的名称。Returns:bool: 如果名称有效则返回 True,否则返回 False。"""if 3 <= len(name) <= 20 and re.fullmatch(r'^[a-zA-Z0-9-]+$', name):return Trueprint(f"名称 '{name}' 无效。")return False# 调用静态方法 (可以通过类名或实例名调用,但通常通过类名调用)
print(f"\n检查名称 'R2-D2' 是否有效: {Robot.is_valid_name('R2-D2')}")
print(f"检查名称 'C3PO!' 是否有效: {Robot.is_valid_name('C3PO!')}")# 结合使用
valid_robot = Robot.create_from_string("MegaBot-Worker-Bot")
if valid_robot:valid_robot.greet()invalid_name_robot = Robot.create_from_string("Bad!Name-Cleaner-Bot")
输出:
检查名称 'R2-D2' 是否有效: True
名称 'C3PO!' 无效。
检查名称 'C3PO!' 是否有效: False
正在初始化机器人 MegaBot,型号 Worker-Bot...
你好!我是机器人 MegaBot,型号 Worker-Bot。
名称 'Bad!Name' 无效。
警告: 型号 'Cleaner-Bot' 未知或名称 'Bad!Name' 无效。无法创建。
is_valid_name
方法不依赖于任何特定的 Robot
实例的数据,也不依赖于 Robot
类的数据(除了它被逻辑上归类到 Robot
下)。它只是一个独立的辅助函数,所以把它定义为静态方法是合适的。
4. 三种方法的对比总结
特性 | 实例方法 (Instance Method) | 类方法 (Class Method) | 静态方法 (Static Method) |
---|---|---|---|
装饰器 | 无 (默认) | @classmethod | @staticmethod |
第一个参数 | self (指向实例) | cls (指向类) | 无 |
访问实例属性 | 能 (self.attr ) | 不能 (除非创建新实例) | 不能 |
访问类属性 | 能 (ClassName.attr 或 self.attr ) | 能 (cls.attr ) | 不能 (除非明确传递) |
用途 | 定义对象特有的行为,操作实例数据 | 操作类数据,作为替代构造器 (工厂方法) | 辅助函数,不依赖实例或类状态 |
调用方式 | instance.method() | ClassName.method() 或 instance.method() | ClassName.method() 或 instance.method() |
选择哪种方法?
- 如果你需要访问或修改实例的属性,使用实例方法。
- 如果你需要访问或修改类的属性,或者需要一个替代的构造器(工厂方法),使用类方法。
- 如果你只是想把一个函数逻辑上归类到某个类下,但它既不需要访问实例属性,也不需要访问类属性,那么使用静态方法。
总结
实例方法、类方法和静态方法是 Python 面向对象编程中三种不同类型的函数。它们各自有独特的用途和调用约定。正确地选择和使用它们可以帮助你更好地组织代码,使类设计更加清晰、合理,并提高代码的可维护性和可读性。理解它们之间的差异,特别是 self
和 cls
参数的含义,是掌握 Python OOP 的重要一步。
练习题
尝试独立完成以下练习题,并通过答案进行对照:
-
Calculator
类:- 定义一个
Calculator
类。 - 定义一个实例方法
add(self, a, b)
,返回a + b
。 - 定义一个实例方法
subtract(self, a, b)
,返回a - b
。 - 定义一个静态方法
multiply(a, b)
,返回a * b
。 - 定义一个静态方法
divide(a, b)
,返回a / b
(注意除数为 0 的情况,可以简单处理为打印错误信息并返回None
)。 - 创建一个
Calculator
对象,并分别调用所有方法进行计算。
- 定义一个
-
Product
类(结合多种方法):- 定义一个
Product
类。 - 添加一个类属性
next_id = 1
,用于生成唯一的商品 ID。 - 在
__init__
中,接收name
和price
。将next_id
赋值给self.product_id
,然后让next_id
递增。 - 定义一个实例方法
get_display_price(self)
,返回格式化后的价格字符串,例如"$19.99"
。 - 定义一个类方法
from_csv_string(cls, csv_string)
,它接收一个形如"Laptop,1200.50"
的 CSV 字符串,然后解析它并返回一个新的Product
实例。 - 定义一个静态方法
is_valid_price(price)
,检查价格是否大于 0。 - 创建几个
Product
实例,包括使用from_csv_string
创建的。测试所有方法。
- 定义一个
-
Logger
类:- 定义一个
Logger
类。 - 添加一个类属性
log_file_path = "app.log"
。 - 定义一个类方法
set_log_file(cls, path)
,用于更改日志文件路径。 - 定义一个静态方法
_format_message(message)
,用于给日志消息添加时间戳和级别(例如,"[2025-07-17 17:50:00] [INFO]: message"
),这里时间部分可以简化为time.time()
或datetime.datetime.now().strftime(...)
。 - 定义一个实例方法
log_info(self, message)
,它接收一条消息,使用_format_message
格式化后,追加到log_file_path
指定的文件中。 - 创建一个
Logger
实例。 - 记录几条信息。
- 更改日志文件路径,并再次记录信息,验证是否写入到新文件。
- 读取日志文件内容并打印。
- 定义一个
练习题答案
1. Calculator
类:
# 1. Calculator 类
class Calculator:def add(self, a, b): # 实例方法"""返回两个数的和。"""print(f"实例方法 add: {a} + {b} = {a + b}")return a + bdef subtract(self, a, b): # 实例方法"""返回两个数的差。"""print(f"实例方法 subtract: {a} - {b} = {a - b}")return a - b@staticmethoddef multiply(a, b): # 静态方法"""返回两个数的乘积。"""print(f"静态方法 multiply: {a} * {b} = {a * b}")return a * b@staticmethoddef divide(a, b): # 静态方法"""返回两个数的商,处理除数为0的情况。"""if b == 0:print("错误: 除数不能为零。")return Noneprint(f"静态方法 divide: {a} / {b} = {a / b}")return a / b# 创建 Calculator 对象
calc = Calculator()# 调用实例方法
calc.add(10, 5)
calc.subtract(20, 7)# 调用静态方法 (可以通过类名或实例名调用,推荐类名)
Calculator.multiply(4, 6)
calc.divide(10, 2)
Calculator.divide(10, 0)
输出:
实例方法 add: 10 + 5 = 15
实例方法 subtract: 20 - 7 = 13
静态方法 multiply: 4 * 6 = 24
静态方法 divide: 10 / 2 = 5.0
错误: 除数不能为零。
2. Product
类(结合多种方法):
# 2. Product 类 (结合多种方法)
class Product:next_id = 1 # 类属性:用于生成唯一的商品 IDdef __init__(self, name, price):"""初始化 Product 对象。Args:name (str): 产品名称。price (float): 产品价格。"""if not self.is_valid_price(price): # 可以在 __init__ 中调用静态方法进行验证raise ValueError("价格必须大于0。")self.product_id = Product.next_idProduct.next_id += 1 # 递增类属性self.name = nameself.price = priceprint(f"产品 '{self.name}' (ID: {self.product_id}) 创建成功。")def get_display_price(self): # 实例方法"""返回格式化后的价格字符串。"""return f"${self.price:.2f}"@classmethoddef from_csv_string(cls, csv_string): # 类方法:工厂方法"""从 'name,price' 格式的 CSV 字符串创建 Product 实例。Args:csv_string (str): 格式为 'name,price' 的 CSV 字符串。Returns:Product: 新创建的 Product 实例,如果格式错误或价格无效则返回 None。"""parts = csv_string.split(',')if len(parts) == 2:name = parts[0].strip()try:price = float(parts[1].strip())if cls.is_valid_price(price): # 在类方法中调用静态方法进行验证return cls(name, price) # 使用 cls() 来创建新的实例else:print(f"从 CSV 创建失败: 价格 '{price}' 无效。")return Noneexcept ValueError:print(f"从 CSV 创建失败: 价格 '{parts[1]}' 不是有效数字。")return Noneelse:print("从 CSV 创建失败: 字符串格式不正确,应为 'name,price'。")return None@staticmethoddef is_valid_price(price): # 静态方法"""检查价格是否有效 (大于 0)。Args:price (float/int): 要检查的价格。Returns:bool: 如果价格有效则返回 True,否则返回 False。"""return price > 0# 创建 Product 实例
p1 = Product("Laptop", 1200.50)
p2 = Product("Mouse", 25)print(f"产品 {p1.name} 的显示价格: {p1.get_display_price()}")
print(f"产品 {p2.name} 的显示价格: {p2.get_display_price()}")# 使用类方法创建实例
p3 = Product.from_csv_string("Keyboard,75.99")
if p3:print(f"产品 {p3.name} (ID: {p3.product_id}) 的显示价格: {p3.get_display_price()}")p4 = Product.from_csv_string("Headphones,0") # 无效价格
p5 = Product.from_csv_string("Monitor,abc") # 无效格式
p6 = Product.from_csv_string("Webcam") # 格式错误# 再次查看类属性 next_id
print(f"下一个产品 ID 将是: {Product.next_id}")
输出:
产品 'Laptop' (ID: 1) 创建成功。
产品 'Mouse' (ID: 2) 创建成功。
产品 Laptop 的显示价格: $1200.50
产品 Mouse 的显示价格: $25.00
产品 'Keyboard' (ID: 3) 创建成功。
产品 Keyboard (ID: 3) 的显示价格: $75.99
从 CSV 创建失败: 价格 '0.0' 无效。
从 CSV 创建失败: 价格 'abc' 不是有效数字。
从 CSV 创建失败: 字符串格式不正确,应为 'name,price'。
下一个产品 ID 将是: 4
3. Logger
类:
# 3. Logger 类
import datetime
import os # 用于检查文件是否存在class Logger:log_file_path = "app.log" # 类属性:默认日志文件路径@classmethoddef set_log_file(cls, path): # 类方法"""设置日志文件路径。"""cls.log_file_path = pathprint(f"日志文件路径已更改为: {cls.log_file_path}")@staticmethoddef _format_message(message, level="INFO"): # 静态方法"""格式化日志消息,添加时间戳和级别。Args:message (str): 原始日志消息。level (str, optional): 日志级别。默认为 "INFO"。Returns:str: 格式化后的日志消息。"""timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")return f"[{timestamp}] [{level.upper()}]: {message}"def log_info(self, message): # 实例方法"""将 INFO 级别的消息写入日志文件。"""formatted_message = self._format_message(message, level="INFO") # 调用静态方法try:# 使用类属性 log_file_pathwith open(Logger.log_file_path, 'a', encoding='utf-8') as f:f.write(formatted_message + "\n")print(f"日志记录成功: '{message}' -> '{Logger.log_file_path}'")except IOError as e:print(f"日志写入失败到 '{Logger.log_file_path}': {e}")def log_error(self, message): # 另一个实例方法,演示不同级别"""将 ERROR 级别的消息写入日志文件。"""formatted_message = self._format_message(message, level="ERROR")try:with open(Logger.log_file_path, 'a', encoding='utf-8') as f:f.write(formatted_message + "\n")print(f"错误日志记录成功: '{message}' -> '{Logger.log_file_path}'")except IOError as e:print(f"错误日志写入失败到 '{Logger.log_file_path}': {e}")# 清理旧的日志文件(如果存在)
if os.path.exists("app.log"):os.remove("app.log")
if os.path.exists("debug.log"):os.remove("debug.log")# 创建 Logger 实例
my_logger = Logger()# 记录几条信息
my_logger.log_info("应用程序启动。")
my_logger.log_info("用户 'admin' 登录成功。")
my_logger.log_error("数据库连接失败。")# 更改日志文件路径 (通过类方法)
Logger.set_log_file("debug.log")# 再次记录信息,验证是否写入到新文件
my_logger.log_info("正在进行调试操作...")
my_logger.log_error("发现一个严重 bug。")# 读取并打印 app.log 的内容
print("\n--- app.log 内容 ---")
try:with open("app.log", 'r', encoding='utf-8') as f:print(f.read())
except FileNotFoundError:print("app.log 文件不存在。")# 读取并打印 debug.log 的内容
print("\n--- debug.log 内容 ---")
try:with open("debug.log", 'r', encoding='utf-8') as f:print(f.read())
except FileNotFoundError:print("debug.log 文件不存在。")
输出(日期和时间会根据你运行的实际时间有所不同):
日志记录成功: '应用程序启动。' -> 'app.log'
日志记录成功: '用户 'admin' 登录成功。' -> 'app.log'
错误日志记录成功: '数据库连接失败。' -> 'app.log'
日志文件路径已更改为: debug.log
日志记录成功: '正在进行调试操作...' -> 'debug.log'
错误日志记录成功: '发现一个严重 bug。' -> 'debug.log'--- app.log 内容 ---
[2025-07-17 17:50:18] [INFO]: 应用程序启动。
[2025-07-17 17:50:18] [INFO]: 用户 'admin' 登录成功。
[2025-07-17 17:50:18] [ERROR]: 数据库连接失败。--- debug.log 内容 ---
[2025-07-17 17:50:18] [INFO]: 正在进行调试操作...
[2025-07-17 17:50:18] [ERROR]: 发现一个严重 bug。