深色模式
第 09 章 · 类与对象:把数据和操作打包在一起
本章目标
会定义类、创建对象、用继承复用代码,理解"数据 + 行为"的封装思想。
9.1 为什么需要类
前面写的是"函数 + 一堆变量"。当数据变多、又有一堆相关操作时,会乱:
python
dog_name = "旺财"
dog_age = 3
def dog_bark(name):
print(f"{name}:汪汪!")类做的事:把属于同一个东西的数据和行为,装进一个盒子里。
python
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print(f"{self.name}:汪汪!")
def birthday(self):
self.age += 1
d = Dog("旺财", 3) # 创建一个对象(实例)
d.bark() # 旺财:汪汪!
d.birthday()
print(d.age) # 49.2 三个核心概念
- 类(class):模板/图纸,如
Dog - 实例(instance):按图纸造出来的具体东西,如
d = Dog("旺财", 3) - 属性(attribute):对象身上的数据,如
d.name - 方法(method):对象身上的函数,如
d.bark()
__init__ 和 self
python
class Dog:
def __init__(self, name): # 造对象时自动执行
self.name = name # 把参数存到对象身上__init__叫初始化方法,Dog("旺财")时自动被调用self代表"这个对象自己",必须是第一个参数(Python 自动传,你不用管)- 实例方法里访问自己的数据,一律写
self.xxx
9.3 属性:类属性 vs 实例属性
python
class Dog:
species = "犬科" # 类属性:所有实例共享
def __init__(self, name):
self.name = name # 实例属性:每个对象自己的
a, b = Dog("旺财"), Dog("来福")
print(a.species, b.species) # 犬科 犬科
Dog.species = "犬科动物"
print(a.species) # 犬科动物⚠️ 易错点:类属性是可变对象时,所有实例会共享同一份:
python
class Bad:
items = [] # ❌ 所有实例共用这个列表
x, y = Bad(), Bad()
x.items.append(1)
print(y.items) # [1] ← y 也被影响了
class Good:
def __init__(self):
self.items = [] # ✅ 每个实例一份9.4 打印对象更友好:__str__ / __repr__
python
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self): # print(obj) 时显示
return f"User({self.name}, {self.age})"
def __repr__(self): # 调试/列表里显示
return f"User(name={self.name!r}, age={self.age})"
print(User("小明", 18)) # User(小明, 18)
print([User("小明", 18)]) # [User(name='小明', age=18)]这叫魔术方法(双下划线开头结尾)。类似的还有 __len__、__eq__,需要时再查。
9.5 继承:少写重复代码
python
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} 发出了声音")
class Dog(Animal): # 继承 Animal
def speak(self): # 覆盖父类方法
print(f"{self.name}:汪汪!")
class Cat(Animal):
def __init__(self, name, indoor=True):
super().__init__(name) # 调用父类初始化
self.indoor = indoor
Dog("旺财").speak() # 旺财:汪汪!
Cat("咪咪").speak() # 咪咪 发出了声音(用父类的)class 子类(父类):就是继承- 子类可以重写父类方法,也可以用
super()复用父类逻辑 - 多个类有共同行为时,抽到父类里去,重复代码就消失了
多重继承(了解)
python
class A:
def hello(self):
print("A")
class B(A):
pass
class C(A):
pass
class D(B, C): # 同时继承两个
pass
D().hello()Python 用 MRO(方法解析顺序)决定走谁的方法,规则复杂。多重继承的解析顺序较复杂,通常用组合(把别的对象当属性)更清晰。
9.6 "私有"变量
python
class Account:
def __init__(self, balance):
self._balance = balance # 单下划线:约定"别乱碰"
self.__secret = "内部" # 双下划线:名字会被"改写"
def show(self):
return self._balance
a = Account(100)
print(a._balance) # 100 ← 单下划线只是约定,挡不住你(但请不要这样用)
# print(a.__secret) # ❌ AttributeError
print(a._Account__secret) # 内部 ← 双下划线被改写成 _类名__属性名所以准确的说法是:
_xxx:约定的礼貌——"这是我内部用的,你别动"__xxx:会被改名为_类名__xxx(叫 name mangling),挡住的是"不小心写重名",不是安全机制(真想访问照样能访问)
Python 的哲学是"我们都是成年人":不设硬门禁,靠约定和文档。看到别人类里带下划线的属性,请用它的公开方法,不要直接改。
9.7 几种常用方法类型
python
class Circle:
pi = 3.14159
def __init__(self, r):
self.r = r
def area(self): # 实例方法:要用 self
return self.pi * self.r ** 2
@classmethod
def from_diameter(cls, d): # 类方法:常用于"另一种构造方式"
return cls(d / 2)
@staticmethod
def describe(): # 静态方法:就是放在类里的普通函数
return "圆是一个平面图形"
@property
def diameter(self): # 属性方法:像属性一样用,不用加括号
return self.r * 2
c = Circle.from_diameter(10)
print(c.area()) # 78.53975
print(c.diameter) # 10 ← 注意没有括号@property 很实用:对外看起来是数据,内部可以加计算或校验逻辑。
9.8 迭代器与生成器
术语先分清(面试也常问):
- 可迭代对象(iterable):能用
for遍历的东西 —— 列表、字典、字符串、文件…… 判断标准是实现了__iter__(老代码也允许用__getitem__) - 迭代器(iterator):
iter(可迭代对象)拿到的东西,内部记着"读到哪了",实现了__next__
python
data = [1, 2]
it = iter(data) # 把列表变成迭代器
print(next(it)) # 1
print(next(it)) # 2
# print(next(it)) # ❌ StopIteration(遍历完了)想自己造一个可迭代对象,就给类实现 __iter__ / __next__(下面是个最小可运行例子,看懂结构就行):
python
class CountDown:
"""倒计时:for x in CountDown(3) 依次得到 3 2 1"""
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration # 用这个异常告诉 for:没了
value = self.current
self.current -= 1
return value
for n in CountDown(3):
print(n) # 3 2 1日常写代码很少需要手写这个,因为用下面的生成器更短。
更常用的是生成器——用 yield 的函数,逐个"产出"值,省内存:
python
def countdown(n):
while n > 0:
yield n # 产出后暂停,下次从这里继续
n -= 1
for x in countdown(3):
print(x) # 3 2 1生成器表达式(把列表推导式的方括号换成圆括号):
python
nums = [1, 2, 3, 4, 5]
gen = (n * n for n in nums) # 不一次性算完,用一个算一个
print(sum(gen)) # 55处理大文件时特别有用:
python
def read_lines(path):
with open(path, encoding="utf-8") as f:
for line in f:
yield line.rstrip()9.9 练一练
- 写
Student类:姓名、成绩列表,方法有average()、is_pass(),__str__输出"姓名:平均分"。 - 写
BankAccount类:deposit()、withdraw()(余额不足抛异常)、balance用@property暴露。 - 写
Shape父类和Rectangle、Circle子类,各自实现area()。 - 用生成器实现
fib(n),产出前 n 个斐波那契数。
本章关键词
class、__init__、self、实例属性 / 类属性、__str__、继承与 super()、@property / @classmethod / @staticmethod、yield