面向对象编程(Object-Oriented Programming,OOP)是现代编程中最重要的编程范式之一。Python 从设计之初就是一门面向对象的语言——在 Python 中,一切皆对象。本篇将从最基础的概念开始,带你彻底掌握 Python 中的类与对象。

面向对象编程概述

面向对象编程的核心思想是:将数据和操作数据的方法封装在一起,形成"对象"。与面向过程编程(以函数为中心)不同,OOP 以"类"和"对象"为中心。

四大基本特性

  • 封装(Encapsulation):将数据和内部实现细节隐藏,对外只暴露必要的接口
  • 继承(Inheritance):子类可以复用父类的属性和方法
  • 多态(Polymorphism):同一接口在不同对象上表现不同行为
  • 抽象(Abstraction):隐藏复杂实现,只暴露简化接口

类的定义与实例化

最简单的类

class Dog:
    """一个简单的 Dog 类"""
    pass


# 实例化对象
my_dog = Dog()
print(type(my_dog))          # <class '__main__.Dog'>
print(isinstance(my_dog, Dog))  # True

class 关键字用于定义一个类。类名通常使用 PascalCase 命名规范。pass 表示空语句块,用来占位。

构造函数 __init__

__init__ 是一个特殊方法(魔法方法),在创建对象时自动调用,用于初始化实例属性:

class Dog:
    def __init__(self, name: str, age: int):
        self.name = name    # 实例属性
        self.age = age      # 实例属性

    def bark(self) -> str:
        return f"{self.name} says: Woof!"


dog1 = Dog("Buddy", 3)
dog2 = Dog("Lucy", 5)

print(dog1.bark())         # Buddy says: Woof!
print(dog2.bark())         # Lucy says: Woof!
print(dog1.name, dog1.age) # Buddy 3

