一、 地基是怎么打的?

在面向对象的世界里,有两个最基础的概念,它们是“抽象”与“具体”的关系:

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)的源码时,你会遇到它们:

  1. 反射与自省 (Reflection & Introspection): 程序在运行时动态检查和修改自身的结构。使用 hasattr(obj, "run") 检查方法是否存在,使用 getattr(obj, "run") 获取方法并执行。它让代码能够灵活处理未知的用户输入指令。
  2. 内存瘦身 (__slots__): 关闭对象默认的 __dict__ 动态字典,将属性固定化(__slots__ = ("x", "y"))。在创建百万级海量对象(如游戏里的海量小怪)时,能大幅降低内存消耗,但牺牲了动态添加新属性的能力。
  3. 描述符 (Descriptors): 实现了 __get__, __set__ 等方法的代理类。它是 @property 的底层原理,用于高度复用的属性控制(比如写一个专门拦截负数的“拦截器类”,供其他所有类使用)。
  4. 元类 (Metaclass): “创建类的类”。对象由类创建,类由元类创建(默认是 type)。它允许你在“类被定义”的一瞬间,拦截并修改类的结构,是掌控全局的上帝视角。

结语

面向对象的设计初衷不是为了把代码变复杂,而是为了**“在面对复杂的业务逻辑时,让代码不乱套”**。不要强行为了 OOP 而 OOP。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