7.python中的面向对象
一、 地基是怎么打的?
在面向对象的世界里,有两个最基础的概念,它们是“抽象”与“具体”的关系:
1. 类 (Class) 与 对象 (Object)
- 类 (Class): 就像是设计图纸或饼干模具。它本身不是能摸得着的实体,而是一个“模板”。它规定了这一类事物应该具备什么特征(属性/数据)和能做什么事(方法/行为)。
- 对象 (Object) / 实例 (Instance): 就像是根据图纸盖出来的真实房子,或用模具按出来的真饼干。对象是实实在在存在的个体。在编程里,“对象”和“实例”通常是同义词。
2. 实例化 (Instantiation)
这个词听起来很专业,其实非常简单。它就是指“拿着图纸盖出房子”或者“拿着模具烤出饼干”的这个动作/过程。在 Python 里,通过“类”创造出一个具体“对象”的过程,就叫做实例化。
3. self 关键字:我是谁?
当一个类(模具)被实例化成多个不同的对象(多块不同的饼干)时,Python 怎么知道当前在对哪块特定的饼干进行操作呢?
这就是 self 的作用。self 永远代表着“当前正在执行动作的那个具体对象本身”。
class Dog:
# __init__ 叫初始化方法。
# 当你“实例化”一只狗时,Python会自动帮这只狗执行这段代码进行“出厂设置”
def __init__(self, name, age):
self.name = name # self.name 意思是“这只狗自己的名字”
self.age = age
# 定义狗的行为(类的方法)
def bark(self):
# self.name 确保哪只狗在叫,就打印哪只狗的名字
print(f"{self.name} 汪汪叫了起来!")
# -----------------------------------
# 开始“实例化”(用图纸造出真狗)
dog1 = Dog("小白", 2)
dog2 = Dog("大黑", 5)
dog1.bark() # 输出: 小白 汪汪叫了起来!
dog2.bark() # 输出: 大黑 汪汪叫了起来!
二、 解剖类:属性与方法的作用域
在 Python 中,不是所有的属性和方法都属于单个对象,理解它们各自的“势力范围”至关重要。
1. 静态的数据:属性 (Attributes)
- 实例属性: 属于某个具体对象的特征。通常在
__init__中用self.xxx定义。每个对象可以拥有不同的值(比如你的学号和我的学号不同)。 - 类属性: 属于整个类,被所有实例共享的数据。直接写在类内部。只要类不改,所有对象看到的都一样(比如所有学生共享同一个学校名称)。
2. 动态的行为:方法 (Methods)
- 实例方法: 最常用。第一个参数必须是
self。用于访问或修改具体对象的属性。 - 类方法 (
@classmethod): 第一个参数是cls(代表类本身)。用于操作类属性,或者作为创建对象的“备选方案”。 - 静态方法 (
@staticmethod): 不需要self也不需要cls。它就是一个普通的函数,只是逻辑上属于这个类(比如一个独立的格式校验工具),不需要读取类或对象的任何数据。
class Book:
# --- 【类属性】 ---
library_name = "城市中心图书馆" # 共享
total_books = 0
def __init__(self, title, author):
# --- 【实例属性】 ---
self.title = title
self.author = author
Book.total_books += 1
# --- 【实例方法】 ---
def get_info(self):
return f"《{self.title}》, 作者: {self.author}, 馆藏于: {self.library_name}"
# --- 【类方法】 ---
@classmethod
def get_total_count(cls):
return f"目前共有 {cls.total_books} 本书。"
# --- 【静态方法】 ---
@staticmethod
def is_valid_isbn(isbn):
return len(isbn) == 13
# 演练
book1 = Book("Python入门", "老王")
print(book1.get_info()) # 实例方法调用
print(Book.get_total_count()) # 类方法调用
print(Book.is_valid_isbn("9781234567890")) # 静态方法调用
三、 面向对象的核心特性
这是 OOP 设计的灵魂,决定了代码的安全性、复用性和灵活性。
1. 封装 (Encapsulation):智能的隐私保护
解决的问题: 属性被随意修改导致程序崩溃。
封装不仅仅是隐藏内部细节,更是建立“智能管家”。Python 通过在变量名前加双下划线 __(名称修饰)实现私有化。
为了让私有变量既安全又易于访问,我们通常使用 @property 装饰器,将方法伪装成属性,并在其中加入安检逻辑。
class SmartAge:
def __init__(self, age):
self.__age = age # 内部私有变量
# 第一步:Getter 取值器(必须先写这个)
@property
def age(self):
return f"{self.__age} 岁"
# 第二步:Setter 存值器(拦截非法赋值)
@age.setter
def age(self, value):
if not isinstance(value, int) or value < 0:
print("错误:年龄必须是正整数!")
else:
self.__age = value
me = SmartAge(25)
print(me.age) # 像属性一样读取 -> 输出 "25 岁"
me.age = -10 # 像属性一样赋值 -> 触发 setter 里的安检逻辑,拒绝修改
2. 继承 (Inheritance):
解决的问题: 不同类之间存在大量重复代码。
子类可以自动获得父类的非私有属性和方法。避坑警告: 如果子类需要扩展自己的初始化逻辑,必须使用 super().__init__() 来调用父类的初始化方法,否则会丢失父类的基础属性!
class Character:
def __init__(self, name):
self.name = name
def move(self):
print(f"{self.name} 正在移动")
class Warrior(Character):
def __init__(self, name, weapon):
super().__init__(name) # 核心:召唤父类帮忙初始化 name
self.weapon = weapon
def attack(self):
print(f"{self.name} 挥舞 {self.weapon} 发动攻击!")
p1 = Warrior("赵云", "长枪")
p1.move() # 继承自父类
3. 多态 (Polymorphism):真正的通用适配器
解决的问题: 主程序因为新增工具类型而频繁修改代码。
基于 Python 独有的**“鸭子类型”**(如果它走起来像鸭子,叫起来也像鸭子,那它就是鸭子)。只要不同的类拥有同名的方法,就可以将它们放入同一个通用函数中调用,无需关心它们的具体类型。
[Image of polymorphism in programming]
class WeChat:
def pay(self, money): print(f"微信支付了 {money} 元")
class Alipay:
def pay(self, money): print(f"支付宝支付了 {money} 元")
# 通用函数,不关心 tool 是微信还是支付宝,只管调用 pay()
def execute_payment(tool, amount):
tool.pay(amount)
execute_payment(WeChat(), 100)
execute_payment(Alipay(), 200)
4. 抽象 (Abstraction):制定团队规范
解决的问题: 团队协作时,开发者忘记实现某些核心方法。
使用内置的 abc 模块定义抽象基类。它就像一份强制合同:要求所有继承它的子类,必须实现某些特定的方法,否则直接报错不让实例化。
from abc import ABC, abstractmethod
class DBPlugin(ABC): # 抽象基类
@abstractmethod
def save(self):
pass # 必须被子类实现
class MySQL(DBPlugin):
def save(self):
print("保存数据到 MySQL")
# MySQL() 正常实例化
# class Oracle(DBPlugin): pass -> 如果这样写会报错,因为没实现 save
四、 魔术方法:Pythonic
魔术方法(Magic/Dunder Methods)是以双下划线开头和结尾的方法。你不需要手动调用它们,Python 会在特定时机自动触发。
__str__与__repr__: 定义对象被print()时的友好格式,告别丑陋的内存地址。- 运算符重载: 实现
__add__,__eq__等,让你的对象可以直接使用+,==进行运算。 __len__与__getitem__: 让你的对象像列表一样,支持len()函数和切片取值obj[0]。__call__: 让一个对象像函数一样直接加括号被调用!
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# 当执行 v1 + v2 时触发
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
# 当别人 print(v) 时触发
def __str__(self):
return f"向量({self.x}, {self.y})"
# 让对象像函数一样被调用
def __call__(self, scale):
return Vector(self.x * scale, self.y * scale)
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # 自动触发 __add__ -> 输出:向量(4, 6)
v3 = v1(10) # 自动触发 __call__ -> 输出:向量(10, 20)
五、 new 与万物之父 object
1. __new__ vs __init__
__new__(施工队): 负责真正在内存里创建并返回一个空对象。它的第一个参数是cls。__init__(装修队): 拿到__new__创建好的对象,给它设置初始属性。它不需要返回任何东西。
2. 单例模式 (Singleton)
单例模式确保无论你调用多少次类,永远只给你返回同一个对象(如日志管理器、数据库连接池)。
重点注意: 在 Python 3 中,所有类默认继承自 object(万物之母)。我们自己没有能力在内存中开辟空间,因此必须通过 super().__new__(cls) 向上求助万物之父 object 来完成对象的真正创建。
# 即使写成 class Singleton:,后台也是 class Singleton(object):
class Singleton(object):
_instance = None # 类属性:记录是否盖过房子
_init_flag = False # 防重装开关
def __new__(cls, *args, **kwargs):
# 1. 检查是否盖过房子
if cls._instance is None:
# 2. 向上求助 object 创建真正的内存实体
cls._instance = super().__new__(cls)
# 3. 必须返回这个实例
return cls._instance
def __init__(self, name):
# 4. 如果已经装修过了,直接跳过,防止后来的对象覆盖属性
if Singleton._init_flag:
return
self.name = name
print(f"真正执行了初始化: {self.name}")
Singleton._init_flag = True # 关掉开关
s1 = Singleton("首个实例")
s2 = Singleton("第二个实例")
print(s1 is s2) # 输出 True,它们指向同一块内存
六、高阶特性速览
当你准备阅读顶级开源框架(如 Django, Flask, Pandas)的源码时,你会遇到它们:
- 反射与自省 (Reflection & Introspection): 程序在运行时动态检查和修改自身的结构。使用
hasattr(obj, "run")检查方法是否存在,使用getattr(obj, "run")获取方法并执行。它让代码能够灵活处理未知的用户输入指令。 - 内存瘦身 (
__slots__): 关闭对象默认的__dict__动态字典,将属性固定化(__slots__ = ("x", "y"))。在创建百万级海量对象(如游戏里的海量小怪)时,能大幅降低内存消耗,但牺牲了动态添加新属性的能力。 - 描述符 (Descriptors): 实现了
__get__,__set__等方法的代理类。它是@property的底层原理,用于高度复用的属性控制(比如写一个专门拦截负数的“拦截器类”,供其他所有类使用)。 - 元类 (Metaclass): “创建类的类”。对象由类创建,类由元类创建(默认是
type)。它允许你在“类被定义”的一瞬间,拦截并修改类的结构,是掌控全局的上帝视角。
结语
面向对象的设计初衷不是为了把代码变复杂,而是为了**“在面对复杂的业务逻辑时,让代码不乱套”**。不要强行为了 OOP 而 OOP。
更多推荐


所有评论(0)