要点:

  • __init__ 的第一个参数必须是 self,代表实例本身
  • self 是约定名称,传参时不用写,Python 自动传入
  • __init__ 不能有 return 语句(除了返回 None

self 参数详解

self 指向实例本身,所有实例方法都必须以 self 作为第一个参数:

class Counter:
    def __init__(self):
        self.count = 0

    def increment(self):
        self.count += 1

    def get_count(self):
        return self.count


c = Counter()
c.increment()
c.increment()
print(c.get_count())  # 2

# self 的等价调用方式
Counter.increment(c)   # 手动传实例,效果等同 c.increment()
print(c.get_count())   # 3

实例属性 vs 类属性

class Employee:
    # 类属性 —— 所有实例共享
    company_name = "TechCorp"
    employee_count = 0

    def __init__(self, name: str, salary: float):
        # 实例属性 —— 每个实例独有
        self.name = name
        self.salary = salary
        Employee.employee_count += 1


e1 = Employee("Alice", 8000)
e2 = Employee("Bob", 9000)

# 访问类属性
print(Employee.company_name)  # TechCorp
print(e1.company_name)        # TechCorp(实例也能访问)

# 类属性被所有实例共享
print(Employee.employee_count)  # 2

# 小心:通过实例修改类属性
e1.company_name = "NewCorp"    # 这实际上创建了一个实例属性!
print(e1.company_name)         # NewCorp(实例属性覆盖了类属性)
print(e2.company_name)         # TechCorp(类属性未受影响)
print(Employee.company_name)   # TechCorp(类属性未受影响)

# 正确修改类属性的方式
Employee.company_name = "BigCorp"
print(e2.company_name)         # BigCorp

属性的查找顺序

Python 查找属性时遵循 LEGB 规则在类上的变体

  1. 实例属性(instance.__dict__
  2. 类属性(class.__dict__
  3. 父类属性(沿 MRO 链查找)
class Parent:
    value = "parent"

class Child(Parent):
    pass

obj = Child()
print(obj.value)  # parent

obj.value = "instance"
print(obj.value)  # instance(实例属性覆盖了类属性)

del obj.value
print(obj.value)  # parent(删除了实例属性,回到类属性)

实例方法、类方法和静态方法

Python 类中有三种方法:

class MethodDemo:
    class_var = "class var"

    def __init__(self, value: str):
        self.value = value

    # 1. 实例方法 —— 操作实例数据
    def instance_method(self):
        return f"Instance: {self.value}"

    # 2. 类方法 —— 操作类数据
    @classmethod
    def class_method(cls):
        return f"Class: {cls.class_var}"

    # 3. 静态方法 —— 工具函数,与类相关但不依赖实例或类
    @staticmethod
    def static_method(x: int, y: int) -> int:
        return x + y


obj = MethodDemo("hello")

# 调用方式对比
print(obj.instance_method())  # Instance: hello
print(MethodDemo.class_method())  # Class: class var
print(obj.class_method())     # Class: class var(实例也能调用类方法)
print(MethodDemo.static_method(3, 5))  # 8
print(obj.static_method(3, 5))  # 8

典型使用场景

class Date:
    def __init__(self, year: int, month: int, day: int):
        self.year = year
        self.month = month
        self.day = day

    # 实例方法
    def display(self) -> str:
        return f"{self.year}-{self.month:02d}-{self.day:02d}"

    # 类方法 —— 替代构造函数
    @classmethod
    def from_string(cls, date_str: str) -> "Date":
        parts = date_str.split("-")
        return cls(int(parts[0]), int(parts[1]), int(parts[2]))

    # 静态方法 —— 校验工具
    @staticmethod
    def is_valid_date(year: int, month: int, day: int) -> bool:
        if month < 1 or month > 12:
            return False
        if day < 1 or day > 31:
            return False
        return True


d = Date.from_string("2026-05-15")
print(d.display())                     # 2026-05-15
print(Date.is_valid_date(2026, 13, 1)) # False

访问控制与命名约定

Python 没有真正的 private / protected 关键字,而是通过命名约定来控制访问:

class AccessDemo:
    def __init__(self):
        self.public = "任何人都可以访问"
        self._protected = "子类可以访问(约定)"
        self.__private = "不要直接访问(名称修饰)"

    def get_private(self):
        return self.__private


obj = AccessDemo()

# 公开属性 —— 自由访问
print(obj.public)  # 任何人都可以访问

# 保护属性 —— 约定,但不强制执行
print(obj._protected)  # 能访问,但不建议

# 私有属性 —— 名称修饰(name mangling)
# print(obj.__private)  # AttributeError!
print(obj.get_private())       # 不要直接访问(名称修饰)
print(obj._AccessDemo__private)  # 能访问,但强烈不建议!

名称修饰(Name Mangling)机制

Python 会将 __双下划线开头的属性名 改写为 _ClassName__属性名

class Parent:
    def __init__(self):
        self.__secret = "parent secret"

    def reveal(self):
        return self.__secret


class Child(Parent):
    def __init__(self):
        super().__init__()
        self.__secret = "child secret"  # 实际上是 _Child__secret


c = Child()
print(c._Parent__secret)  # parent secret(父类的私有属性)
print(c._Child__secret)   # child secret(子类的私有属性)

这种机制主要是为了避免子类意外覆盖父类的私有属性。

__str____repr__

这两个方法控制对象的字符串表示:

class Book:
    def __init__(self, title: str, author: str, pages: int):
        self.title = title
        self.author = author
        self.pages = pages

    # 面向开发者的表示 —— 调试用,尽可能包含足够信息
    def __repr__(self) -> str:
        return f"Book('{self.title}', '{self.author}', {self.pages})"

    # 面向用户的表示 —— 可读性强
    def __str__(self) -> str:
        return f"《{self.title}》by {self.author}"


book = Book("Python从入门到精通", "张三", 500)

print(repr(book))    # Book('Python从入门到精通', '张三', 500)
print(str(book))     # 《Python从入门到精通》by 张三
print(book)          # 《Python从入门到精通》by 张三(自动调用 str)

最佳实践:

  • __repr__ 应该返回一个可以用来重建对象的表达式字符串
  • __str__ 返回用户友好的描述
  • 如果只实现一个,实现 __repr__——Python 在缺少 __str__ 时会回退到 __repr__

isinstance 与 issubclass

class Animal:
    pass

class Mammal(Animal):
    pass

class Dog(Mammal):
    pass


dog = Dog()

# isinstance:检查对象是否属于某个类(或父类)
print(isinstance(dog, Dog))      # True
print(isinstance(dog, Mammal))   # True(继承关系)
print(isinstance(dog, Animal))   # True(继承关系)
print(isinstance(dog, (Dog, Cat)))  # True(元组中的任意一个匹配即可)

# issubclass:检查类之间的继承关系
print(issubclass(Dog, Mammal))   # True
print(issubclass(Mammal, Animal))  # True
print(issubclass(Dog, Animal))   # True(传递性)
print(issubclass(Dog, Dog))      # True(类是其自身的子类)

对象比较:__eq__

默认情况下,== 比较的是对象的**身份(id)**而非内容:

class Point:
    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y


p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = p1

print(p1 == p2)  # False(不同对象,即使内容相同)
print(p1 == p3)  # True(同一对象)
print(p1 is p2)  # False(身份不同)

通过 __eq__ 自定义相等逻辑:

class Point:
    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __eq__(self, other) -> bool:
        if not isinstance(other, Point):
            return NotImplemented
        return self.x == other.x and self.y == other.y

    def __repr__(self) -> str:
        return f"Point({self.x}, {self.y})"


p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)

print(p1 == p2)  # True(内容相同)
print(p1 == p3)  # False(内容不同)
print(p1 == 42)  # False(类型不同,返回 NotImplemented)

Getter/Setter 模式

在简单的场景下,直接访问属性就足够了。当需要控制访问逻辑时,可以用方法:

class Temperature:
    def __init__(self, celsius: float = 0):
        self._celsius = celsius

    # Getter
    def get_celsius(self) -> float:
        return self._celsius

    # Setter(带校验)
    def set_celsius(self, value: float) -> None:
        if value < -273.15:
            raise ValueError(f"温度不能低于绝对零度: {value}")
        self._celsius = value

    # 计算属性
    def get_fahrenheit(self) -> float:
        return self._celsius * 9 / 5 + 32


t = Temperature(25)
print(t.get_celsius())     # 25
print(t.get_fahrenheit())  # 77.0

t.set_celsius(100)
print(t.get_celsius())     # 100

# t.set_celsius(-500)      # ValueError!

在后面的文章中,我们会学习使用 @property 装饰器来实现更优雅的 getter/setter。

综合示例:银行账户系统

将上述知识点整合到一个完整的示例中:

class BankAccount:
    """银行账户类"""
    # 类属性
    bank_name = "Python Bank"
    interest_rate = 0.03  # 3% 年利率
    _total_accounts = 0

    def __init__(self, owner: str, initial_balance: float = 0):
        self.owner = owner
        self._balance = initial_balance
        self._transactions: list[str] = []
        BankAccount._total_accounts += 1
        self._account_number = f"PB{BankAccount._total_accounts:06d}"

    # 实例方法
    def deposit(self, amount: float) -> float:
        if amount <= 0:
            raise ValueError("存款金额必须为正数")
        self._balance += amount
        self._transactions.append(f"存款: +{amount}")
        return self._balance

    def withdraw(self, amount: float) -> float:
        if amount <= 0:
            raise ValueError("取款金额必须为正数")
        if amount > self._balance:
            raise ValueError("余额不足")
        self._balance -= amount
        self._transactions.append(f"取款: -{amount}")
        return self._balance

    def get_balance(self) -> float:
        return self._balance

    def get_transactions(self) -> list[str]:
        return self._transactions.copy()  # 返回副本防止外部修改

    # 类方法
    @classmethod
    def create_savings_account(cls, owner: str) -> "BankAccount":
        """创建一个有初始存款的储蓄账户"""
        return cls(owner, 1000)

    @classmethod
    def get_total_accounts(cls) -> int:
        return cls._total_accounts

    @classmethod
    def update_interest_rate(cls, new_rate: float) -> None:
        if new_rate < 0 or new_rate > 1:
            raise ValueError("利率必须在 0-1 之间")
        cls.interest_rate = new_rate

    # 静态方法
    @staticmethod
    def validate_account_number(account_num: str) -> bool:
        return account_num.startswith("PB") and len(account_num) == 8

    # 字符串表示
    def __repr__(self) -> str:
        return f"BankAccount('{self.owner}', {self._balance})"

    def __str__(self) -> str:
        return f"账户[{self._account_number}] {self.owner}: ¥{self._balance:.2f}"

    def __eq__(self, other) -> bool:
        if not isinstance(other, BankAccount):
            return NotImplemented
        return self._account_number == other._account_number


# ---- 使用示例 ----

# 创建账户
alice_acc = BankAccount("Alice", 5000)
bob_acc = BankAccount.create_savings_account("Bob")

# 存款取款
alice_acc.deposit(2000)
alice_acc.withdraw(1500)

# 查看信息
print(alice_acc)                     # 账户[PB000001] Alice: ¥5500.00
print(bob_acc)                       # 账户[PB000002] Bob: ¥1000.00

# 类属性和类方法
print(BankAccount.bank_name)         # Python Bank
print(BankAccount.get_total_accounts())  # 2

# 静态方法
print(BankAccount.validate_account_number("PB000001"))  # True

# 比较
print(alice_acc == bob_acc)          # False
print(alice_acc == alice_acc)        # True

常见陷阱与最佳实践

陷阱 1:可变默认参数

class Bad:
    def __init__(self, items=[]):  # 危险!
        self.items = items

class Good:
    def __init__(self, items=None):
        self.items = items if items is not None else []


a = Bad()
a.items.append(1)
b = Bad()
print(b.items)  # [1] —— 共享了同一个列表!

c = Good()
c.items.append(1)
d = Good()
print(d.items)  # [] —— 互不影响

陷阱 2:混淆实例属性和类属性

class Student:
    grades = []  # 类属性,所有实例共享

    def __init__(self, name: str):
        self.name = name

    def add_grade(self, grade: int):
        self.grades.append(grade)  # 修改的是类属性!


s1 = Student("Alice")
s1.add_grade(90)
s2 = Student("Bob")
print(s2.grades)  # [90] —— 受到了影响!

正确做法:

class Student:
    def __init__(self, name: str):
        self.name = name
        self.grades: list[int] = []  # 实例属性

    def add_grade(self, grade: int):
        self.grades.append(grade)

陷阱 3:用 __ 双下划线过于积极

class TooPrivate:
    def __init__(self):
        self.__value = 42  # 实际很少需要真正的 name mangling

    def __do_something(self):
        pass

大多数时候,_ 单下划线就足够了。__ 只在需要避免子类覆盖时才使用。

小结

本篇我们系统学习了 Python 面向对象编程的基础知识:

  • 类的定义:使用 class 关键字定义类,__init__ 作为构造函数初始化实例
  • self 参数:实例方法的第一个参数,代表实例本身
  • 实例属性 vs 类属性:实例属性每个对象独有,类属性所有实例共享
  • 三种方法:实例方法(操作实例)、类方法(@classmethod 操作类)、静态方法(@staticmethod 工具函数)
  • 访问控制_ 保护属性约定、__ 名称修饰机制
  • 字符串表示__repr__ 面向开发者、__str__ 面向用户
  • 类型检查isinstance 检查对象类型、issubclass 检查继承关系
  • 对象比较:通过 __eq__ 实现基于内容的相等判断

类是 Python 面向对象编程的基石,理解这些概念将为后续的继承、多态、魔法方法等内容做好充分准备。

Summary: 类与对象基础:定义、属性、方法、封装