【Python-Day 29】万物皆对象:详解 Python 类的定义、实例化与 `__init__` 方法

发布于:2025-06-25 ⋅ 阅读:(24) ⋅ 点赞:(0)

Langchain系列文章目录

01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
08-【万字长文】MCP深度解析:打通AI与世界的“USB-C”,模型上下文协议原理、实践与未来

Python系列文章目录

PyTorch系列文章目录

机器学习系列文章目录

深度学习系列文章目录

Java系列文章目录

JavaScript系列文章目录

Python系列文章目录

01-【Python-Day 1】告别编程恐惧:轻松掌握 Python 安装与第一个程序的 6 个步骤
02-【Python-Day 2】掌握Python基石:变量、内存、标识符及int/float/bool数据类型
03-【Python-Day 3】玩转文本:字符串(String)基础操作详解 (上)
04-【Python-Day 4】玩转文本:Python 字符串常用方法深度解析 (下篇)
05-【Python-Day 5】Python 格式化输出实战:%、format()、f-string 对比与最佳实践
06- 【Python-Day 6】从零精通 Python 运算符(上):算术、赋值与比较运算全解析
07-【Python-Day 7】从零精通 Python 运算符(下):逻辑、成员、身份运算与优先级规则全解析
08-【Python-Day 8】从入门到精通:Python 条件判断 if-elif-else 语句全解析
09-【Python-Day 9】掌握循环利器:for 循环遍历序列与可迭代对象详解
10-【Python-Day 10】Python 循环控制流:while 循环详解与 for 循环对比
11-【Python-Day 11】列表入门:Python 中最灵活的数据容器 (创建、索引、切片)
12-【Python-Day 12】Python列表进阶:玩转添加、删除、排序与列表推导式
13-【Python-Day 13】Python 元组 (Tuple) 详解:从创建、操作到高级应用场景一网打尽
14-【Python-Day 14】玩转Python字典(上篇):从零开始学习创建、访问与操作
15-【Python-Day 15】深入探索 Python 字典 (下):常用方法、遍历、推导式与嵌套实战
16-【Python-Day 16】代码复用基石:详解 Python 函数的定义与调用
17-【Python-Day 17】玩转函数参数(上):轻松掌握位置、关键字和默认值
18-【Python-Day 18】玩转函数参数(下):*args 与 **kwargs 终极指南
19-【Python-Day 19】函数的回响:深入理解 return 语句与返回值
20-【Python-Day 20】揭秘Python变量作用域:LEGB规则与global/nonlocal关键字详解
21-【Python-Day 21】一行搞定!Python lambda 匿名函数的妙用与实战
22-【Python-Day 22】代码的基石:模块(Module)的导入与使用详解
23-【Python-Day 23】Python 模块化编程实战:创建、导入及 sys.path 深度解析
24-【Python-Day 24】告别杂乱代码!一文掌握 Python 包(Package)的创建与使用
25-【Python-Day 25】玩转数字:精通 math 与 random 模块,从数学运算到随机抽样
26-【Python-Day 26】解锁时间魔法:深入解析 time 与 datetime 模块
27-【Python-Day 27】轻松驾驭操作系统:精通 os 与 sys 模块核心功能
28-【Python-Day 28】从指令到蓝图:Python面向对象编程(OOP)入门指南
29-【Python-Day 29】万物皆对象:详解 Python 类的定义、实例化与 __init__ 方法



前言

在上一篇文章中,我们共同揭开了面向对象编程(OOP)的神秘面纱,理解了“类(Class)”和“对象(Object)”这两个核心概念的理论基础。我们知道,类是创建对象的蓝图,而对象则是类的具体实例。

理论是行动的指南,但真正的掌握源于实践。从本篇文章开始,我们将正式从理论迈向实战,动手创建并使用我们自己的 Python 类。这篇文章将是您踏入 Python 面向对象世界的第一步,也是至关重要的一步。我们将详细讲解如何使用 class 关键字定义一个类,深入剖析构造方法 __init__ 的作用,并彻底搞懂那个让许多初学者困惑的 self 参数究竟是什么。

