Python3 面向对象

在 Python 3 中,面向对象编程(OOP) 是一种将数据和操作数据的方法绑定在一起的编程范式。Python 的 OOP 设计哲学是 “实用主义”:它支持所有经典的 OOP 特性,但不强制你过度设计,同时提供了许多独特的语法糖(如 @property、魔术方法)让代码更优雅。

下面我将从基础语法四大核心特性Python 专属高级特性现代最佳实践,为你系统梳理 Python 3 的面向对象编程。


一、 基础语法:类与对象

在 Python 中,使用 class 关键字定义类。__init__ 是构造方法,self 代表实例本身(注意:self 只是约定俗成的名字,不是关键字,但绝对不要改掉它)。

纯文本
class Dog:
    # 1. 类属性:所有实例共享
    species = "Canis familiaris"

    # 2. 实例方法:第一个参数必须是 self
    def __init__(self, name: str, age: int):
        # 实例属性:每个实例独有
        self.name = name
        self.age = age

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

# 实例化对象
my_dog = Dog("Buddy", 3)
print(my_dog.name)       # 输出: Buddy
print(my_dog.bark())     # 输出: Buddy says Woof!
print(my_dog.species)    # 输出: Canis familiaris (也可通过类名 Dog.species 访问)

二、 OOP 四大核心特性在 Python 中的体现

1. 封装 (Encapsulation)

Python 没有严格的 privatepublic 关键字,而是依靠命名约定名称修饰 (Name Mangling) 来实现封装。

  • _attribute (单下划线):约定俗成的私有。告诉其他开发者“这是内部实现,请勿直接访问”,但技术上仍可访问。
  • __attribute (双下划线):名称修饰。Python 会在底层将其重命名为 _ClassName__attribute,防止子类意外覆盖,提供较强的私有性。
纯文本
class BankAccount:
    def __init__(self, owner: str, balance: float):
        self.owner = owner          # 公开属性
        self._account_id = "12345"  # 受保护属性 (约定不直接访问)
        self.__balance = balance    # 私有属性 (名称修饰)

    def deposit(self, amount: float):
        if amount > 0:
            self.__balance += amount

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

acc = BankAccount("Alice", 1000)
# print(acc.__balance)  # ❌ AttributeError
print(acc.get_balance())  # ✅ 1000
print(acc._BankAccount__balance) # ⚠️ 技术上可以这样访问,但强烈不推荐

2. 继承 (Inheritance)

使用 class Child(Parent): 语法。使用 super() 调用父类的方法。

纯文本
class Animal:
    def __init__(self, name: str):
        self.name = name

    def speak(self):
        raise NotImplementedError("子类必须实现此方法")

class Cat(Animal):
    def __init__(self, name: str, color: str):
        super().__init__(name)  # 调用父类的 __init__
        self.color = color

    def speak(self):
        return f"{self.name} says Meow!"

my_cat = Cat("Whiskers", "White")
print(my_cat.speak())  # 输出: Whiskers says Meow!

3. 多态 (Polymorphism)

Python 的多态基于 “鸭子类型” (Duck Typing):“如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子”。Python 不关心对象的类型,只关心它是否有对应的方法

纯文本
def make_it_speak(animal: Animal):
    # 不关心 animal 是 Dog 还是 Cat,只要有 speak() 方法就行
    print(animal.speak())

make_it_speak(my_cat)
make_it_speak(my_dog)

4. 抽象 (Abstraction)

使用内置的 abc (Abstract Base Classes) 模块来强制子类实现特定方法,防止实例化抽象类。

纯文本
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        pass

# shape = Shape()  # ❌ TypeError: 无法实例化抽象类

class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius

    def area(self) -> float:
        return 3.14159 * self.radius ** 2

三、 Python 专属高级特性 (极其重要 🌟)

1. @property:优雅的 Getter/Setter

在 Java 中,你可能需要写 get_balance()set_balance()。在 Python 中,使用 @property 可以将方法伪装成属性,既保持了封装性,又让调用者像访问普通属性一样自然。

纯文本
class Temperature:
    def __init__(self, celsius: float):
        self._celsius = celsius

    @property
    def celsius(self) -> float:
        """Getter: 允许像属性一样读取"""
        return self._celsius

    @celsius.setter
    def celsius(self, value: float):
        """Setter: 允许像属性一样赋值,并加入校验逻辑"""
        if value < -273.15:
            raise ValueError("温度不能低于绝对零度")
        self._celsius = value

