在 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 没有严格的 private 或 public 关键字,而是依靠命名约定和名称修饰 (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 # ❌ 会抛出 ValueError2. @classmethod 与 @staticmethod
@classmethod:第一个参数是cls(类本身)。常用于替代构造函数(工厂模式)。@staticmethod:没有self或cls参数。只是一个放在类命名空间里的普通函数,与类或实例的状态无关。
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)) # False3. 魔术方法 (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=[])五、 常见陷阱与避坑指南
- 可变默认参数陷阱(在
__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 []- 忘记调用
super().__init__():在多重继承或复杂继承链中,子类__init__必须显式调用super().__init__(),否则父类的初始化逻辑不会执行。 - 过度使用 OOP:Python 支持多范式。如果一个功能用几个简单的函数就能解决,不要强行把它塞进一个类里。Python 之禅:“简单优于复杂”。
总结
Python 的面向对象既保留了经典 OOP 的严谨(通过 abc 和继承),又提供了极大的灵活性(鸭子类型、@property、魔术方法)。
- 存储纯数据 👉 使用
@dataclass - 需要控制属性访问 👉 使用
@property - 需要与内置操作符交互 👉 实现 魔术方法 (
__xx__)
你想深入探讨哪个具体场景?比如:如何实现一个单例模式 (Singleton),或者 @dataclass 的高级用法(如 __post_init__)?随时告诉我!