准备好了吗?让我们一起从零开始,构建属于你自己的第一个 Python 类!

一、回顾:什么是类和对象?

在动手编码之前,让我们通过一个生动的类比,快速回顾一下类与对象的关系,为后续的实践打下坚实的基础。

1.1.1 蓝图与实体

想象一下,一位汽车设计师绘制了一张“汽车设计蓝图”。

  • 类 (Class):就是这张设计蓝图。它详细定义了汽车应该具备的所有通用特性(属性),比如品牌、颜色、型号,以及能够执行的功能(方法),比如启动、加速、刹车。蓝图本身并不是一辆可以驾驶的汽车,它只是一个模板或规范。

  • 对象 (Object):就是根据这张蓝图制造出来的每一辆具体的汽车。每一辆车(对象)都遵循蓝图(类)的设计,但又拥有自己独特的属性值。例如,一辆是“红色的法拉利”,另一辆是“黑色的特斯拉”。它们都是“汽车”这个类的实例,但它们是相互独立的实体。

1.1.2 属性与行为

  • 属性 (Attribute):对象的特征或状态,在代码中通常表现为变量。例如,对于一个“人”类,nameageheight 就是属性。
  • 方法 (Method):对象的行为或能力,在代码中通常表现为函数。例如,“人”可以 eat()sleep()work()

我们的目标就是学习如何用 Python 代码来描述这张“蓝图”(定义类),并根据它来创建具体的“实体”(实例化对象)。

二、定义一个简单的类:class 关键字

在 Python 中,定义类的语法非常直观,我们使用 class 关键字。

2.1.1 基本语法

让我们从最简单的形式开始,创建一个代表“狗”的类。

class Dog:
    pass

代码解析:

  • class Dog:class 是关键字,告诉 Python 我们正在定义一个类。Dog 是我们给这个类起的名字。类名的命名规范通常遵循 大驼峰命名法 (PascalCase),即每个单词的首字母都大写,例如 MyClassName
  • pass:是一个占位符语句,表示“什么都不做”。因为 Python 的语法要求代码块(如类定义、函数定义等)不能为空,所以当我们想创建一个最精简的空类时,就需要使用 pass

2.1.2 实例化:从类创建对象

我们已经有了 Dog 类的“蓝图”,现在就可以根据它来创建具体的“狗”对象了。这个过程称为实例化 (Instantiation)

语法非常简单,就像调用一个函数一样:

# 实例化一个 Dog 对象,并将其赋值给变量 my_dog
my_dog = Dog()

# 我们可以再创建一个
another_dog = Dog()

# 打印这两个对象,看看会发生什么
print(my_dog)
print(another_dog)

# 使用 type() 函数查看它们的类型
print(type(my_dog))

输出结果 (内存地址会不同):

<__main__.Dog object at 0x000001A8D9B43E20>
<__main__.Dog object at 0x000001A8D9B43E50>
<class '__main__.Dog'>

结果分析:

  1. my_dog = Dog() 这行代码就完成了一次实例化。它创建了一个 Dog 类的实例,并把这个实例(对象)的引用存放在了 my_dog 这个变量里。
  2. 打印 my_doganother_dog,我们看到它们是两个不同的 Dog 对象,位于内存中的不同地址 (0x...)。这证明了每个实例都是独一无二的。
  3. type(my_dog) 的输出 <class '__main__.Dog'> 明确告诉我们,my_dog 这个变量的类型是我们刚刚定义的 Dog 类。

目前,我们的 Dog 类还只是一个空壳,它没有具体的属性(比如名字、年龄)和行为(比如吠叫)。接下来,我们将学习如何让它变得丰满起来。

三、灵魂所在:构造方法 __init__self

