🎯 学习目标
- 理解类、对象、属性、方法四大概念,理解
self - 区分类属性与实例属性、实例/类/静态三种方法
- 掌握封装手段:
_x、__x与@property - 掌握继承、方法重写、
super()、多继承 MRO 与多态 - 理解 Python"鸭子类型"哲学
- 掌握常用魔法方法、
dataclass与枚举
1类与对象:从一张"图纸"说起
类(class)是抽象模板(如"学生"),对象/实例(object/instance)是按模板造出的具体个体(如"学生张三")。对象包含两部分:
- 属性(数据):姓名、年龄、成绩……
- 方法(行为):学习、考试、自我介绍……
面向过程关注"先做什么再做什么",面向对象关注"谁(对象)来做、它有什么数据和能力"。
class Student:
# __init__ 是构造方法,创建对象时自动调用
def __init__(self, name, age, score):
self.name = name # 实例属性
self.age = age
self.score = score
def introduce(self): # 实例方法,第一个参数永远是 self
return f"我是{self.name},{self.age}岁,成绩{self.score}"
def is_excellent(self):
return self.score >= 90
# 实例化:类名(...) 自动调用 __init__,创建具体对象
s1 = Student("张三", 20, 95)
s2 = Student("李四", 21, 76)
print(s1.name) # 张三 访问属性
print(s1.introduce()) # 我是张三… 调用方法
print(s2.is_excellent()) # False
self 到底是什么?它代表"当前这个对象自己"。调用 s1.introduce() 时,Python 自动把 s1 作为第一个参数传给 self,等价于 Student.introduce(s1)。方法内访问自己的属性/其他方法都要写 self.xxx。
2类属性与实例属性
class Dog:
species = "犬科动物" # 类属性:所有对象共享
def __init__(self, name):
self.name = name # 实例属性:各对象独立
d1 = Dog("旺财")
d2 = Dog("大黄")
print(d1.species, d2.species) # 犬科动物 犬科动物
Dog.species = "家犬" # 修改类属性,所有实例都受影响
print(d2.species) # 家犬
d1.species = "个体覆盖" # 给 d1 新建同名实例属性(遮蔽类属性)
print(d1.species, d2.species) # 个体覆盖 家犬
# 典型用途:统计创建了多少个对象
class Counter:
count = 0
def __init__(self):
Counter.count += 1 # 修改类属性要通过类名
Counter(); Counter(); Counter()
print(Counter.count) # 3
不要通过 self.类属性 = 值 去修改类属性——那只会给当前对象新建一个同名实例属性,类属性本身没变。
3三种方法
class Temperature:
unit = "摄氏度"
def __init__(self, celsius):
self.celsius = celsius
def show(self): # 实例方法:能访问实例数据(最常用,90%场景)
return f"{self.celsius}{Temperature.unit}"
@classmethod
def from_fahrenheit(cls, f): # 类方法:第一个参数是类本身,常用于"备选构造器"
c = (f - 32) * 5 / 9
return cls(c) # cls(...) 等价于 Temperature(...)
@staticmethod
def is_freezing(celsius): # 静态方法:既不用 self 也不用 cls,就是放在类里的普通函数
return celsius <= 0
t = Temperature(25)
print(t.show()) # 25摄氏度
t2 = Temperature.from_fahrenheit(100) # 用华氏度创建对象
print(t2.celsius) # 37.777...
print(Temperature.is_freezing(-5)) # True
4封装:保护对象的数据
封装 = 把数据和操作数据的方法绑在一起,并对外隐藏内部细节。Python 没有真正的 private,靠命名约定表达访问限制:
| 写法 | 含义 |
|---|---|
self.name | 公开属性,外部可自由访问 |
self._name | 约定的"内部使用",请不要外部访问(但语法上可以) |
self.__name | 双下划线触发名称改写为 _类名__name,外部不能直接 obj.__name 访问 |
@property:让方法"伪装"成属性(重点)
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance # 真正"藏起来"的余额
@property
def balance(self): # 读取:account.balance(像属性,实为方法)
return self.__balance
@balance.setter
def balance(self, value): # 赋值时自动校验:account.balance = 100
if value < 0:
raise ValueError("余额不能为负")
self.__balance = value
@property
def is_rich(self): # 计算型属性(只读)
return self.__balance >= 1_000_000
acc = BankAccount("张三", 500)
print(acc.balance) # 500(不用加括号!)
acc.balance = 800 # 自动走 setter 校验
print(acc.is_rich) # False
# acc.balance = -1 # ValueError: 余额不能为负
property 的价值:① 对属性赋值做校验/联动;② 把计算属性暴露成 obj.x;③ 以后把普通属性改成受控属性时,调用方代码不用改。
5继承:站在父类的肩膀上
class Animal: # 父类(基类/超类)
def __init__(self, name):
self.name = name
def eat(self):
return f"{self.name}在吃东西"
def speak(self):
return "某种叫声"
class Cat(Animal): # 子类继承父类,自动拥有其全部属性方法
def speak(self): # 方法重写(override):同名覆盖
return f"{self.name}:喵~"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 调用父类构造方法,复用初始化逻辑
self.breed = breed # 子类再扩展自己的属性
def speak(self):
return f"{self.name}:汪!"
def fetch(self):
return f"{self.name}去捡飞盘了"
c = Cat("咪咪")
d = Dog("旺财", "金毛")
print(c.eat()) # 咪咪在吃东西 ← 继承来的方法
print(c.speak()) # 咪咪:喵~ ← 重写后的方法
print(d.speak(), d.breed) # 旺财:汪! 金毛
print(isinstance(c, Animal)) # True 猫也是动物
print(isinstance(c, Cat)) # True
super() 用于调用父类方法,重写 __init__ 时几乎必须调用 super().__init__(...),否则父类的初始化逻辑丢失。
多继承与 MRO
class A:
def hello(self): return "A"
class B(A):
def hello(self): return "B"
class C(A):
def hello(self): return "C"
class D(B, C): # Python 支持多继承
pass
print(D().hello()) # B
print(D.__mro__) # 方法解析顺序:D -> B -> C -> A -> object
# Python 3 用 C3 线性化算法决定查找顺序,同名方法取 MRO 中最靠左的
多继承强大但复杂,易产生菱形继承等问题。工程上优先:单继承 + 组合(在类中持有其他类的对象);多继承主要用于 Mixin(混入)模式。所有类最终都继承自 object。
6多态与鸭子类型
多态:同一方法调用,不同对象表现出不同行为:
def make_sound(animal):
print(animal.speak()) # 不关心具体类型,只要它有 speak 方法
make_sound(Cat("咪咪")) # 咪咪:喵~
make_sound(Dog("旺财", "金毛")) # 旺财:汪!
Python 奉行鸭子类型(Duck Typing):"如果一只鸟走起来像鸭子、叫起来像鸭子,那它就是鸭子。"——不要求继承同一父类,只要具备相同方法/属性就能被同样使用:
class ToyDuck: # 没有继承 Animal
def speak(self):
return "嘎嘎(玩具声)"
make_sound(ToyDuck()) # 照样能用!因为它也有 speak()
# len() 就是鸭子类型的典范:列表、字符串、字典、集合都支持
len([1,2]); len("abc"); len({"a":1})
7魔法方法(双下方法)
形如 __init__、__str__ 的方法叫魔法方法/特殊方法,它们让自定义对象支持内置操作符和函数:
| 魔法方法 | 触发时机 | 用途 |
|---|---|---|
__init__ | 对象创建时 | 初始化属性 |
__str__ | str()/print() | 人类可读的字符串表示 |
__repr__ | 调试/容器内显示 | 开发者表示,建议形如 类名(参数) |
__len__ | len(obj) | 长度 |
__eq__/__lt__/... | == < > | 自定义比较(实现后可直接 sorted) |
__add__/__sub__ | + - | 运算符重载 |
__getitem__ | obj[k] / for 遍历 | 让对象支持索引/切片/迭代 |
__contains__ | x in obj | 成员判断 |
__call__ | obj() | 让实例像函数一样被调用 |
class Money:
def __init__(self, amount, currency="CNY"):
self.amount = amount
self.currency = currency
def __str__(self):
return f"{self.amount:.2f} 元"
def __add__(self, other): # 支持 + 运算
if self.currency != other.currency:
raise ValueError("币种不同")
return Money(self.amount + other.amount, self.currency)
def __eq__(self, other):
return self.amount == other.amount and self.currency == other.currency
def __repr__(self):
return f"Money({self.amount}, {self.currency!r})"
print(Money(10) + Money(5)) # 15.00 元
print(Money(10) == Money(10)) # True
print(repr(Money(10))) # Money(10, 'CNY')
8dataclass 与枚举
dataclass:数据类(Python 3.7+)
当一个类主要用来存数据,__init__、__repr__ 等样板代码可自动生成:
from dataclasses import dataclass
@dataclass
class Article:
title: str # 类型注解必填
category: str = "未分类"
views: int = 0
a = Article("Python入门", "编程", 1000)
print(a) # Article(title='Python入门', category='编程', views=1000)
print(a.title) # 属性照常访问
a.views += 1
# 自动生成了 __init__、__repr__、__eq__,NLP 中表示"文档/样本"非常合适
枚举 Enum:固定的一组取值
from enum import Enum
class Sentiment(Enum):
POSITIVE = "正面"
NEGATIVE = "负面"
NEUTRAL = "中性"
label = Sentiment.POSITIVE
print(label) # Sentiment.POSITIVE
print(label.name, label.value) # POSITIVE 正面
print(label == Sentiment.POSITIVE) # True
# 比直接用字符串 "positive" 更安全:避免拼错、取值受限、代码可提示
9NLP 场景示例:文档与分词器
class Document:
"""表示一篇待分析的文档。"""
def __init__(self, doc_id, text):
self.doc_id = doc_id
self.text = text
self.tokens = [] # 分词结果
def word_count(self):
return len(self.tokens)
def __len__(self):
return len(self.text)
def __repr__(self):
return f"Document(id={self.doc_id}, {len(self.text)}字)"
class Tokenizer:
"""简易分词器(NLP 篇会换成 jieba)。"""
def __init__(self, stopwords=None):
self.stopwords = set(stopwords or []) # 停用词集合
def __call__(self, doc): # tokenizer(doc) 直接切词
import re
words = re.findall(r"[\u4e00-\u9fff]|[a-zA-Z]+", doc.text)
doc.tokens = [w for w in words if w not in self.stopwords]
return doc.tokens
doc = Document(1, "Python 很 好 学 python")
tk = Tokenizer(stopwords={"很"})
print(tk(doc)) # ['Python', '好', '学', 'python']
print(doc.word_count()) # 4
print(len(doc)) # 文本字符数
10本章小结与练习
📌 关键结论
- 类是模板,实例是具体对象;
__init__初始化,实例方法首参是 self。 - 类属性共享、实例属性独立;类方法
@classmethod适合做备选构造器。 - 封装靠约定:单下划线"内部使用",property 控制读写校验。
- 继承用
super()复用父类逻辑;多态 + 鸭子类型是 Python 风格核心。 - 数据容器优先
@dataclass;固定标签集合用 Enum。