Skip to content

Python 类和实例:从图纸到房子的编程艺术

引言:建筑设计图与真实的房子

建筑师画了一张图纸:三室一厅、南北通透、带阳台。这张图纸本身不能住人,但根据它可以盖出一栋、十栋、一百栋真实的房子——每栋房子结构相同,但住着不同的人、摆着不同的家具。

Python 的面向对象编程(OOP)核心就是这两个概念:

  • 类(Class):抽象的模板、图纸——比如"学生"这个概念;
  • 实例(Instance):根据类创建的具体对象——比如"学生小明""学生小红"。

每个实例拥有相同的方法(都会考试、都会自我介绍),但各自的数据不同(小明 90 分,小红 85 分)。


一、定义第一个类

1.1 最简单的类定义

python
class Student(object):
    pass

逐部分拆解:

部分含义说明
class关键字告诉 Python"我要定义一个类"
Student类名通常大写开头(驼峰命名法),如 StudentMyClass
(object)继承的父类没有合适的父类就写 object——所有类最终都继承它
pass占位符暂时什么都不做,先占个位置

关于 (object):这是"继承"的语法,表示 Student 类是从 object 类派生出来的。继承的详细概念后面会讲,现在只需记住:不知道继承谁就写 object

注意:Python 3 中 (object) 可以省略不写,class Student: 也完全合法。但写上更明确,兼容旧代码习惯。

1.2 创建实例:图纸变成房子

python
>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>

Student() 就像"按图纸开工",bart 就是盖好的那栋房子——一个具体的 Student 实例。后面的 0x10a67a590 是它在内存中的地址,每个实例地址都不同。

Student 本身呢?它仍然是类(图纸):

python
>>> Student
<class '__main__.Student'>

生活化理解Student 是图纸,Student() 是开工命令,bart 是盖好的房子。

1.3 给实例绑定属性

Python 非常灵活,可以随时给实例"贴标签":

python
>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'

这就像给房子挂门牌——房子盖好后,随时可以挂上"Bart 家"的牌子。


二、__init__ 方法:强制填写"必填项"

2.1 为什么要用 __init__

上面的方式有个问题:创建实例后再逐个绑定属性,容易遗漏。如果每个学生必须有姓名和成绩,能不能在"开工"时就强制填写?

可以!定义 __init__ 方法:

python
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

⚠️ 注意__init__ 前后各有两个下划线!不是 _init_,也不是 __init

2.2 __init__ 的特殊之处

第一个参数永远是 self

self 指向正在被创建的实例本身。在 __init__ 内部,self.name = name 的意思是:"把传入的 name 绑定到当前这个实例上。"

创建实例时必须传入匹配的参数

有了 __init__,就不能空着手创建了:

python
>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59

但注意:self 不需要手动传!Python 解释器会自动把实例变量传进去。你只需要传 namescore

生活化理解__init__ 像购房合同上的"必填信息"——姓名、身份证号必须当场填好,房子才能交付。self 就是"这套房子本身",销售人员(Python)会自动把"这套房子"填进合同,你只需要填个人信息。

2.3 self 到底是什么?

self 不是 Python 关键字,它只是一个约定俗成的名字(你完全可以写成 thisme,但全世界 Python 程序员都用 self)。

它的作用:让实例的方法知道自己在操作哪个实例的数据

python
bart = Student('Bart', 59)
lisa = Student('Lisa', 87)

bart.print_score()   # self 就是 bart,打印 Bart 的分数
lisa.print_score()   # self 就是 lisa,打印 Lisa 的分数

同一个方法,因为 self 指向不同实例,操作的数据就不同。


三、类的方法:把数据和操作绑在一起

3.1 从"外部函数"到"内部方法"

假设我们要打印学生成绩。最直观的做法是写个外部函数:

python
def print_score(std):
    print('%s: %s' % (std.name, std.score))

>>> print_score(bart)
Bart Simpson: 59

这能工作,但有个问题:数据和操作分离了。Student 实例自己拥有 name 和 score,却要依赖外面的函数来打印——就像你家里有米有锅,却非要叫外卖才能吃上饭。

3.2 数据封装:把操作收进类里

更好的做法:把打印逻辑写进 Student 类内部,作为方法

python
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def print_score(self):
        print('%s: %s' % (self.name, self.score))

调用时直接在实例上点出来:

python
>>> bart.print_score()
Bart Simpson: 59

这就是封装(Encapsulation):数据(name、score)和操作数据的逻辑(print_score)被"打包"进同一个类里。外部调用者只需要知道"给 name 和 score 就能创建学生,调用 print_score 就能打印成绩",不需要知道内部怎么实现的。

3.3 再封装一个方法:成绩等级

python
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C'

使用:

python
lisa = Student('Lisa', 99)
bart = Student('Bart', 59)

print(lisa.name, lisa.get_grade())   # Lisa A
print(bart.name, bart.get_grade())   # Bart C

外部调用者不需要知道"90 分以上是 A"这个规则,直接调用 get_grade() 就行。内部规则随时可以改(比如 95 分以上才是 A),只要方法名和返回格式不变,外部代码零改动。


四、Python 的"自由"与风险

4.1 实例属性可以动态增删

和 Java、C++ 等静态语言不同,Python 允许给实例临时绑定任何属性

python
>>> bart = Student('Bart', 59)
>>> lisa = Student('Lisa', 87)

>>> bart.age = 8     # 给 bart 单独加一个 age 属性
>>> bart.age
8

>>> lisa.age         # lisa 没有这个属性,报错!
AttributeError: 'Student' object has no attribute 'age'

两个实例来自同一个类,但拥有的属性可以不同——这是 Python 的灵活性,也是潜在的风险。

4.2 风险:属性不一致导致代码脆弱

python
def print_age(std):
    print(std.age)   # 假设所有学生都有 age

print_age(bart)   # 正常
print_age(lisa)   # 崩溃!

建议

  • 类应该有的属性,__init__ 中统一定义,不要依赖动态绑定;
  • 动态绑定只用于临时、个别的场景,别当成常规操作。

五、类 vs 实例:一张表看懂区别

对比项类(Class)实例(Instance)
本质抽象的模板、图纸具体的对象、房子
创建方式class Student(object):bart = Student('Bart', 59)
数量一个类可以创建无数个实例
数据不存具体数据(存方法定义)各自独立的数据(name、score)
方法定义方法(第一个参数是 self)调用方法(self 自动传入)
内存一份类定义每个实例独立占用内存

六、常见误区与避坑指南

6.1 误区一:__init__ 写成 _init_init

python
# ❌ 错误:前后只有一个下划线
def _init_(self, name):
    self.name = name

# ❌ 错误:没有下划线
def init(self, name):
    self.name = name

# ✅ 正确:前后各两个下划线
def __init__(self, name):
    self.name = name

写错不会报错,但创建实例时不会自动调用,属性就没绑定上——排查起来非常痛苦。

6.2 误区二:忘记写 self

python
class Student(object):
    def __init__(self, name):
        name = name          # ❌ 错!这只是局部变量赋值
        self.name = name     # ✅ 对!绑定到实例上

name = name 只是把参数赋给自己,方法结束就消失。必须 self.name = name 才能存到实例里。

6.3 误区三:调用方法时手动传 self

python
bart = Student('Bart', 59)

# ❌ 错误:多此一举
bart.print_score(bart)

# ✅ 正确:self 自动传入
bart.print_score()

6.4 误区四:把实例属性当类属性

python
class Student(object):
    count = 0          # 这是类属性,所有实例共享

    def __init__(self, name):
        self.name = name  # 这是实例属性,每个实例独立

bart = Student('Bart')
lisa = Student('Lisa')

Student.count += 1
print(bart.count)   # 1
print(lisa.count)   # 1 —— 共享的!

类属性像"班级公告栏"——所有人看到的是同一块;实例属性像"个人课桌"——各用各的。别搞混。

6.5 误区五:过度设计

python
# ❌ 杀鸡用牛刀
class Adder(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def add(self):
        return self.a + self.b

result = Adder(1, 2).add()

# ✅ 简单计算用函数就够了
def add(a, b):
    return a + b

result = add(1, 2)

判断标准:如果数据和操作是长期绑定的(学生有名字、成绩,还要算等级、打印),用类;如果只是一次性计算,用函数。


七、实战练习

练习 1:长方形类

定义一个 Rectangle 类:

  • __init__ 接收长和宽
  • 方法 area() 返回面积
  • 方法 perimeter() 返回周长
python
r = Rectangle(3, 4)
print(r.area())        # 12
print(r.perimeter())   # 14
参考答案
python
class Rectangle(object):
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * (self.length + self.width)

练习 2:银行账户类

定义一个 BankAccount 类:

  • __init__ 接收户名和初始余额
  • 方法 deposit(amount) 存钱
  • 方法 withdraw(amount) 取钱(余额不足时提示)
  • 方法 get_balance() 返回当前余额
python
acc = BankAccount('小明', 1000)
acc.deposit(500)
acc.withdraw(200)
print(acc.get_balance())   # 1300
acc.withdraw(2000)         # 余额不足!
参考答案
python
class BankAccount(object):
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount
        print(f'存入 {amount},当前余额 {self.balance}')

    def withdraw(self, amount):
        if amount > self.balance:
            print('余额不足!')
        else:
            self.balance -= amount
            print(f'取出 {amount},当前余额 {self.balance}')

    def get_balance(self):
        return self.balance

练习 3:找出 bug

下面代码创建学生后,为什么 print(bart.name)AttributeError

python
class Student(object):
    def __init__(self, name):
        name = name

bart = Student('Bart')
print(bart.name)
参考答案

__init__ 里写的是 name = name,这只是把参数赋给局部变量,没有绑定到实例上。方法结束后,这个值就消失了。

修复

python
def __init__(self, name):
    self.name = name   # 必须加 self.

八、小结

  1. 类是模板,实例是对象——图纸 vs 房子,概念 vs 具体;
  2. class Student(object): 定义类,类名大写开头,父类默认 object;
  3. __init__ 方法在创建实例时自动调用,用于强制绑定必要属性;
  4. self 指向实例本身,方法的第一个参数必须是它,但调用时不用传;
  5. 封装 = 数据 + 操作数据的方法,外部只管用,不管内部实现;
  6. 实例属性各自独立,类属性所有实例共享——别搞混;
  7. Python 允许动态绑定属性,但应在 __init__ 中统一定义,避免属性不一致。

类和实例是面向对象编程的地基。理解了"图纸"和"房子"的关系,后面的继承、多态、魔法方法,都只是在这块地基上盖更高的楼层。