要让每个 Dog 对象都拥有自己独特的名字和年龄等信息,我们需要在对象被创建的那一刻就为它设定好这些初始属性。这就需要用到一个非常特殊的函数——构造方法 __init__

3.1.1 什么是构造方法 __init__

__init__ 是 Python 类中的一个特殊方法(也称为“魔术方法”或“双下划线方法”)。它的名字前后都有两个下划线。

它的核心作用是:在类的实例被创建时,自动被调用,用于完成对象的初始化工作。

你可以把它想象成工厂流水线上的“初始化工位”。每当一个新产品(对象)从生产线上下来(被创建),它都会自动经过这个工位,进行喷漆、贴标签等一系列初始化操作。

3.1.2 self 的奥秘

在介绍 __init__ 的用法之前,我们必须先理解 selfself 是类中方法(函数)的第一个参数,它是一个让无数初学者感到困惑的概念。

一句话解释 selfself 代表的就是类的实例(对象)本身。

当一个对象调用它的方法时,Python 会自动将这个对象本身作为第一个参数传递给该方法。这个参数,按照约定俗成的惯例,我们都将其命名为 self

举个例子:
当我们执行 my_dog = Dog("Buddy", 3) 来创建一个 Dog 对象时,Python 内部大致会做两件事:

  1. 先在内存中创建一个空的 Dog 对象。
  2. 然后自动调用 __init__ 方法,并将这个刚刚创建的空对象作为第一个参数传递进去,即 Dog.__init__(刚刚创建的空对象, "Buddy", 3)。在这个 __init__ 方法内部,self 就指向了这个“刚刚创建的空对象”。

通过 self,我们就可以在类的内部访问和操作这个对象自身的属性和方法了。

3.1.3 定义带属性的类

现在,让我们结合 __init__self 来改造我们的 Dog 类,让它在创建时就能拥有名字、品种和年龄。

class Dog:
    # 定义构造方法
    def __init__(self, name, breed, age):
        """
        初始化一只新的小狗对象。
        """
        print(f"初始化开始... 一只新的小狗对象正在被创建!")
        
        # 将传入的参数值赋给实例的属性
        # self.name 创建了一个属于该实例的属性 name
        self.name = name
        self.breed = breed
        self.age = age
        
        print(f"初始化完成!你好,我叫 {self.name}。")

# 实例化时,在类名后的括号里传入 __init__ 方法需要的参数 (除了 self)
dog1 = Dog("旺财", "中华田园犬", 2)
print("-" * 20)
dog2 = Dog("Buddy", "金毛巡回犬", 3)

输出结果:

初始化开始... 一只新的小狗对象正在被创建!
初始化完成!你好,我叫 旺财。
--------------------
初始化开始... 一只新的小狗对象正在被创建!
初始化完成!你好,我叫 Buddy。

代码解析:

  1. def __init__(self, name, breed, age)::我们定义了构造方法。它的第一个参数必须self。后面的 name, breed, age 是我们希望在创建对象时接收的外部数据。
  2. self.name = name:这是 __init__ 方法的核心。
    • self.name:表示“为当前这个对象(self)创建一个名为 name 的属性”。这种与 self 关联的属性称为实例属性 (Instance Attribute)
    • = name:将传入的参数 name 的值赋给这个实例属性。
  3. 实例化过程
    • 当我们执行 dog1 = Dog("旺财", "中华田园犬", 2) 时,Python 自动调用 __init__ 方法,并将 dog1 这个实例传给 self,“旺财” 传给 name,“中华田园犬” 传给 breed,2 传给 age
    • 方法内部,self.name = "旺财"self.breed = "中华田园犬"self.age = 2 等语句就为 dog1 这个具体对象设置了它独有的属性。
    • dog2 的创建过程同理,它也拥有自己的一套独立的属性值。

四、访问对象的属性和方法

创建了带有属性的对象后,我们自然需要一种方式来使用这些属性,并命令对象去执行某些行为(调用方法)。

4.1.1 访问实例属性

