跳到主要内容Python 面向对象进阶:封装、继承、多态与元类详解 | 极客日志Python算法
Python 面向对象进阶:封装、继承、多态与元类详解
综述由AI生成深入讲解 Python 面向对象编程(OOP)的核心概念。涵盖类与对象的基础定义、内存布局、三大特性(封装、继承、多态)。详细解析方法解析顺序(MRO)、描述符机制及其与 property 的关系、属性查找流程。此外,还介绍了元类的原理与应用,以及魔法方法的使用。最后通过 RPG 战斗系统示例综合实践,并给出避免过度设计和合理使用继承的建议。
云间运维25 浏览 1. 核心问题:为什么我们要搞 OOP?
面向对象编程(Object-Oriented Programming, OOP)不是为了把简单的事情搞复杂,而是为了在大规模协作时,代码不至于变成一团乱麻。
- 过程式编程:像是在写一份详细的"炒菜步骤"。
- 面向对象编程:像是在雇佣一个"专业厨师"。你不需要知道他怎么切菜、怎么控温,你只需要告诉他:"厨师,给我来份宫保鸡丁!"
本文将探讨如何从编写步骤转变为设计对象。
2. 基础:类(Class)与对象(Object)
2.1 模具与成品
- 类 (Class):就是一张"图纸"或者一个"模具"。它定义了东西长啥样,能干啥。
- 对象 (Object):就是根据图纸造出来的"实物"。
class Robot:
def __init__(self, name, version):
self.name = name
self.version = version
def say_hello(self):
print(f"你好!我是机器人 {self.name},版本号:{self.version}")
r1 = Robot("小爱", "v1.0")
r2 = Robot("小度", "v2.0")
r1.say_hello()
r2.say_hello()
2.2 self 的作用
self 就像是函数里的一个占位符。当你喊 r1.say_hello() 时,Python 自动把 r1 这个实例传给了 self。这样函数才知道该打印"小爱"还是"小度"。
2.3 类与对象的内存布局
理解内存布局是掌握面向对象的关键。让我们看看 Python 中类和对象是如何在内存中存储的:
class Person:
species = "Homo sapiens"
def __init__(self, name, age):
self.name = name
.age = age
():
p1 = Person(, )
p2 = Person(, )
(Person.species)
(p1.species)
(p2.species)
Person.species =
(p1.species)
p1.species =
(p1.species)
(p2.species)
(Person.species)
self
def
greet
self
return
f"Hello, I'm {self.name}"
"Alice"
25
"Bob"
30
print
print
print
"Human"
print
"Modified"
print
print
print
- 类对象:存储类属性、方法定义,只有一份
- 实例对象:存储实例属性,每个实例独立一份
- 方法查找:实例方法实际存储在类中,实例通过
__class__ 引用找到方法
3. 三大特性:封装、继承、多态
3.1 封装 (Encapsulation):把脏活累活藏起来
你不希望别人随便改你的内部数据。在 Python 里,用双下划线 __ 就能把变量藏起来。
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"存入成功,当前余额:{self.__balance}")
def get_balance(self):
return self.__balance
account = BankAccount(1000)
print(account.get_balance())
3.2 继承 (Inheritance):少写代码的捷径
如果你要写"猫"和"狗",它们都有"吃"和"睡"的功能,那就先写个"动物"类。
class Animal:
def eat(self):
print("正在吃东西...")
class Dog(Animal):
def bark(self):
print("汪汪汪!")
my_dog = Dog()
my_dog.eat()
my_dog.bark()
3.3 多态 (Polymorphism):鸭子类型
Python 的多态特别灵活。只要你长得像鸭子,叫起来也像鸭子,那我就把你当鸭子。
4. 深入:MRO(方法解析顺序)算法详解
当类存在多重继承时,Python 如何决定调用哪个方法?这就是 MRO(Method Resolution Order)要解决的问题。
4.1 什么是 MRO
MRO 定义了 Python 查找方法和属性的顺序。Python 3 使用 C3 线性化算法 来计算 MRO。
class A:
def method(self):
print("A.method")
class B(A):
def method(self):
print("B.method")
class C(A):
def method(self):
print("C.method")
class D(B, C):
pass
print(D.__mro__)
4.2 C3 线性化算法的核心规则
- 子类优先于父类:先查找子类,再查找父类
- 单调性:如果 C 是 D 的父类,那么在 C 的 MRO 中出现的类,在 D 的 MRO 中也以相同顺序出现
- 从左到右:按照继承列表中的顺序查找
class Base:
def greet(self):
print("Base greet")
class Mixin1(Base):
def greet(self):
print("Mixin1 greet")
super().greet()
class Mixin2(Base):
def greet(self):
print("Mixin2 greet")
super().greet()
class Combined(Mixin1, Mixin2):
def greet(self):
print("Combined greet")
super().greet()
c = Combined()
c.greet()
4.3 使用 super() 的正确姿势
super() 不是调用父类,而是按照 MRO 顺序调用下一个类。
class A:
def __init__(self):
print("A.__init__")
super().__init__()
class B(A):
def __init__(self):
print("B.__init__")
super().__init__()
class C(A):
def __init__(self):
print("C.__init__")
super().__init__()
class D(B, C):
def __init__(self):
print("D.__init__")
super().__init__()
d = D()
5. 进阶:描述符(Descriptor)机制
描述符是 Python 中实现属性访问控制的核心机制。理解描述符,你就理解了 @property、@staticmethod、@classmethod 的本质。
5.1 什么是描述符
描述符协议:实现了 __get__、__set__ 或 __delete__ 方法的类就是描述符。
class Validator:
"""一个描述符,用于验证属性值"""
def __init__(self, min_value, max_value):
self.min_value = min_value
self.max_value = max_value
self.name = None
def __set_name__(self, owner, name):
"""当描述符被赋值给类属性时调用"""
self.name = name
self.storage_name = f"_{name}"
def __get__(self, instance, owner):
"""获取属性值时调用"""
if instance is None:
return self
return getattr(instance, self.storage_name, None)
def __set__(self, instance, value):
"""设置属性值时调用"""
if not isinstance(value, (int, float)):
raise TypeError(f"{self.name} must be a number")
if not self.min_value <= value <= self.max_value:
raise ValueError(f"{self.name} must be between {self.min_value} and {self.max_value}")
setattr(instance, self.storage_name, value)
class Person:
age = Validator(0, 150)
salary = Validator(0, 1000000)
def __init__(self, name, age, salary):
self.name = name
self.age = age
self.salary = salary
p = Person("Alice", 25, 50000)
print(p.age)
5.2 描述符的类型
| 类型 | 实现的方法 | 说明 |
|---|
| 非数据描述符 | 只有 __get__ | 实例属性优先于描述符 |
| 数据描述符 | 有 __set__ 或 __delete__ | 描述符优先于实例属性 |
class NonDataDescriptor:
"""非数据描述符"""
def __get__(self, instance, owner):
return "from descriptor"
class DataDescriptor:
"""数据描述符"""
def __get__(self, instance, owner):
return "from descriptor"
def __set__(self, instance, value):
print("Setting value")
class TestNonData:
attr = NonDataDescriptor()
class TestData:
attr = DataDescriptor()
t1 = TestNonData()
t1.attr = "from instance"
print(t1.attr)
t2 = TestData()
t2.attr = "from instance"
print(t2.attr)
5.3 property 的本质
class Property:
def __init__(self, fget=None, fset=None, fdel=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self, instance, owner):
if instance is None:
return self
if self.fget is None:
raise AttributeError("can't get attribute")
return self.fget(instance)
def __set__(self, instance, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(instance, value)
def setter(self, fset):
self.fset = fset
return self
class Circle:
def __init__(self, radius):
self._radius = radius
@Property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
6. 进阶:属性查找机制
理解 Python 如何查找属性,是掌握面向对象编程的关键。
6.1 属性查找顺序
对于 obj.attr,Python 按以下顺序查找:
- 数据描述符:在
obj.__class__ 中查找 attr,如果是数据描述符,返回描述符的值
- 实例字典:在
obj.__dict__ 中查找 attr
- 非数据描述符:在
obj.__class__ 及其父类的 __dict__ 中查找 attr
- 类属性:在
obj.__class__ 及其父类的 __dict__ 中查找 attr
__getattr__:如果以上都没找到,调用 __getattr__
class Descriptor:
"""数据描述符"""
def __get__(self, instance, owner):
return "descriptor value"
def __set__(self, instance, value):
pass
class MyClass:
attr = Descriptor()
def __init__(self):
self.__dict__['attr'] = "instance value"
def __getattr__(self, name):
return f"{name} not found, using getattr"
obj = MyClass()
print(obj.attr)
class NonDataDescriptor:
def __get__(self, instance, owner):
return "descriptor value"
class MyClass2:
attr = NonDataDescriptor()
def __init__(self):
self.attr = "instance value"
obj2 = MyClass2()
print(obj2.attr)
6.2 自定义属性访问
class AttributeTracer:
"""追踪所有属性访问"""
def __init__(self):
self._data = {}
def __getattribute__(self, name):
"""拦截所有属性访问"""
if name.startswith('_'):
return object.__getattribute__(self, name)
print(f"Getting: {name}")
return object.__getattribute__(self, '_data').get(name)
def __setattr__(self, name, value):
"""拦截所有属性设置"""
if name.startswith('_'):
object.__setattr__(self, name, value)
else:
print(f"Setting: {name} = {value}")
object.__getattribute__(self, '_data')[name] = value
def __getattr__(self, name):
"""属性不存在时调用"""
print(f"Attribute {name} not found")
return None
obj = AttributeTracer()
obj.x = 10
print(obj.x)
print(obj.y)
7. 进阶:元类(Metaclass)入门
元类是"类的类",它控制类的创建过程。理解元类,你就站在了 Python 面向对象编程的顶端。
7.1 什么是元类
在 Python 中,一切皆对象。类也是对象,而创建类的"东西"就是元类。默认情况下,所有类都是由 type 创建的。
class MyClass:
pass
print(type(MyClass))
MyClass2 = type('MyClass2', (), {})
print(type(MyClass2))
7.2 自定义元类
class SingletonMeta(type):
"""单例模式元类"""
_instances = {}
def __call__(cls, *args, **kwargs):
"""控制实例创建"""
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=SingletonMeta):
"""使用元类实现单例"""
def __init__(self, connection_string):
self.connection_string = connection_string
print(f"Initializing database with {connection_string}")
db1 = Database("postgresql://localhost/db")
db2 = Database("mysql://localhost/db")
print(db1 is db2)
print(db1.connection_string)
7.3 元类的 __new__ 和 __init__
class AutoRegisterMeta(type):
"""自动注册子类的元类"""
registry = {}
def __new__(mcs, name, bases, namespace):
"""创建类对象时调用"""
print(f"Creating class: {name}")
namespace['created_by'] = 'AutoRegisterMeta'
cls = super().__new__(mcs, name, bases, namespace)
if name != 'BaseModel':
mcs.registry[name] = cls
return cls
def __init__(cls, name, bases, namespace):
"""初始化类对象时调用"""
print(f"Initializing class: {name}")
super().__init__(name, bases, namespace)
class BaseModel(metaclass=AutoRegisterMeta):
"""所有模型的基类"""
pass
class User(BaseModel):
pass
class Product(BaseModel):
pass
print(AutoRegisterMeta.registry)
7.4 元类的实际应用
class ORMMeta(type):
"""简化 ORM 模型定义的元类"""
def __new__(mcs, name, bases, namespace):
fields = {}
for key, value in list(namespace.items()):
if isinstance(value, Field):
fields[key] = value
value.name = key
namespace['_fields'] = fields
return super().__new__(mcs, name, bases, namespace)
class Field:
"""字段定义"""
def __init__(self, field_type, nullable=True):
self.field_type = field_type
self.nullable = nullable
self.name = None
def __repr__(self):
return f"Field({self.field_type})"
class Model(metaclass=ORMMeta):
"""ORM 基类"""
def __init__(self, **kwargs):
for name, field in self._fields.items():
value = kwargs.get(name)
if value is None and not field.nullable:
raise ValueError(f"{name} is required")
setattr(self, name, value)
def save(self):
"""模拟保存到数据库"""
data = {name: getattr(self, name) for name in self._fields}
print(f"Saving {self.__class__.__name__}: {data}")
class User(Model):
id = Field(int, nullable=False)
name = Field(str, nullable=False)
email = Field(str)
user = User(id=1, name="Alice", email="[email protected]")
user.save()
8. 魔法方法:让你的类更"Pythonic"
Python 里有很多双下划线开头结尾的方法(Dunder Methods),它们能让你的对象支持 +, -, len() 等操作。
class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
def __str__(self):
return f"《{self.title}》- 共{self.pages}页"
def __len__(self):
return self.pages
def __eq__(self, other):
if not isinstance(other, Book):
return False
return self.title == other.title and self.pages == other.pages
def __add__(self, other):
if isinstance(other, Book):
return Book(f"{self.title} + {other.title}", self.pages + other.pages)
return NotImplemented
my_book = Book("Python 从入门到放弃", 999)
print(my_book)
print(f"这本书厚度:{len(my_book)}")
book2 = Book("Python 从入门到放弃", 999)
print(my_book == book2)
combined = my_book + book2
print(combined)
9. 综合实战:简易 RPG 战斗系统
咱们来个硬核的,用 OOP 写个简单的游戏角色对战逻辑。
import random
class Character:
def __init__(self, name, hp, ad):
self.name = name
self.hp = hp
self.ad = ad
def is_alive(self):
return self.hp > 0
def attack(self, target):
damage = self.ad + random.randint(-5, 5)
print(f"【{self.name}】发起攻击,对【{target.name}】造成了 {damage} 点伤害!")
target.receive_damage(damage)
def receive_damage(self, damage):
self.hp -= damage
if self.hp < 0:
self.hp = 0
print(f"【{self.name}】剩余血量:{self.hp}")
class Warrior(Character):
def __init__(self, name):
super().__init__(name, hp=150, ad=20)
def receive_damage(self, damage):
if random.random() < 0.2:
print(f"【{self.name}】格挡了攻击!")
damage = damage // 2
super().receive_damage(damage)
class Mage(Character):
def __init__(self, name):
super().__init__(name, hp=80, ad=45)
def attack(self, target):
if random.random() < 0.3:
damage = int(self.ad * 1.5) + random.randint(-5, 5)
print(f"【{self.name}】发动暴击!")
else:
damage = self.ad + random.randint(-5, 5)
print(f"【{self.name}】发起攻击,对【{target.name}】造成了 {damage} 点伤害!")
target.receive_damage(damage)
p1 = Warrior("盖伦")
p2 = Mage("拉克丝")
print("--- 战斗开始 ---")
while p1.is_alive() and p2.is_alive():
p1.attack(p2)
if not p2.is_alive():
print(f"\n恭喜【{p1.name}】获得胜利!")
break
p2.attack(p1)
if not p1.is_alive():
print(f"\n恭喜【{p2.name}】获得胜利!")
break
10. 注意事项
- 别为了 OOP 而 OOP:如果一个功能写个函数就能搞定,千万别强行封装个类。过度设计是万恶之源。
- 优先使用组合而非继承:如果你发现继承树超过 3 层,说明你的架构出问题了。记住:"Has-a" 往往比 "Is-a" 更灵活。
self 别漏写:在类的方法里访问属性,必须带 self.,这是新手最容易犯的低级错误。
- 理解 MRO 再使用多重继承:多重继承很强大,但如果不理解 MRO,很容易踩坑。
- 慎用元类:元类是强大的工具,但会增加代码复杂度,只在必要时使用。
11. 总结与实践
OOP 是设计出来的,不是写出来的。动动手,感受一下全局视角。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online