Python面向对象入门:类与对象的基础代码示例
面向对象编程(OOP)的核心在于把“数据”与“对数据的操作”打包成一个独立的单元。在 Python 中,理解面向对象只需要搞清楚两个基本概念:类(Class)是用于生成对象的模板,而对象(Object)是根据模板生产出来的具体实例。本文将通过可运行的精简代码和直观图解,分步解析类的定义、实例化、__init__ 构造方法以及 self 参数的作用机制。
一、核心认知:什么是“类”与“对象”
初学者在接触面向对象时,常常困惑于它与普通函数有什么区别。面向过程的代码习惯于将数据保存在零散的字典或列表中,再写一堆函数去接收并修改这些变量。当项目规模扩大时,数据结构一旦改变,所有相关的函数都需要逐个调整,代码维护成本极高。
面向对象提供了一种组织代码的新思路:它让你把事物的状态特征(属性)和行为动作(方法)整合在一起。
- 类(Class):相当于一张建筑蓝图,或者做月饼的模具。它规定了未来生产出来的物体应该长什么样、具备哪些功能,但蓝图本身并不占用实际地皮。
- 对象(Object / Instance):则是根据蓝图真正建造出来的房子,或者从模具里倒出来的月饼。你可以基于同一张蓝图建造多座房子,它们结构一致,但门牌号、室内装修和住户各不相同。
二、基础语法:如何定义类与创建对象
在 Python 中,通过 class 关键字来声明一个类。根据 PEP 8 命名规范,类名一般采用大驼峰命名法(PascalCase),即每个单词的首字母大写,例如 StudentCourse。
下面是一个极简示例,展示如何定义一个空类并基于它创建对象:
# 1. 定义一个最简单的类
class Dog:
pass # pass 是占位语句,表示暂无具体代码
# 2. 实例化对象(通过 类名() 调用)
my_dog = Dog()
# 3. 打印对象的类型和内存地址
print(type(my_dog))
print(my_dog)
<__main__.Dog object at 0x7fa28c11e350>
从输出结果可以看到,my_dog 是一个类型为 Dog 的对象,后面的十六进制字符串展示了它在内存中的存储地址。
三、核心机制:__init__ 构造方法与 self
光有一个空对象并没有实际价值。通常我们希望在创建对象的同时,把它的初始属性(比如名字、年龄)一起绑定好。在 Python 中,这是通过 __init__ 初始化方法来实现的。
1. 理解 __init__ 方法
双下划线开头和结尾的方法在 Python 中被称为“魔法方法”或“特殊方法”。__init__ 是最常用的魔法方法,它在对象被创建之后立即被自动调用。不需要你手动去执行 p.__init__(),当你写出 Dog("小白") 时,Python 会在后台帮你完成这次调用。
2. 理解 self 的含义
在类的方法定义中,第一个参数总是 self。很多初学者在这里产生疑问:调用时明明没有传它,它到底是干什么的?
self 代表的其实就是当前正在被操作的具体对象本身。类是一个通用模板,当有三个不同的狗对象同时想要调用“叫一声”的方法时,计算机必须搞清楚到底是哪一只狗在叫。self 就像现实生活中的第一人称代词“我”,在类的代码中,self.name 指的就是“我自己的名字”。
self 命名为其他标识符(如 this 或 me),但强烈建议严格使用 self。这是全体 Python 开发者的共识规范,替换它会严重降低代码的可读性。
四、完整实战代码:银行账户管理器
我们来看一个贴近真实应用场景的完整示例:使用面向对象的方式编写一个银行账户管理器,让读者直观感受属性读写与方法调用的全过程。
# 完整可运行代码示例:BankAccount 类
class BankAccount:
"""简易银行账户管理类"""
def __init__(self, owner, initial_balance=0.0):
# 实例属性:归属于每个独立的账户对象
self.owner = owner
self.balance = float(initial_balance)
def deposit(self, amount):
"""存钱方法"""
if amount > 0:
self.balance += amount
print(f"[{self.owner}] 成功存入: {amount} 元,当前余额: {self.balance} 元")
else:
print(f"[{self.owner}] 存入金额必须大于 0!")
def withdraw(self, amount):
"""取钱方法"""
if amount <= 0:
print(f"[{self.owner}] 取款金额必须大于 0!")
elif amount > self.balance:
print(f"[{self.owner}] 余额不足!当前可用余额: {self.balance} 元,尝试取出: {amount} 元")
else:
self.balance -= amount
print(f"[{self.owner}] 成功取出: {amount} 元,当前余额: {self.balance} 元")
def check_balance(self):
"""查询余额方法"""
print(f"[{self.owner}] 账户最终余额: {self.balance} 元")
# ---------------- 测试运行 ----------------
# 创建第一个独立账户:Alice
account_a = BankAccount(owner="Alice", initial_balance=1000.0)
# 创建第二个独立账户:Bob
account_b = BankAccount(owner="Bob", initial_balance=200.0)
# 针对不同对象进行独立操作
account_a.deposit(500) # Alice 存入 500
account_a.withdraw(200) # Alice 取出 200
account_b.withdraw(300) # Bob 尝试取出 300(预期提示余额不足)
account_b.deposit(100) # Bob 存入 100
# 查看两个对象的最终状态
account_a.check_balance()
account_b.check_balance()
[Alice] 成功取出: 200 元,当前余额: 1300.0 元
[Bob] 余额不足!当前可用余额: 200.0 元,尝试取出: 300 元
[Bob] 成功存入: 100 元,当前余额: 300.0 元
[Alice] 账户最终余额: 1300.0 元
[Bob] 账户最终余额: 300.0 元
通过上面的输出结果可以清晰看到:虽然 account_a 和 account_b 执行的是同一套 deposit 与 withdraw 逻辑,但它们内部的 balance 数值彼此完全隔离,互不干扰。这就是对象状态独立性的体现。
五、进阶对比:实例属性与类属性的区别
在初学阶段,另一个容易被混淆的概念是“类属性”与“实例属性”。
- 实例属性(Instance Attribute):定义在
__init__内部,带self.前缀。每个对象独享一份,修改一个对象的实例属性不会影响其他对象。 - 类属性(Class Attribute):直接定义在类体内部、方法之外。它是所有由该类创建出来的对象共同共享的公共变量。
通过以下代码可以清晰验证两者的差异:
class Employee:
# 类属性(所有员工都属于同一家公司)
company_name = "未来科技"
def __init__(self, name):
# 实例属性(每个员工有不同的姓名)
self.name = name
# 创建两名员工
e1 = Employee("王五")
e2 = Employee("赵六")
# 1. 访问类属性(可以直接用 类名.属性名 访问,也可以用 对象.属性名 访问)
print(Employee.company_name) # 输出: 未来科技
print(e1.company_name) # 输出: 未来科技
# 2. 修改类属性(必须使用 类名.属性名 进行修改)
Employee.company_name = "创新未来"
print(e1.company_name) # 输出: 创新未来
print(e2.company_name) # 输出: 创新未来(全部联动变更)
# 3. 常见陷阱:如果通过实例对象赋值类属性
e1.company_name = "临时子公司" # 这不会修改类属性,而是给 e1 悄悄新增了一个名为 company_name 的实例属性!
print(e1.company_name) # 输出: 临时子公司(优先读取实例自身的属性)
print(e2.company_name) # 输出: 创新未来(其他对象依旧读取原类属性)
六、初学者常见报错与排查清单
在编写类和对象代码时,有两类报错最常出现,遇到时可根据以下规律快速排查定位:
1. TypeError: __init__() takes X positional arguments but Y were given
报错原因:传递的参数个数与 __init__ 定义的参数不一致。请牢记:调用时传入的参数是自动匹配给 self 后面的参数的。如果你写了 def __init__(self, name),调用时却写了 Person()(没有传参数),就会报缺少 1 个位置参数。
2. TypeError: my_method() takes 0 positional arguments but 1 was given
报错原因:在类中定义常规方法时漏写了 self。例如定义了 def speak(): pass,而在调用 obj.speak() 时,Python 会自动把 obj 作为第一个参数硬塞进去,导致参数多出了一个。只要在方法声明处补上 self 即可修复。
七、总结:面向对象入门要点速查
掌握类与对象是后续深入学习继承(Inheritance)、封装(Encapsulation)以及多态(Polymorphism)的前提。在日常练习中,建议先通过具体的现实事物(例如汽车、员工、购物车)尝试写出简单的类结构,观察多个对象属性的变化,从而形成直观的代码感知。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