我们可以使用“点 (.) 操作符”来访问一个对象的属性。

语法:对象名.属性名

# 延续上面的代码
print(f"{dog1.name} 是一只 {dog1.breed}。")
print(f"{dog2.name} 今年 {dog2.age} 岁了。")

输出结果:

旺财 是一只 中华田园犬。
Buddy 今年 3 岁了。

dog1.name 精确地获取了 dog1 这个对象的 name 属性值,而 dog2.name 获取的是 dog2 对象的,两者互不干扰。

4.1.2 修改实例属性

实例属性的值是可变的(除非我们使用更高级的技巧来限制)。修改它同样使用点操作符。

print(f"过生日之前, {dog1.name}{dog1.age} 岁。")

# 修改 age 属性
dog1.age = dog1.age + 1 
# 或者使用更简洁的写法
# dog1.age += 1

print(f"过完生日后, {dog1.name} 现在是 {dog1.age} 岁了。")

输出结果:

过生日之前, 旺财 是 2 岁。
过完生日后, 旺财 现在是 3 岁了。

4.1.3 定义实例方法

光有属性还不够,对象应该有自己的行为。在类中定义的函数,我们称之为方法 (Method)。与实例属性一样,能访问实例属性的方法称为实例方法 (Instance Method)

实例方法的第一个参数也必须是 self,这样它才能访问到调用该方法的对象的内部属性。

让我们给 Dog 类添加两个方法:bark() (吠叫) 和 describe() (自我介绍)。

class Dog:
    def __init__(self, name, breed, age):
        self.name = name
        self.breed = breed
        self.age = age

    # 定义一个实例方法
    def bark(self):
        # self 在这里同样代表调用这个方法的那个实例
        return f"{self.name} 说: 汪!汪汪!"

    # 定义另一个使用多个属性的实例方法
    def describe(self):
        return f"你好,我是 {self.name}, 我是一只 {self.age} 岁的 {self.breed}。"

4.1.4 调用实例方法

调用方法和访问属性的方式完全一样,也是使用点操作符,但方法名后面需要加上一对括号 ()

语法:对象名.方法名()

dog1 = Dog("旺财", "中华田园犬", 3)
dog2 = Dog("Buddy", "金毛巡回犬", 3)

# 调用 dog1 的方法
bark_sound = dog1.bark()
description = dog1.describe()
print(bark_sound)
print(description)

print("-" * 20)

# 调用 dog2 的方法
print(dog2.bark())
print(dog2.describe())

输出结果:

旺财 说: 汪!汪汪!
你好,我是 旺财, 我是一只 3 岁的 中华田园犬。
--------------------
Buddy 说: 汪!汪汪!
你好,我是 Buddy, 我是一只 3 岁的 金毛巡回犬。

深入理解调用过程:当你执行 dog1.bark() 时,Python 实际上做的是 Dog.bark(dog1)。它将 dog1 这个实例作为第一个参数(即 self)传递给了 bark 方法。这就是为什么在方法内部,self 能够引用到 dog1 的所有属性(如 self.name)。

五、实战场景:创建一个“英雄”类

理论学习后,让我们通过一个更有趣的实战案例来巩固所学知识。假设我们在开发一款简单的文字对战游戏,我们需要创建一个 Hero 类。

5.1.1 需求分析

  1. 属性: 每个英雄都应该有名字(name)、生命值(hp)和攻击力(ap)。
  2. 方法:
    • attack(target):一个英雄可以攻击另一个英雄,对目标造成伤害(减少目标的 hp)。
    • show_status():可以显示英雄当前的状态信息。

5.1.2 代码实现

import time

