第一次接触面向对象编程(OOP)时,我完全无法理解为什么要把代码写得这么"绕"。直到参与了一个真实的电商系统开发,才明白这种编程范式在复杂业务场景下的价值。面向对象不是语法糖,而是一种组织代码的思维方式。
在过程式编程中,我们关注的是"怎么做"——先做什么,再做什么。而面向对象关注的是"谁来做"——把数据和操作数据的方法绑定在一起,形成独立的逻辑单元。就像现实世界中,我们不会把"人"拆解成"姓名数据"和"走路方法",而是自然地认为"人"这个实体本身就具备这些属性和能力。
Python作为多范式语言,其面向对象实现既保持了简洁性,又提供了完整的OOP特性支持。不同于Java等语言的严格OOP要求,Python允许你根据场景灵活选择编程范式。这种灵活性让初学者容易上手,但也容易忽视面向对象的精髓。
在Python中,类使用class关键字定义。一个最简单的类定义如下:
python复制class User:
pass
这个空类已经具备类的基本特性。我们可以通过类名加括号的方式创建实例:
python复制user1 = User()
user2 = User()
这里user1和user2就是User类的两个独立实例。它们在内存中占用不同空间,就像现实世界中两个同型号但独立的设备。
类的真正价值在于封装数据和行为。我们给User类添加属性和方法:
python复制class User:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
print(f"Hello, I'm {self.name}, {self.age} years old.")
__init__是Python中特殊的初始化方法,在实例创建时自动调用。self参数指向实例本身,相当于其他语言中的this。
属性分为实例属性和类属性两种:
python复制class Config:
# 类属性
default_timeout = 10
def __init__(self, env):
# 实例属性
self.env = env
类属性被所有实例共享,而实例属性属于特定对象。修改类属性会影响所有实例:
python复制config1 = Config('dev')
config2 = Config('prod')
Config.default_timeout = 20 # 影响所有实例
config1.default_timeout # 20
注意:当实例属性与类属性同名时,实例属性会覆盖类属性。这种特性容易引发难以排查的问题,建议避免属性名重复。
封装不仅仅是把数据和方法放在一起,更重要的是控制访问权限。Python通过命名约定实现封装:
_var:提示这是内部使用,但外部仍可访问__var:触发名称改写(name mangling),防止意外覆盖__var__:特殊方法,不要自行定义python复制class BankAccount:
def __init__(self, balance):
self.__balance = balance # 私有属性
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def get_balance(self):
return self.__balance
这种设计防止了外部代码直接修改余额,必须通过定义好的方法操作,确保了数据安全。
继承实现代码复用,但过度使用会导致设计僵化。Python支持多重继承,增加了灵活性也带来了复杂性。
python复制class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
使用super()调用父类方法:
python复制class Logger:
def log(self, message):
print(f"Log: {message}")
class DatabaseLogger(Logger):
def log(self, message):
super().log(message)
print(f"Also saving to database: {message}")
经验:优先使用组合而非继承。继承关系应该是"是一个"的关系,而不是"有一个"。
Python通过"鸭子类型"实现多态——只要对象实现了预期的方法,就可以被视为特定类型:
python复制class PDFExporter:
def export(self, data):
print(f"Exporting {data} to PDF")
class CSVExporter:
def export(self, data):
print(f"Exporting {data} to CSV")
def run_export(exporter, data):
exporter.export(data)
这里run_export不关心exporter的具体类型,只要它有export方法即可。
python复制class DateUtil:
@classmethod
def from_string(cls, date_str):
year, month, day = map(int, date_str.split('-'))
return cls(year, month, day)
@staticmethod
def is_valid_date(date_str):
try:
year, month, day = map(int, date_str.split('-'))
return True
except:
return False
@classmethod)第一个参数是类本身,常用于替代构造函数@staticmethod)与类和实例都无关,只是逻辑上属于这个类python复制class Circle:
def __init__(self, radius):
self.radius = radius
@property
def diameter(self):
return 2 * self.radius
@diameter.setter
def diameter(self, value):
self.radius = value / 2
@property把方法变成属性式访问,@xxx.setter定义设置逻辑,实现了更精细的属性控制。
魔术方法(双下划线方法)让自定义类拥有内置类型的行为:
python复制class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
常用魔术方法包括:
__str__:str(obj)和print(obj)时调用__repr__:解释器显示和repr(obj)时调用__len__:len(obj)时调用__getitem__:obj[key]时调用一个类应该只有一个引起变化的原因。违反这一原则的典型表现是"上帝类":
python复制# 不好的设计
class UserManager:
def __init__(self):
self.users = []
def add_user(self, user):
pass
def delete_user(self, user_id):
pass
def send_email(self, user_id, content):
pass
def generate_report(self):
pass
改进方案:
python复制class UserRepository:
def __init__(self):
self.users = []
def add_user(self, user):
pass
def delete_user(self, user_id):
pass
class EmailService:
def send_email(self, user, content):
pass
class ReportGenerator:
def generate_user_report(self, users):
pass
python复制class Database:
def query(self, sql):
pass
class UserService:
def __init__(self, database):
self.db = database
def get_user(self, user_id):
return self.db.query(f"SELECT * FROM users WHERE id = {user_id}")
# 使用
db = Database()
user_service = UserService(db)
这种方式解耦了UserService和具体数据库实现,便于测试和维护。
abc模块定义抽象基类:
python复制from abc import ABC, abstractmethod
class Renderer(ABC):
@abstractmethod
def render(self, data):
pass
class HTMLRenderer(Renderer):
def render(self, data):
return f"<div>{data}</div>"
抽象基类强制子类实现特定方法,否则在实例化时会报错。
当两个模块相互导入时会导致循环导入。解决方案:
钻石继承问题:
python复制class A:
def method(self):
print("A")
class B(A):
def method(self):
print("B")
super().method()
class C(A):
def method(self):
print("C")
super().method()
class D(B, C):
pass
D().method()
输出结果为:
code复制B
C
A
Python使用C3线性化算法确定方法解析顺序(MRO),可通过D.__mro__查看。
动态添加属性可能导致难以预料的行为。使用__slots__限制实例属性:
python复制class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
__slots__能节省内存,但会破坏一些动态特性如pickle。
对于简单数据结构,考虑使用collections.namedtuple:
python复制from collections import namedtuple
User = namedtuple('User', ['name', 'age'])
user = User('Alice', 25)
namedtuple创建的是元组子类,比普通类更轻量。
使用@property实现延迟计算:
python复制class DataSet:
def __init__(self, raw_data):
self.raw_data = raw_data
self._processed = None
@property
def processed(self):
if self._processed is None:
print("Processing data...")
self._processed = self._process_data()
return self._processed
def _process_data(self):
# 耗时的数据处理
return sorted(self.raw_data)
__slots__减少内存对于需要创建大量实例的类,__slots__可以显著减少内存占用:
python复制class Point:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
测试表明,使用__slots__的类实例内存占用可减少40%-50%。
python复制import unittest
class TestUser(unittest.TestCase):
def setUp(self):
self.user = User('Alice', 30)
def test_greet(self):
self.assertEqual(
self.user.greet(),
"Hello, I'm Alice, 30 years old."
)
def test_age_next_year(self):
self.user.age += 1
self.assertEqual(self.user.age, 31)
python复制from unittest.mock import Mock
def test_send_email():
mock_email = Mock()
user = User('Bob', 25, email_service=mock_email)
user.notify("Hello")
mock_email.send.assert_called_with('Bob', 'Hello')
使用hypothesis进行属性测试:
python复制from hypothesis import given
import hypothesis.strategies as st
@given(st.text(), st.integers(min_value=0))
def test_user_creation(name, age):
user = User(name, age)
assert user.name == name
assert user.age == age
这种测试方法能自动生成大量测试用例,发现边界情况问题。