跳转至

10 类的使用

面向对象编程 (OOP) 是现代软件开发的主流范式之一,而 类 (Class) 则是 OOP 的核心概念。在 Python 中,类为我们提供了一种强大的方式来创建自定义数据类型、封装数据和行为、构建复杂的系统。这篇博客将带你深入探索 Python 类的方方面面。

一、什么是类?对象又是什么?

  • 类 (Class): 可以将其视为一个蓝图模板。它定义了某种事物(例如:DogCarBankAccount应该具有哪些属性(如:名字、颜色、余额)和能够执行哪些操作(如:叫、加速、存款)。类本身只是一个定义,并不占用实际的内存空间来存储具体的数据。
  • 对象 (Object) / 实例 (Instance): 当你根据类这个蓝图实际创建出一个具体的东西时,这个东西就是一个对象实例。例如,根据 Dog 类,你可以创建出名叫 buddy 的狗对象,它有具体的属性值(buddy.name = "Buddy")并能执行具体的方法(buddy.bark())。对象是类的具体化,占用内存存储其属性值。

简单来说:类定义结构,对象是具体存在

二、定义一个简单的类

在 Python 中,使用 class 关键字来定义类,后面跟着类名(通常采用 PascalCase 命名规范,即每个单词首字母大写)。

class Dog:
    pass  # 最简单的类定义,目前什么也没做

现在,我们可以使用这个 Dog 类来创建对象:

my_dog = Dog()  # 创建 Dog 类的一个实例/对象,赋值给变量 my_dog
print(my_dog)  # 输出类似 <__main__.Dog object at 0x000001F2D4C6B310>,表示这是一个 Dog 对象

三、初始化方法:__init__

我们创建了 Dog 对象,但它还没有任何属性(名字、品种等)。我们需要一种在创建对象时就设置初始属性的方法。这就是 __init__ 方法的作用。

__init__ 是一个特殊的构造方法。当你创建类的新实例时(例如 my_dog = Dog()),Python 会自动调用 __init__ 方法。

class Dog:
    def __init__(self, name, breed):
        self.name = name   # 将传入的 name 参数赋值给实例的 name 属性
        self.breed = breed # 将传入的 breed 参数赋值给实例的 breed 属性

# 创建对象时传入初始值
buddy = Dog("Buddy", "Golden Retriever")
fido = Dog("Fido", "Labrador")

print(buddy.name)  # 输出: Buddy
print(fido.breed)  # 输出: Labrador
  • self 参数: 这是 __init__ 方法的第一个参数,它代表当前正在创建的实例对象本身。Python 自动传递这个参数,我们不需要显式传入它(例如 buddy = Dog("Buddy", "Golden Retriever"),没有给 self 传值)。
  • self.nameself.breed 这行代码创建了实例属性self.name = name 意思是“将传入的 name 参数的值,赋给这个特定实例 (self) 的 name 属性”。这样,每个 Dog 对象就有了自己独立的 namebreed

四、实例方法

方法是定义在类内部的函数,用于描述对象可以执行的操作。方法的第一个参数也总是 self,指向调用该方法的实例对象。

class Dog:
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed

    def bark(self):  # 定义 bark 方法
        print(f"{self.name} says: Woof! Woof!")  # 使用实例的属性

    def describe(self):
        print(f"I am a {self.breed} named {self.name}.")

# 使用
buddy = Dog("Buddy", "Golden Retriever")
buddy.bark()      # 输出: Buddy says: Woof! Woof!
buddy.describe()  # 输出: I am a Golden Retriever named Buddy.

方法 bark()describe() 可以访问并操作该实例的属性(self.name, self.breed)。

五、类属性与实例属性

  • 实例属性: 属于特定实例的属性,如上面 buddy.name。每个实例的属性值可以不同。通常在 __init__ 中初始化。
  • 类属性: 属于类本身的属性,所有实例共享。定义在类内部,但在任何方法之外(通常紧跟在 class 语句下方)。
class Dog:
    species = "Canis familiaris"  # 类属性,所有狗共享的物种

    def __init__(self, name, breed):
        self.name = name   # 实例属性
        self.breed = breed # 实例属性

# 访问类属性 (通过类名或实例)
print(Dog.species)       # 输出: Canis familiaris
buddy = Dog("Buddy", "Golden Retriever")
print(buddy.species)     # 输出: Canis familiaris (实例访问类属性)

# 修改类属性会影响所有实例
Dog.species = "Canis lupus familiaris"
print(buddy.species)     # 输出: Canis lupus familiaris

注意:如果通过一个实例去设置一个与类属性同名的属性,Python 会为该实例创建一个新的实例属性,而不会修改类属性。这可能会造成混淆(所以设置实例的时候不要和类名冲突)。

六、继承 (Inheritance)

继承是 OOP 的重要特性,它允许我们创建一个新类(子类派生类)来继承另一个类(父类基类)的属性和方法。子类可以:

  1. 继承父类的所有功能。
  2. 扩展,添加自己新的属性和方法。
  3. 覆盖 (Override),重新定义父类已有的方法以实现不同的行为。
class Animal:  # 父类
    def __init__(self, name):
        self.name = name

    def speak(self):
        print(f"{self.name} makes a sound.")

class Dog(Animal):  # 子类 Dog 继承自 Animal
    def __init__(self, name, breed):
        super().__init__(name)  # 调用父类的 __init__ 初始化 name
        self.breed = breed

    def speak(self):  # 覆盖父类的 speak 方法
        print(f"{self.name} says: Woof! Woof!")

class Cat(Animal):  # 子类 Cat 继承自 Animal
    def speak(self):  # 覆盖父类的 speak 方法
        print(f"{self.name} says: Meow!")

# 使用
generic = Animal("Generic")
generic.speak()  # 输出: Generic makes a sound.

buddy = Dog("Buddy", "Golden")
buddy.speak()    # 输出: Buddy says: Woof! Woof!

whiskers = Cat("Whiskers")
whiskers.speak() # 输出: Whiskers says: Meow!
  • super() 在子类中,super().__init__(name) 调用父类 Animal__init__ 方法来初始化 name 属性,避免了代码重复。
  • 方法覆盖: DogCat 都定义了自己的 speak 方法,覆盖了父类的实现,实现了多态(同一个方法名,不同类的对象调用时产生不同行为)。

七、封装 (Encapsulation)

封装是将数据(属性)和操作数据的方法(方法)捆绑在一起,并隐藏内部实现的细节。在 Python 中,主要通过命名约定来实现访问控制,而不是严格的访问限制(像 Java 的 private 关键字)。

  • 公有 (Public): 属性和方法名直接公开(如 self.name, self.bark())。
  • 保护 (Protected): 约定使用单个下划线开头(如 _internal_data)。这是一种提示,表示“请勿直接访问,除非你知道自己在做什么”,但 Python 并不阻止访问。
  • 私有 (Private): 约定使用双下划线开头(如 __very_secret)。Python 会对这些名称进行名称重整 (Name Mangling),使其在类外部难以直接访问(实际名称会变成 _ClassName__very_secret)。主要用于避免子类意外覆盖内部属性。
class BankAccount:
    def __init__(self, account_holder, initial_balance=0):
        self.account_holder = account_holder  # 公有
        self._account_number = "ACC123456"    # 保护 (约定)
        self.__balance = initial_balance      # 私有 (名称重整)

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            print(f"Deposited ${amount}. New balance: ${self.__balance}")
        else:
            print("Invalid deposit amount.")

    def withdraw(self, amount):
        if 0 < amount <= self.__balance:
            self.__balance -= amount
            print(f"Withdrew ${amount}. New balance: ${self.__balance}")
        else:
            print("Invalid withdrawal amount or insufficient funds.")

    def get_balance(self):  # 提供公有方法来访问私有属性(控制访问)
        return self.__balance

# 使用
account = BankAccount("Alice", 100)
print(account.account_holder)  # Alice (公有,可直接访问)
print(account._account_number) # ACC123456 (保护,虽然能访问,但不建议)
# print(account.__balance)     # 直接访问会报错: AttributeError
print(account.get_balance())   # 100 (通过方法访问)
account.deposit(50)            # Deposited $50. New balance: $150
account.withdraw(30)           # Withdrew $30. New balance: $120
account.__balance = 1000       # 这样做不会修改真正的私有变量,只是创建了一个新的公有属性
print(account.get_balance())   # 还是 120
print(account.__balance)       # 1000 (新创建的公有属性),和私有属性不一样,只是一种约定,不建议直接访问
account.deposit(500)           # Deposited $500. New balance: $620
print(account.get_balance())   # 620,可以看到方法修改的还是私有属性

八、特殊方法

特殊方法是以双下划线开头和结尾的方法(如 __init__, __str__, __len__, __add__)。它们由 Python 解释器在特定场景下自动调用,让我们自定义类的行为以更自然地融入 Python 环境。

  • __str__(self): 定义对象的“非正式”字符串表示。当使用 print(obj)str(obj) 时调用,通常返回易于人类阅读的信息。
  • __len__(self): 定义对象的“长度”。当使用 len(obj) 时调用。
  • __add__(self, other): 定义 + 操作符的行为(obj1 + obj2)。
  • __eq__(self, other): 定义 == 操作符的行为。
  • ... 还有很多,如 __getitem__, __setitem__, __call__ 等。
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"Vector({self.x}, {self.y})"  # 用户友好的表示

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

# 使用
v1 = Vector(2, 3)
v2 = Vector(4, 5)
v3 = v1 + v2  # 调用 v1.__add__(v2)
print(v3)     # 输出: Vector(6, 8) (调用 __str__)
print(v1 == Vector(2, 3))  # 输出: True (调用 __eq__)

九、实际案例:一个简单的计算器类

class Calculator:
    def __init__(self):
        self.memory = 0  # 存储计算结果

    def add(self, a, b):
        result = a + b
        self.memory = result
        return result

    def subtract(self, a, b):
        result = a - b
        self.memory = result
        return result

    def multiply(self, a, b):
        result = a * b
        self.memory = result
        return result

    def divide(self, a, b):
        if b == 0:
            raise ValueError("Cannot divide by zero!")
        result = a / b
        self.memory = result
        return result

    def recall_memory(self):
        return self.memory

    def clear_memory(self):
        self.memory = 0

# 使用
calc = Calculator()
print(calc.add(10, 5))        # 输出: 15
print(calc.multiply(3, 4))     # 输出: 12
print(calc.recall_memory())    # 输出: 12 (记住最后一次操作结果)
calc.clear_memory()
print(calc.recall_memory())    # 输出: 0

Python 的类是实现面向对象编程的强大工具。通过理解类、对象、__init__、实例方法、类属性、实例属性、继承、封装以及特殊方法,你可以构建出结构清晰、可复用、可维护的代码。类让你能够更好地模拟现实世界中的实体和关系,是编写复杂 Python 应用程序的基础。不断实践,尝试用类来解决你遇到的问题!