class Hero:
    """
    代表一个游戏中的英雄角色。
    """
    def __init__(self, name, hp=100, ap=15):
        """
        初始化英雄属性,hp 和 ap 可以有默认值。
        """
        self.name = name
        self.hp = hp
        self.ap = ap
        print(f"英雄 {self.name} 已降临战场! [HP: {self.hp}, AP: {self.ap}]")

    def attack(self, target):
        """
        攻击另一个英雄 (target)。
        target: 另一个 Hero 对象。
        """
        if self.hp <= 0:
            print(f"{self.name} 已经阵亡,无法发起攻击。")
            return # 提前结束方法

        if target.hp <= 0:
            print(f"攻击失败:{target.name} 已经阵亡。")
            return
            
        print(f"【战斗】{self.name}{target.name} 发起了猛烈的攻击!")
        
        # 目标英雄的 hp 减少攻击者的 ap 值
        target.hp -= self.ap
        
        print(f"          造成了 {self.ap} 点伤害。")

        if target.hp <= 0:
            target.hp = 0 # 避免 hp 出现负数
            print(f"          {target.name} 被击败了!")
        else:
            print(f"          {target.name} 剩余生命值: {target.hp}")

    def show_status(self):
        """
        显示英雄当前的状态。
        """
        status = "存活" if self.hp > 0 else "阵亡"
        print(f"--- 状态面板 [{self.name}] ---")
        print(f"  生命值 (HP): {self.hp}")
        print(f"  攻击力 (AP): {self.ap}")
        print(f"  当前状态: {status}")
        print(f"--------------------------")

这个 Hero 类比 Dog 类更复杂一些,它不仅有属性,还有一个可以与其他同类对象交互的 attack 方法,这正是面向对象编程强大之处的体现。

5.1.3 模拟对战

现在,让我们创建两位英雄,并让他们进行一场简单的对决!

# 创建两位英雄实例
garen = Hero("盖伦", hp=150, ap=10)
darius = Hero("德莱厄斯", hp=120, ap=12)

time.sleep(1) # 暂停1秒,让输出更有节奏感
print("\n===== 战斗开始! =====\n")
time.sleep(1)

# 第一回合
darius.attack(garen)
garen.show_status()
time.sleep(1)

# 第二回合
garen.attack(darius)
darius.show_status()
time.sleep(1)

# 第三回合
darius.attack(garen)
garen.show_status()

print("\n===== 战斗结束! =====\n")

运行上述代码,你将看到一场生动的文字直播战斗,这全部是通过我们定义的 Hero 类和它的实例对象之间的交互完成的。

六、总结

恭喜你!通过本文的学习和实践,你已经成功创建并使用了自己的第一个 Python 类。这是你从面向过程思维转向面向对象思维的关键一步。让我们对今天的核心知识点进行梳理和总结:

  1. 类的定义:使用 class 关键字来声明一个类,类名推荐使用大驼峰命名法(PascalCase)。类是创建对象的模板。

  2. 构造方法 __init__:一个特殊的“魔术方法”,在通过类创建对象时被自动调用。它的主要职责是完成新对象的初始化工作,如设置初始属性。

  3. self 参数:是所有实例方法的第一个参数,它代表调用该方法的对象实例本身。通过 self,我们可以在类的内部访问实例的属性和调用其他实例方法。它是一个约定俗成的名称,请务必遵守。

  4. 实例属性:与特定对象实例绑定的变量(例如 self.name)。它们在 __init__ 方法中定义,每个对象都拥有一份自己独立的副本,互不影响。

  5. 实例化:通过 类名() 的形式创建类的具体实例(对象)。如果 __init__ 方法有其他参数,需要在括号内提供相应的值。

  6. 访问与调用:统一使用点 (.) 操作符来访问对象的属性(object.attribute)和调用其方法(object.method())。

  7. 封装思想初探:通过本章的学习,我们已经初步体验了面向对象三大特性之一的“封装”。类将描述对象状态的数据(属性)和操作这些数据的代码(方法)巧妙地“封装”在了一起,形成了一个功能完整的独立单元。

在下一篇文章中,我们将继续深入探索类的世界,学习不同类型的方法(实例方法、类方法、静态方法)及其各自的应用场景。请务必勤加练习,真正消化今天的内容!