temp = Temperature(25)
print(temp.celsius)  # 调用 getter: 25
temp.celsius = 30    # 调用 setter
# temp.celsius = -300 # ❌ 会抛出 ValueError

2. @classmethod@staticmethod

  • @classmethod:第一个参数是 cls (类本身)。常用于替代构造函数(工厂模式)。
  • @staticmethod:没有 selfcls 参数。只是一个放在类命名空间里的普通函数,与类或实例的状态无关。
纯文本
class Date:
    def __init__(self, year: int, month: int, day: int):
        self.year = year
        self.month = month
        self.day = day

    @classmethod
    def from_string(cls, date_str: str):
        """替代构造函数:从字符串 'YYYY-MM-DD' 创建对象"""
        year, month, day = map(int, date_str.split('-'))
        return cls(year, month, day)

    @staticmethod
    def is_valid_date(year: int, month: int, day: int) -> bool:
        """静态方法:纯粹的逻辑校验,不依赖实例或类状态"""
        return 1 <= month <= 12 and 1 <= day <= 31

# 使用类方法创建实例
my_date = Date.from_string("2023-10-25")
print(Date.is_valid_date(2023, 13, 1))  # False

3. 魔术方法 (Dunder Methods)

以双下划线开头和结尾的方法。它们允许你的对象与 Python 的内置操作符无缝集成。

纯文本
class Vector2D:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def __str__(self) -> str:
        return f"Vector({self.x}, {self.y})"  # 给 print() 用

    def __repr__(self) -> str:
        return f"Vector2D(x={self.x}, y={self.y})"  # 给开发者调试用

    def __add__(self, other: 'Vector2D') -> 'Vector2D':
        # 重载 + 运算符
        return Vector2D(self.x + other.x, self.y + other.y)

    def __eq__(self, other: object) -> bool:
        # 重载 == 运算符
        if not isinstance(other, Vector2D):
            return False
        return self.x == other.x and self.y == other.y

v1 = Vector2D(1, 2)
v2 = Vector2D(3, 4)
print(v1 + v2)       # 输出: Vector(4, 6)  (触发了 __str__)
print(v1 == v2)      # 输出: False

四、 现代 Python 3 最佳实践:@dataclass

如果你写类的目的仅仅是为了存储数据(没有复杂的业务逻辑),从 Python 3.7 开始,强烈推荐使用 @dataclass。它会自动为你生成 __init____repr____eq__ 等方法,大幅减少样板代码。

纯文本
from dataclasses import dataclass, field
from typing import List

@dataclass
class User:
    name: str
    age: int
    # 默认值如果是可变对象,必须使用 field(default_factory=...)
    tags: List[str] = field(default_factory=list) 

    # 依然可以定义普通方法
    def add_tag(self, tag: str):
        self.tags.append(tag)

# 自动拥有了完美的 __init__ 和 __repr__
u = User("Alice", 25)
print(u)  # 输出: User(name='Alice', age=25, tags=[])

五、 常见陷阱与避坑指南

  1. 可变默认参数陷阱(在 __init__ 中):
纯文本
   # ❌ 错误:所有实例共享同一个列表
   class Team:
       def __init__(self, members=[]):
           self.members = members

   # ✅ 正确:使用 None 并在内部初始化
   class Team:
       def __init__(self, members=None):
           self.members = members if members is not None else []
  1. 忘记调用 super().__init__():在多重继承或复杂继承链中,子类 __init__ 必须显式调用 super().__init__(),否则父类的初始化逻辑不会执行。
  2. 过度使用 OOP:Python 支持多范式。如果一个功能用几个简单的函数就能解决,不要强行把它塞进一个类里。Python 之禅:“简单优于复杂”。

总结

Python 的面向对象既保留了经典 OOP 的严谨(通过 abc 和继承),又提供了极大的灵活性(鸭子类型、@property、魔术方法)。

  • 存储纯数据 👉 使用 @dataclass
  • 需要控制属性访问 👉 使用 @property
  • 需要与内置操作符交互 👉 实现 魔术方法 (__xx__)

你想深入探讨哪个具体场景?比如:如何实现一个单例模式 (Singleton),或者 @dataclass 的高级用法(如 __post_init__?随时告诉我!