面向对象编程是 Python 的核心特性之一,它通过类(class)和对象(object)将数据(属性)与操作(方法)封装在一起,从而构建出模块化、易于维护和复用的代码结构。
9. 方法
在类内部定义的函数被称为方法,它们用于描述对象的行为。根据作用域和绑定方式的不同,Python 主要提供了三种方法:实例方法、类方法和静态方法。理解它们的区别是写出优雅、正确代码的关键。
9.1 实例方法
这是我们最常用的一种方法。它的第一个参数必须是 self,这个 self 会绑定到调用该方法的实例本身,用于访问和操作该实例的变量。
- 关键特点:依赖对象的具体状态。通过
self 参数,方法可以读写该实例独有的属性。
- 调用方式:只能通过类的实例来调用(例如
obj.method())。Python 会自动将调用该方法的实例作为 self 参数传入。
- 访问能力:可以访问实例变量(通过
self.xxx),也可以访问类变量(通过 self.__class__.xxx 或直接使用类名)。
示例1:基础使用
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print(f"{self.name} 汪汪叫!")
dog = Dog("旺财")
dog.bark() # 输出:旺财 汪汪叫!
示例2:操作实例变量
这个例子更清楚地展示了实例方法如何与每个对象独立的数据交互。
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner # 实例变量
self.balance = balance # 实例变量
# 实例方法:操作实例变量
def deposit(self, amount):
self.balance += amount
return f"{self.owner} deposited ${amount}. New balance: ${self.balance}"
# 创建对象
account1 = BankAccount("Alice", 100)
account2 = BankAccount("Bob", 50)
# 调用实例方法
print(account1.deposit(50)) # 输出: Alice deposited $50. New balance: $150
print(account2.deposit(100)) # 输出: Bob deposited $100. New balance: $150
# 实例方法无法通过类名直接调用(会报错)
# BankAccount.deposit(100) # 错误!缺少self参数
✅ 验证:
deposit 方法通过 self.balance 修改特定账户的余额。
account1.deposit(50) 只影响 account1 的余额,account2 的余额保持不变。
9.2 类方法
类方法使用 @classmethod 这个装饰器来定义,它的第一个参数是 cls,代表类本身。
- 关键特点:操作类级别的状态或行为,不依赖于任何特定实例的状态。
- 调用方式:既可以通过类名调用(
Class.method()),也可以通过实例调用(obj.method())。无论哪种方式,传入的 cls 参数都指向类本身。
- 访问能力:可以访问和修改类变量,但不能直接访问实例变量(因为没有绑定到具体实例的
self)。
- 典型用途:工厂方法(创建并返回类的实例)、修改或获取类属性。
示例1:访问类变量
class Dog:
species = "Canis familiaris"
@classmethod
def get_species(cls):
return cls.species
print(Dog.get_species()) # 通过类调用:Canis familiaris
dog = Dog()
print(dog.get_species()) # 通过实例调用:Canis familiaris
示例2:修改类状态
class Car:
total_cars = 0 # 类变量
def __init__(self, model):
self.model = model
Car.total_cars += 1 # 通过类名修改类变量
# 类方法:重置类变量
@classmethod
def reset_total(cls):
cls.total_cars = 0
# 创建对象
car1 = Car("Toyota Camry")
car2 = Car("Honda Civic")
# 验证类变量
print(Car.total_cars) # 输出: 2 (初始总数)
# 调用类方法(通过类名)
Car.reset_total()
print(Car.total_cars) # 输出: 0 (重置成功)
# 通过实例调用类方法(同样有效)
car1.reset_total()
print(Car.total_cars) # 输出: 0
✅ 验证:
total_cars 是类变量,所有实例共享。
reset_total 是类方法,它通过 cls.total_cars 修改的是类本身的状态。
- 即使通过
car1.reset_total() 调用,cls 仍然指向 Car 类,而不是 car1 这个实例。
实例方法 vs 类方法:核心对比
为了帮你快速决策,这里有一个清晰的对比表格:
| 特性 |
实例方法 (Instance Methods) |
类方法 (Class Methods) |
| 第一个参数 |
self(表示对象本身) |
cls(表示类本身) |
| 操作对象 |
操作实例变量(依赖对象状态) |
操作类变量(依赖类状态) |
| 调用方式 |
仅通过实例调用(obj.method()) |
通过类名或实例调用(Class.method() 或 obj.method()) |
| 典型用途 |
对象行为(如 deposit(), greet()) |
类级操作(如工厂方法、重置类状态) |
| 示例 |
def withdraw(self, amount): |
@classmethod def create_car(cls, model): |
一个常见错误:在定义类方法时忘记添加 @classmethod 装饰器,这会导致它被误认为实例方法。
9.3 静态方法
静态方法使用 @staticmethod 装饰器定义。它既不需要 self 也不需要 cls 参数,你可以把它理解为一个恰好放在类命名空间下的普通函数。
- 关键特点:与类和实例状态都无关。它不操作实例变量,也不操作类变量(除非显式通过类名传递)。
- 调用方式:可以通过类名或实例调用。
- 典型用途:工具函数、数据验证、与类相关但不依赖其状态的逻辑。
class MathUtil:
@staticmethod
def add(x, y):
return x + y
print(MathUtil.add(3, 5)) # 输出:8
9.4 三种方法的综合对比与示例
如何在实际中选择?下表从多个维度进行了总结:
| 特性 |
实例方法 |
类方法 |
静态方法 |
| 第一个参数 |
self(实例自身) |
cls(类自身) |
无特殊参数 |
| 可访问实例变量 |
是 |
否 |
否 |
| 可访问类变量 |
是(通过 self.__class__ 或类名) |
是(通过 cls) |
否(需显式通过类名) |
| 调用方式 |
只能通过实例调用 |
可通过类或实例调用 |
可通过类或实例调用 |
| 自动传入的参数 |
实例对象 |
类对象 |
无 |
| 典型用途 |
操作实例数据(依赖对象状态) |
操作类数据、工厂方法 |
工具函数、逻辑验证(不依赖类和实例状态) |
详细示例1:学生类
让我们用一个 Student 类来展示三种方法的实际应用场景。
class Student:
school = "第一中学" # 类变量
def __init__(self, name, grade):
self.name = name # 实例变量
self.grade = grade
def display_info(self):
# 实例方法,可访问实例变量和类变量
print(f"姓名:{self.name},年级:{self.grade},学校:{self.school}")
@classmethod
def change_school(cls, new_school):
# 类方法,修改类变量
cls.school = new_school
@classmethod
def from_string(cls, info_str):
# 类方法作为工厂方法,创建实例
name, grade = info_str.split(',')
return cls(name, int(grade))
@staticmethod
def is_valid_grade(grade):
# 静态方法,验证年级是否有效
return 1 <= grade <= 12
# 使用实例方法
s1 = Student("小明", 5)
s1.display_info() # 姓名:小明,年级:5,学校:第一中学
# 使用类方法修改类变量
Student.change_school("第二中学")
s2 = Student("小红", 3)
s2.display_info() # 姓名:小红,年级:3,学校:第二中学
# 使用类方法创建对象
s3 = Student.from_string("小刚,8")
s3.display_info() # 姓名:小刚,年级:8,学校:第二中学
# 使用静态方法
print(Student.is_valid_grade(10)) # True
print(Student.is_valid_grade(15)) # False
详细示例2:图书馆类(综合应用)
这个例子更复杂,展示了实例变量、类变量以及各种方法如何协同工作。
class Library:
# 类变量:所有图书馆共享的馆藏数量
total_books = 0
def __init__(self, name):
self.name = name # 实例变量
self.books = [] # 实例变量(独立于其他图书馆)
Library.total_books += 1 # 更新类变量
# 实例方法:添加书籍(操作实例变量)
def add_book(self, book):
self.books.append(book)
return f"Added '{book}' to {self.name} library."
# 类方法:获取所有图书馆总数(操作类变量)
@classmethod
def get_total_libraries(cls):
return f"Total libraries: {cls.total_books}"
# 类方法:创建新图书馆(工厂方法)
@classmethod
def create_public_library(cls, name):
return cls(name) # 调用__init__创建新实例
# 创建图书馆
public_lib = Library("City Public Library")
university_lib = Library("University Library")
# 验证实例变量
print(public_lib.name) # 输出: City Public Library
print(university_lib.books) # 输出: [] (独立实例变量)
# 验证类变量
print(Library.total_books) # 输出: 2 (类变量更新)
print(public_lib.get_total_libraries()) # 输出: Total libraries: 2 (类方法)
# 使用类方法创建新图书馆
new_lib = Library.create_public_library("County Library")
print(new_lib.name) # 输出: County Library
print(Library.total_books) # 输出: 3 (类变量更新)
✅ 验证:
total_books 类变量被所有实例共享并正确更新。
- 每个图书馆的
books 列表是完全独立的。
create_public_library 作为工厂类方法,成功地创建了新的实例。
10. 特殊方法(魔术方法)
Python 的特殊方法,也称为魔术方法,是以双下划线开头和结尾的方法(如 __init__)。它们赋予了自定义类“超能力”,让你可以定义对象在与 Python 内置操作符或函数交互时的行为。
10.1 常用特殊方法
__init__(self, ...):构造函数,在对象创建时自动调用。
__str__(self):定义当使用 str() 或 print() 时返回的、对用户友好的字符串。
__repr__(self):定义对象的“官方”字符串表示,通常用于调试,理想情况下应能用来重新创建该对象。
__len__(self):定义 len() 函数的行为。
__call__(self, ...):使对象可以像函数一样被“调用”。
__getitem__(self, key):定义通过 obj[key] 进行索引访问的行为。
__setitem__(self, key, value):定义通过 obj[key] = value 进行赋值的行为。
__eq__(self, other):定义相等比较 == 的行为。
__lt__(self, other):定义小于比较 < 的行为(其他比较操作符类似)。
10.2 示例
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
def __str__(self):
return f"《{self.title}》 by {self.author}"
def __repr__(self):
return f"Book('{self.title}', '{self.author}', {self.pages})"
def __len__(self):
return self.pages
def __call__(self):
print(f"开始阅读《{self.title}》...")
book = Book("Python编程", "张三", 300)
print(str(book)) # 《Python编程》 by 张三
print(repr(book)) # Book('Python编程', '张三', 300)
print(len(book)) # 300
book() # 开始阅读《Python编程》...
通过实现这些特殊方法,我们自定义的 Book 类对象就可以像字符串、列表等内置类型一样,自然地被 print、len 等函数操作,甚至可以被直接“调用”,大大提升了代码的直观性和表达力。
11. 总结回顾
| 概念 |
语法 |
作用 |
独立性 |
调用方式 |
| 实例变量 |
self.attribute = value |
存储对象特定状态 |
每个对象独立 |
通过实例访问(obj.attr) |
| 类变量 |
attribute = value(类体内) |
存储类级别共享数据 |
所有实例共享 |
通过类或实例访问(Class.attr 或 obj.attr) |
| 实例方法 |
def method(self, ...): |
操作实例变量(需 self) |
依赖实例 |
通过实例调用(obj.method()) |
| 类方法 |
@classmethod def method(cls, ...): |
操作类变量或创建新实例 |
依赖 cls |
通过类或实例调用(Class.method() 或 obj.method()) |
12. 常见错误与预防
-
混淆实例变量与类变量
- 错误:意图定义类变量
school = "Harvard",却在 __init__ 里用 self.school 赋值,这实际上为每个实例创建了独立的同名实例变量。
- 正确:类变量直接在类体内定义;实例变量在
__init__ 中通过 self.xxx 定义。
-
该用类方法时误用实例方法
-
在类方法中使用 self
- 错误:
def method(cls, self):(参数命名混乱)。
- 正确:类方法的第一个参数必须是
cls,代表类本身。
-
意外修改类变量
- 现象:
class Config: MAX_SIZE = 100,之后执行 obj.MAX_SIZE = 200。你以为修改了类变量,实际上是为 obj 这个实例创建了一个名为 MAX_SIZE 的实例变量,类变量 Config.MAX_SIZE 依然是 100。
- 注意:通过实例访问并修改类属性时要格外小心。若要修改类变量本身,应通过类名直接修改:
Config.MAX_SIZE = 200。
希望这篇关于 Python 类与对象中各种方法的详解能帮助你理清概念。在实际编程中,根据方法是否需要访问实例状态或类状态来明智地选择,能让你的代码更加清晰和高效。如果你在实践中有更多心得或疑问,欢迎在 云栈社区 的 Python 板块与其他开发者交流探讨。