Python 从入门到精通
第 14 / 36 篇
从环境安装和基础语法出发,逐步学习工程实践、自动化、数据处理与 Web 开发。
-
01
Python 怎么装、怎么用 -
02
变量、数字和字符串 -
03
列表:把一组数据放在一起 -
04
元组、集合和字典 -
05
条件判断:让程序做选择 -
06
循环:重复的事交给程序 -
07
函数:把代码整理成可复用的块 -
08
模块与包:拆分你的程序 -
09
输入、输出与字符串格式化 -
10
文件读写:保存程序的数据 -
11
异常处理:程序出错时怎么办 -
12
基础阶段练习:命令行记账本 -
13
类和对象:面向对象入门 -
14
继承、组合与特殊方法正在阅读 -
15
迭代器与生成器 -
16
列表推导式与生成器表达式 -
17
装饰器:给函数增加能力 -
18
上下文管理器与 with -
19
类型标注与 dataclass -
20
正则表达式:从文本中找规律 -
21
日期、时间与时区 -
22
日志与调试 -
23
虚拟环境与依赖管理 -
24
测试:让修改不再提心吊胆 -
25
网络请求:用 Python 调用 API -
26
网页解析与合规采集 -
27
操作 Excel、CSV 与批量文件 -
28
SQLite:给程序加一个数据库 -
29
数据分析入门:NumPy 与 Pandas -
30
画图:把数据变得直观 -
31
Flask 入门:做一个小网站 -
32
异步编程:同时处理多项任务 -
33
线程、进程与并发选择 -
34
性能分析与优化 -
35
项目结构、配置与发布 -
36
综合项目:从需求到上线
上一篇学了类和对象的基础。但面向对象不止”把数据和函数捆一起”——现实里东西是有层次的:狗是动物,猫也是动物,它们共享”会吃会睡”,又各有各的本事。继承就是表达这种”是某种东西”的关系。这一篇讲继承、组合,以及几个神奇的魔术方法。
继承:子类复用父类
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} 在吃饭")
def sleep(self):
print(f"{self.name} 在睡觉")
class Dog(Animal): # Dog 继承 Animal
def bark(self):
print(f"{self.name}:汪汪!")
class Cat(Animal): # Cat 继承 Animal
def meow(self):
print(f"{self.name}:喵~")
d = Dog("旺财")
c = Cat("咪咪")
d.eat() # 旺财 在吃饭(继承来的)
d.bark() # 旺财:汪汪!(自己的)
c.sleep() # 咪咪 在睡觉(继承来的)
c.meow() # 咪咪:喵~(自己的)
子类(Dog)自动拥有父类(Animal)的所有方法和属性,还能加自己的。这就是代码复用:公共逻辑写一次,子类各加各的。
覆写:子类改父类的方法
子类可以重新定义同名方法,覆盖父类行为:
class Dog(Animal):
def eat(self): # 覆写
print(f"{self.name} 狼吞虎咽地吃")
class Bird(Animal):
def eat(self):
print(f"{self.name} 啄着吃")
但更常见的是:想保留父类的逻辑,再加点自己的。用 super() 调父类的方法:
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 调父类 __init__,把 name 交给它处理
self.breed = breed
def eat(self):
super().eat() # 先执行父类的 eat
print("然后舔了舔嘴")
super() 就是”父类”,别自己复制父类代码,用 super 调用才是正确姿势。
多重继承(了解即可)
Python 支持一个类继承多个父类,但慎用——两个父类有同名方法时,调用顺序容易绕晕:
class Flyable:
def fly(self):
print("飞起来了")
class Swimmable:
def swim(self):
print("游起来了")
class Duck(Flyable, Swimmable):
pass
d = Duck()
d.fly() # 飞起来了
d.swim() # 游起来了
简单的”会飞、会游”这种组合用多重继承还行,复杂了建议用组合(下面讲)。
组合:优先于继承
很多时候,”有一个”比”是一个”更自然。比如汽车有发动机,而不是汽车继承发动机:
class Engine:
def start(self):
print("发动机启动了")
class Car:
def __init__(self):
self.engine = Engine() # 组合:Car 里有一个 Engine
def start(self):
self.engine.start() # 委托给它
print("汽车可以走了")
car = Car()
car.start()
# 发动机启动了
# 汽车可以走了
业界有句话叫“组合优于继承”(composition over inheritance):能用组合表达的关系,别硬套继承。继承是”is-a”(狗是一种动物),组合是”has-a”(车有一个发动机)。分不清时,优先组合。
魔术方法:让对象更”原生”
双下划线开头结尾的方法统称魔术方法,Python 在特定场景自动调用它们。上一篇的 __str__ 是其一,再认识几个常用的:
class Score:
def __init__(self, value):
self.value = value
def __str__(self):
return f"分数({self.value})"
def __len__(self): # len(obj) 时调用
return self.value
def __eq__(self, other): # == 时调用
return self.value == other.value
def __lt__(self, other): # < 时调用(排序用)
return self.value < other.value
def __add__(self, other): # + 时调用
return Score(self.value + other.value)
a = Score(85)
b = Score(90)
print(a == b) # False(__eq__)
print(a < b) # True(__lt__)
print(a + b) # 分数(175)(__add__)
print(len(a)) # 85(__len__)
print(sorted([b, a])) # [分数(85), 分数(90)](__lt__ 支持排序)
有了这些,自定义类就能像内置类型一样用 +、==、len()、排序。这就是”让类融入 Python 生态”。
property:把方法当属性用
有时候想”读取时计算一下”,又不想暴露方法。用 @property:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self): # 像属性一样访问,不用加括号
return self.width * self.height
@property
def is_square(self):
return self.width == self.height
r = Rectangle(4, 5)
print(r.area) # 20(不是 r.area()!)
print(r.is_square) # False
好处:外部用法不变(r.area),内部可以从”存好的值”改成”现算的”,调用方无感知。
新手坑
坑 1:继承层级太深。三层以内还好,五层以上改一个父类全局地震。浅一点,多用组合。
坑 2:忘记 super().__init__。子类定义了 __init__ 又不调 super 的,父类属性全没了,访问报 AttributeError。
坑 3:把 __str__ 写成 __repr__ 或反过来。两个都定义最省心:__repr__ 给开发者看(调试),__str__ 给用户看(print)。只写一个就写 __str__。
小结
- 继承:
class Dog(Animal),子类复用父类代码,可加新方法 - 覆写 +
super():保留父类逻辑再扩展,别复制父类代码 - 组合优于继承:is-a 用继承,has-a 用组合
- 魔术方法:
__eq__/__lt__/__add__/__len__让类支持运算符和内置函数 @property把计算方法变成属性,调用方无感知
练习
- 定义
Shape父类(name 属性、describe 方法),Circle和Rectangle继承它并各自实现 area() - 给
Money类实现__add__和__eq__,让两个金额对象能相加、比较 - 思考:鸟和飞机都能飞。用继承怎么写?用组合怎么写?哪种在”给鸟加个下蛋方法”时更不容易出问题?