资讯详情

资讯详情

Python类的详细定义与使用案例(实例讲解)

前言类讲再多语法不如看几个真正用起来的例子。这篇不讲抽象定义那些在别处能查到而是用四个由浅入深的案例把定义类和用好类之间的差距摆出来同一个需求属性放错位置、方法该不该拆、什么时候该用继承、什么时候只要组合——这些选择直接决定代码好不好改。先纠正一个常见误解很多人以为类就是把一堆函数和变量圈在一起。真正的分界线是封装——类的价值在于它能守住自己的内部约束比如余额不能为负并对外只暴露必要的接口。把self.balance随便交给外部改类就退化成了一个更好看的字典。示例基于 Python 3.8用到 3.7 的dataclass会注明。一、定义的三要素构造、属性、方法一个能用的类通常包含__init__里初始化实例属性、若干操作这些属性的方法、以及必要时限制外部的访问。# 适用于 Python 3.8class BankAccount:银行账户演示封装——余额只能通过方法改变。def __init__(self, owner, balance0):self.owner ownerself._balance balance # 下划线表示内部用别直接碰def deposit(self, amount):if amount 0:raise ValueError(存款金额必须为正)self._balance amountreturn self._balancedef withdraw(self, amount):if amount 0:raise ValueError(取款金额必须为正)if amount self._balance:raise ValueError(余额不足)self._balance - amountreturn self._balancepropertydef balance(self):只读属性外部可查不能直接赋值。return self._balance要点_balance用单个下划线开头是这是内部实现的约定外部通过balance这个只读属性查询通过deposit/withdraw修改。这样一来余额不能为负的规则就锁在类里不会因为某处忘记校验而被破坏。# 适用于 Python 3.8acc BankAccount(小明, 100)acc.deposit(50)print(acc.balance) # 150try:acc.withdraw(1000)except ValueError as e:print(被拦住了, e)二、案例形状体系与多态当多个类共享同一套接口、各自实现不同行为时继承加多态就派上用场。调用方只面对形状这个抽象不关心具体是圆还是矩形。# 适用于 Python 3.8import mathclass Shape:def area(self):raise NotImplementedError(子类必须实现 area)def describe(self):return f{type(self).__name__} 面积 {self.area():.2f}class Circle(Shape):def __init__(self, r):self.r rdef area(self):return math.pi * self.r ** 2class Rect(Shape):def __init__(self, w, h):self.w, self.h w, hdef area(self):return self.w * self.hfor s in (Circle(1), Rect(2, 3)):print(s.describe())要点父类的area用raise NotImplementedError声明我定义接口但实现交给子类describe只依赖area()这个接口于是对所有子类都成立。这就是面向接口编程——新增一种形状时只要实现area()describe一行都不用改。三、案例用组合写购物车购物车里有若干商品商品是独立实体可单独存在、可被多个购物车引用。这种有一个的关系应该用组合而不是继承购物车不是商品的一种。# 适用于 Python 3.8class Product:def __init__(self, name, price):self.name nameself.price pricedef __repr__(self):return fProduct({self.name!r}, {self.price!r})class Cart:def __init__(self):self.items [] # 组合持有 Product 列表def add(self, product, qty1):self.items.append((product, qty))def total(self):return sum(p.price * q for p, q in self.items)def __len__(self): # 支持 len(cart)return len(self.items)cart Cart()cart.add(Product(键盘, 199))cart.add(Product(鼠标, 89), qty2)print(cart.total()) # 377print(len(cart)) # 2要点Cart只管存Product完全不关心商品的内部结构__len__让len(cart)自然可用。如果哪天要换成服务类商品只要它也有pricetotal()照常工作——这就是鸭子类型带来的解耦。注意total()用的是推导式 sum初始值为 0空购物车也返回 0。四、案例用 dataclass 减少样板如果类的主要工作是存一组字段dataclass3.7能自动生成__init__、__repr__、__eq__少写一大段重复代码。# 适用于 Python 3.7from dataclasses import dataclass, fielddataclassclass Student:name: strscores: list field(default_factorylist) # 可变默认值要用 factorydef average(self):if not self.scores:return 0.0return sum(self.scores) / len(self.scores)s Student(小美, [90, 80, 100])print(s) # Student(name小美, scores[90, 80, 100])print(s.average()) # 90.0print(s Student(小美, [90, 80, 100])) # True要点dataclass生成的__eq__按字段逐个比较所以两个内容相同的学生对象相等。字段有默认值时必须注意顺序——有默认值的字段不能排在无默认值字段前面。可变默认值要用field(default_factorylist)不能直接写[]原因和普通函数的可变默认参数是同一个坑。五、四个案例的对照案例用到的机制关键判断银行账户封装、property内部状态要被保护改动走方法形状体系继承、多态、抽象接口多个类共享接口各有实现购物车组合、__len__有一个关系不继承学生成绩dataclass以存字段为主减少样板常见坑点坑 1把该保护的属性直接暴露。❌ 外部随手acc._balance -100—— 封装形同虚设余额出现负数。✅ 内部状态用下划线命名通过方法或只读property访问。坑 2为了复用而用继承。❌class Cart(list):只是想用列表的append结果继承了sort、insert等一堆无关行为。✅ 用组合self.items []只暴露需要的方法。坑 3父类接口没实现却不说。❌ 父类area()直接pass返回None子类忘了实现时静默出错。✅ 用raise NotImplementedError或抽象基类让遗漏尽早暴露。坑 4可变的类属性被所有实例共享。❌class Student: scores []—— 所有学生共用同一个列表。✅ 放进__init__或用field(default_factorylist)。坑 5__eq__定义了却没定义__hash__。❌ 自定义__eq__后想把对象放进set—— 默认__hash__被置空抛TypeError。✅ 需要哈希就显式定义一致的__hash__用dataclass(frozenTrue)可自动获得。坑 6property只有 getter外部赋值报错还以为坏了。❌acc.balance 100抛AttributeError提示信息随版本不同却不知道是只读属性所致。✅ 明确只读是有意设计需要可写就补 setter并在 setter 里做校验。坑 7dataclass字段顺序写错。❌name: str 匿名排在scores: list前面 —— 定义时报TypeError。✅ 无默认值的字段放前面有默认值的放后面。总结做法适用场景封装 property内部状态有约束、需要守住规则继承 多态多个类共享接口、行为各自实现组合一个类持有另一个对象dataclass以保存字段为主想少写样板__len__/__repr__等让对象用起来像内置类型一句话总结定义类只是起点用好类靠的是判断关系——状态要不要保护、对象是是还是有、字段要不要样板自动化。四个案例里账户靠封装、形状靠多态、购物车靠组合、学生靠数据类选对了机制代码自然短而稳。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →