系列文章

Python 从入门到精通

第 13 / 36 篇

从环境安装和基础语法出发,逐步学习工程实践、自动化、数据处理与 Web 开发。

  1. 01
    Python 怎么装、怎么用
  2. 02
    变量、数字和字符串
  3. 03
    列表:把一组数据放在一起
  4. 04
    元组、集合和字典
  5. 05
    条件判断:让程序做选择
  6. 06
    循环:重复的事交给程序
  7. 07
    函数:把代码整理成可复用的块
  8. 08
    模块与包:拆分你的程序
  9. 09
    输入、输出与字符串格式化
  10. 10
    文件读写:保存程序的数据
  11. 11
    异常处理:程序出错时怎么办
  12. 12
    基础阶段练习:命令行记账本
  13. 13
    类和对象:面向对象入门正在阅读
  14. 14
    继承、组合与特殊方法
  15. 15
    迭代器与生成器
  16. 16
    列表推导式与生成器表达式
  17. 17
    装饰器:给函数增加能力
  18. 18
    上下文管理器与 with
  19. 19
    类型标注与 dataclass
  20. 20
    正则表达式:从文本中找规律
  21. 21
    日期、时间与时区
  22. 22
    日志与调试
  23. 23
    虚拟环境与依赖管理
  24. 24
    测试:让修改不再提心吊胆
  25. 25
    网络请求:用 Python 调用 API
  26. 26
    网页解析与合规采集
  27. 27
    操作 Excel、CSV 与批量文件
  28. 28
    SQLite:给程序加一个数据库
  29. 29
    数据分析入门:NumPy 与 Pandas
  30. 30
    画图:把数据变得直观
  31. 31
    Flask 入门:做一个小网站
  32. 32
    异步编程:同时处理多项任务
  33. 33
    线程、进程与并发选择
  34. 34
    性能分析与优化
  35. 35
    项目结构、配置与发布
  36. 36
    综合项目:从需求到上线

查看整个系列 →

前面写的都是”函数 + 数据”:数据是数据,函数是函数,各管各的。但现实世界的东西是”数据 + 行为”一体的——一只狗有名字、年龄(数据),还会叫、会跑(行为)。面向对象编程(OOP)就是让代码贴近这种思维方式:把数据和操作它的函数绑在一起,组成”对象”。

类和对象的关系

先分清两个词:

  • 类(class):模板、图纸。定义了”这种东西”长什么样、会干什么
  • 对象(object / 实例):按图纸造出来的具体东西

打个比方:Dog 是类(图纸),楼下那只叫旺财的狗是对象(实例)。

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

    def bark(self):
        print(f"{self.name}:汪汪!")

# 创建对象(实例化)
wangcai = Dog("旺财", 3)
wangcai.bark()          # 旺财:汪汪!
print(wangcai.name)     # 旺财

__init__ 和 self

两个必须理解的机制:

  • __init__构造方法,创建对象时自动调用,用来初始化数据。名字前后各两个下划线,这种叫”魔术方法”
  • self:代表当前这个对象自己。类里的方法第一个参数永远是 self,调用时不用传,Python 自动传入
class Student:
    def __init__(self, name, score):
        self.name = name        # 存到对象上
        self.score = score

    def intro(self):
        print(f"我是{self.name},考了{self.score}分")

s1 = Student("小明", 92)
s2 = Student("小红", 88)
s1.intro()   # 我是小明,考了92分(self 自动是 s1)
s2.intro()   # 我是小红,考了88分(self 自动是 s2)

注意:__init__self.name = name 是把参数存到对象上,两个 name 不是一回事(左边是对象的属性,右边是参数)。

属性:对象的”数据”

对象上的数据叫属性(attribute),随时可以访问和修改:

class BankAccount:
    def __init__(self, owner):
        self.owner = owner
        self.balance = 0

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

    def withdraw(self, amount):
        if amount > self.balance:
            print("余额不足")
            return
        self.balance -= amount
        print(f"取出 {amount},余额 {self.balance}")

acc = BankAccount("南山")
acc.deposit(500)     # 南山 存入 500,余额 500
acc.withdraw(200)    # 取出 200,余额 300
acc.withdraw(999)    # 余额不足

余额是对象自己的状态,方法操作自己的状态——数据和行为绑在一起了。这就是封装:外部只调用方法,不用管内部怎么算。

类属性 vs 实例属性

属性分两种:

class Student:
    school = "南山中学"        # 类属性:所有实例共享

    def __init__(self, name):
        self.name = name       # 实例属性:每个实例自己的

s1 = Student("小明")
s2 = Student("小红")
print(s1.school, s2.school)   # 南山中学 南山中学(共享)
s1.school = "北山中学"         # 给 s1 单独设了一个,不影响类
print(s1.school, s2.school)   # 北山中学 南山中学
print(Student.school)         # 南山中学(类的还是没变)

类属性适合放”所有实例都一样”的常量(校名、配置);实例属性放各自不同的数据。

__str__:让打印好看

直接打印对象会输出一串看不懂的地址:

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

    def __str__(self):
        return f"学生({self.name}, {self.score}分)"

s = Student("小明", 92)
print(s)          # 学生(小明, 92分)   —— 有了 __str__ 就好看了
print(str(s))     # 一样

__str__ 定义”这个对象转成字符串长什么样”,调试打印时非常有用。下一篇还会讲更多魔术方法。

什么时候该用类

不是所有代码都要写成类。经验法则:

  • 有一组相关的数据 + 对这些数据的操作 → 用类(如银行账户、学生、订单)
  • 只是一些独立函数 → 不用类,普通函数就够(如字符串处理工具)
  • 多个地方要维护同一份状态 → 用类很合适

记账本那个例子,如果升级成”多个账户、每个账户有余额”,用类就比散落的函数清晰得多。

新手坑

坑 1:方法忘了写 self。定义时第一个参数必须是 self(名字可以改但别改),调用时不传。

坑 2:忘写 __init__ 或拼错。写成 __init___int__,Python 不会报错,只是不初始化,属性访问时才报 AttributeError,很难查。

坑 3:类里直接放可变默认值。和函数默认参数一样的问题:

class Bad:
    items = []          # 所有实例共享同一个列表!

a = Bad(); b = Bad()
a.items.append(1)
print(b.items)          # [1]!b 也被影响了

要在 __init__ 里创建:self.items = []

小结

  • 类是模板,对象是实例;类名() 创建对象
  • __init__ 初始化,self 指当前对象,方法第一个参数永远是 self
  • 属性 = 对象的数据,方法 = 对象的行为,封装在一起
  • 类属性共享、实例属性独立;__str__ 让打印好看
  • 有”数据 + 行为”的捆绑需求才用类,别为了用而用

练习

  1. 定义一个 Book 类:书名、作者、价格,一个 discount(rate) 方法按折扣改价
  2. 定义一个 Circle 类:半径属性,area()perimeter() 方法
  3. 思考:s1 = Student("小明") 时,Python 内部到底发生了什么?__init__ 是什么时候被调用的,self 是谁传进去的?