行业资讯
Python面向对象编程——让代码和现实世界对齐
一、OOP 解决了什么问题编程本质上是把现实问题翻译成计算机能执行的逻辑。那现实世界长什么样——它不是一堆零散的变量和顺序执行的过程而是由一个个独立的个体组成的你用计算器不是拿一堆电路元件拼凑运算逻辑而是直接按按钮。你存钱不是手动改 Excel而是让账户对象帮你处理。面向过程写计算器长这样——数据和操作数据的过程是分离的# 面向过程数据是数据流程是流程n1int(input(第一个运算数))n2int(input(第二个运算数))opinput(请输入运算符)ifop:print(f{n1}{n2}{n1n2})elifop-:print(f{n1}-{n2}{n1-n2})面向对象则把数据和行为打包进一个整体# 面向对象先有计算器这个东西再操作它calcCalculator()calc.input()calc.result()面向对象代码量不一定会少但三个优势是面向过程给不了的面向过程面向对象复用每次用都得复制粘贴流程创建新对象就能反复调方法模块化代码是紧耦合的整体类可拆分到独立文件各管各的分工边界不清一改动牵全身一拨人写类一拨人用类一句话面向过程围绕流程组织代码面向对象围绕对象组织代码。你的大脑本来就用对象来理解世界让代码结构跟你大脑对齐写起来才不拧巴。二、类和对象图纸和实物类对某一类事物的抽象描述定义了这类事物有什么和能干什么。对象符合类描述的一个具体个体是类这张图纸生产出来的实物。如果Person是类那刘备就是一个对象。类描述事物分两方面静态特征 → 属性变量姓名、年龄动态特征 → 方法函数说话、走路classPerson:一个简单的人类模型name无名age18defintroduce(self):print(f我叫{self.name}今年{self.age}岁)# 创建对象并赋值lbPerson()lb.name刘备lb.age61lb.introduce()# 输出我叫刘备今年61岁从内存角度看类本质就是自定义的数据类型——int是个类10是其对象是其方法。Python 里一切皆对象这句话落到内存里就是连int、str的变量也遵循类→对象这套逻辑。三、类的内部结构拆解3.1 类变量 vs 实例变量定义位置归属修改影响类变量class块内、方法外类本身所有对象共享一处改处处变实例变量方法内通过self.xxx定义单个对象独有互不影响classRobot:population0# 类变量统计机器人数def__init__(self,name):self.namename# 实例变量每个机器人名字不同Robot.population1# 引用类变量Robot.xxx 或 self.__class__.xxxdefdestroy(self):Robot.population-1print(f{self.name}已销毁剩余{Robot.population}台)classmethoddefreport(cls):# cls 是类本身的引用print(f当前{cls.population}台机器人在线)踩坑提醒类变量和实例变量同名时self.xxx优先访问实例变量。如果你不小心用self.population 100会悄悄创建一个实例变量类变量纹丝不动——这种 bug 很难排查。3.2 三种方法对象方法、类方法、静态方法classDemo:defmethod(self):# 对象方法self 接收当前对象passclassmethoddefclass_method(cls):# 类方法cls 接收类本身passstaticmethoddefstatic_method():# 静态方法不自动传任何参数就是个普通函数pass调用方式差异obj.method()→ Python 自动填self等价于Demo.method(obj)Demo.class_method()→ Python 自动填cls不依赖具体对象Demo.static_method()→ 直接调跟类外函数没区别什么时候用哪个如果你需要访问实例数据self.xxx→ 对象方法如果需要访问类变量cls.xxx→ 类方法如果纯属工具函数跟类和实例都无关 → 静态方法。别三种混着用逻辑会很乱。3.3 构造方法__init__对象创建时自动调用用于初始化。几乎所有类都有它classStudent:def__init__(self,name,score):self.namename self.scorescore注意点__init__不是真正的构造函数——先由__new__创建对象再用__init__初始化。日常你只需要关心__init__。3.4 Python 式私有成员Python没有真正的 private 关键字靠两套约定_name单下划线纯约定“我是内部成员别碰”Python 不会拦你。__name双下划线触发名称改写name mangling变成_ClassName__name外部直接访问会AttributeError。classBankAccount:def__init__(self,owner,balance):self.ownerowner# 公有self.__balancebalance# 私有外部碰不到defdeposit(self,amount):ifamount0:self.__balanceamountdefget_balance(self):returnself.__balance需要保护的数据用双下划线需要暴露的用公有方法。这就是封装的落地——数据藏在对象里面外面只能通过你允许的方法操作。3.5 文档字符串类、方法的第一行字符串就是文档靠__doc__读取classStudent:学生模型存储学号和成绩defexam(self,subject):参加考试 Args: subject: 考试科目 passprint(Student.__doc__)# 学生模型存储学号和成绩print(Student.exam.__doc__)# 参加考试...好的文档字符串 给别人用的说明书。尤其你写类给别人调的时候__doc__是救命稻草。四、继承复用与扩展class子类(父类):def__init__(self,...):父类.__init__(self,...)# 必须显式调用三条铁律子类__init__必须显式调父类的__init__否则父类的初始化逻辑不会执行——静默 bug很难排查。如果子类没定义__init__Python 会自动沿继承链往上找找到就用。Python 支持多继承class C(A, B)但层次一深就是灾难能用单继承就别碰多继承。classMember:def__init__(self,name):self.namenamedefshow(self):print(fMember:{self.name})classTeacher(Member):def__init__(self,name,subject):Member.__init__(self,name)# 必须显式调用self.subjectsubjectdefshow(self):# 重写父类方法Member.show(self)# 想保留父类逻辑显式调print(f科目:{self.subject})五、多态同一个接口不同的行为多态的本质方法查找沿着「对象 → 类 → 父类」链条往上走找到就停。所以不同子类重写同名方法后调用同样的obj.show()会产生不同效果。classAnimal:defspeak(self):return...classDog(Animal):defspeak(self):return汪汪classCat(Animal):defspeak(self):return喵喵# 同一接口不同行为forpetin[Dog(),Cat()]:print(pet.speak())# 汪汪 / 喵喵多态的价值不在于省代码而在于写通用逻辑。你能写一个feed(animal)函数传入任何动物的子类都正常工作——不用判断是狗还是猫。这是 OOP 最强力的抽象能力。六、特殊方法让你的类像内置类型一样工作Python 把__xxx__这种双下划线方法叫 special method魔法方法。它不是语法糖是 Python 的语言协议。你实现这些方法你的类就能被print、被len、被、被[]索引特殊方法触发场景你的类能干嘛__str__print(obj),str(obj)友好的字符串表示__len__len(obj)获取长度__getitem__obj[i]索引访问__lt__obj1 obj2比较大小__add__obj1 obj2对象拼接一个完整的栈类一次性演示全部classStack:def__init__(self,*items):self._itemslist(items)defpush(self,item):self._items.append(item)defpop(self):returnself._items.pop()ifself._itemselseNonedef__str__(self):returnstr(self._items)def__len__(self):returnlen(self._items)def__getitem__(self,i):returnself._items[i]if0ilen(self)elseNonedef__add__(self,other):拼接两个栈returnStack(*(self._itemsother._items))有了这些你写的栈用起来就跟list一样自然sStack(A,B,C)print(s)# [A, B, C] ← __str__print(len(s))# 3 ← __len__print(s[1])# B ← __getitem__s2sStack(D,E)# ← __add__特殊方法的本质实现协议融入生态。定义了__len__你的类就能塞进任何接受len()的通用函数里。这就是 Python「一切皆对象」的真正威力。七、异常处理优雅地应对意外异常 ≠ 错误。它是程序运行中的意外情况你得提前写好预案。7.1 基础结构try / except / else / finallytry:numint(input(输入数字))result100/numexceptValueError:print(不是数字)exceptZeroDivisionError:print(不能除以 0)else:print(f结果{result})# 没异常才执行finally:print(不管有没有异常我都会执行)except指定异常类型 → 精确捕获不指定 → 捕获所有危险别这么干。else没异常才走这里跟try里的代码分开逻辑更清晰。finally无论如何都执行用来释放资源关闭文件、断开连接。7.2 自定义异常继承Exception用raise抛出用as接收classInsufficientBalanceError(Exception):余额不足异常def__init__(self,balance,required):self.balancebalance self.requiredrequireddefwithdraw(balance,amount):ifamountbalance:raiseInsufficientBalanceError(balance,amount)returnbalance-amounttry:withdraw(100,500)exceptInsufficientBalanceErrorase:print(f余额{e.balance}需要{e.required}还差{e.required-e.balance})什么时候自定义异常当你需要区分这是业务逻辑的意外和这是程序的 bug时。ValueError太泛了BalanceNotEnoughError一看就懂。八、正则表达式文本处理的瑞士军刀正则表达式跟 OOP 看似两个话题但实际项目中它们经常结伴出现——你写一个用户注册类构造方法里拿正则验证邮箱、手机号、密码强度不合法就抛异常。它就是文本验证和提取的标准工具。8.1 什么时候用正则验证邮箱格式对不对、手机号是不是 11 位数字提取从日志里抠出 IP 地址、从 HTML 里抓内容替换把一串文本中的所有电话号码打码分割按多种分隔符拆分字符串8.2 re 模块核心方法importre text价格¥99.9库存20 件re.search(r\d\.?\d*,text)# 找第一个匹配99.9re.match(r价格,text)# 从开头匹配必须从头匹配re.findall(r\d,text)# 找所有匹配[99, 9, 20]re.sub(r\d,XX,text)# 替换所有数字价格¥XX.XX库存XX 件re.split(r[,],text)# 按中文逗号或英文逗号分割searchvsmatchmatch要求从字符串开头匹配search在整个字符串中搜索。日常验证用match提取用search或findall。8.3 常用元字符速查符号含义示例.任意字符除换行a.b→aab,acb*前一个字符 0 次或多次ab*→a,ab,abb前一个字符 1 次或多次ab→ab,abb不匹配a?前一个字符 0 次或 1 次ab?→a,ab{n}恰好 n 次\d{11}→ 11 位数字{n,m}n 到 m 次\d{6,18}→ 6-18 位^字符串开头^1→ 必须以 1 开头$字符串结尾\.com$→ 以 .com 结尾[]字符类[a-z]→ 任意小写字母()分组捕获(\d{3})-(\d{4})→ 捕获两段数字\d数字 [0-9]\d→ 一串数字\w字母数字下划线 [a-zA-Z0-9_]\w→ 单词\s空白字符\s→ 连续空白8.4 贪婪 vs 非贪婪默认贪婪匹配——尽可能多吃字符。加?变非贪婪——尽量少吃textdiv内容1/divdiv内容2/divre.findall(rdiv.*/div,text)# 贪婪[div内容1/divdiv内容2/div] 吞掉了全部re.findall(rdiv.*?/div,text)# 非贪婪[div内容1/div, div内容2/div]这是正则最容易踩的坑之一。提取 HTML 内容时记住加?。8.5 预编译优化多次使用同一个正则用re.compile提前编译性能更好、代码更清晰# 不好每次调用都重新编译正则defvalidate_phone(phone):returnbool(re.match(r^1[3-9]\d{9}$,phone))# 好编译一次复用PHONE_PATTERNre.compile(r^1[3-9]\d{9}$)defvalidate_phone(phone):returnbool(PHONE_PATTERN.match(phone))8.6 三个实战常用正则# 邮箱EMAILre.compile(r^[\w.-][\w-]\.[a-zA-Z]{2,}$)# 手机号中国大陆PHONEre.compile(r^1[3-9]\d{9}$)# 提取日期 YYYY-MM-DDDATE_EXTRACTORre.compile(r\d{4}-\d{2}-\d{2})8.7 用类封装验证器——正则 × OOP把正则封装进类配合异常处理是实际项目中最常见的组合importreclassValidationError(Exception):输入验证失败passclassInputValidator:输入验证器——OOP 正则 异常的实战组合EMAIL_REre.compile(r^[\w.-][\w-]\.[a-zA-Z]{2,}$)PHONE_REre.compile(r^1[3-9]\d{9}$)classmethoddefemail(cls,value):ifnotcls.EMAIL_RE.match(value):raiseValidationError(f邮箱格式无效:{value})classmethoddefphone(cls,value):ifnotcls.PHONE_RE.match(value):raiseValidationError(f手机号格式无效:{value})# 使用try:InputValidator.email(userexample.com)# OKInputValidator.phone(13812345678)# OKInputValidator.email(not-an-email)# 抛异常exceptValidationErrorase:print(e)这个例子把前面学的类、类方法、正则表达式、自定义异常全用上了。以后你在 FastAPI 里写 Pydantic validator 或者写数据校验中间件套路跟这一模一样。总结这篇覆盖了 Python OOP 的核心知识 正则表达式。用一张表收尾概念一句话类一类事物的抽象描述图纸对象类的具体实例实物封装数据 方法打包进对象用私有成员控制访问继承子类复用父类代码显式调父类.__init__多态同名方法沿继承链查找不同子类不同行为特殊方法实现__str__/__len__等协议让自定义类融入 Python 生态异常try/except/else/finally 自定义异常优雅应对意外正则文本验证和提取的标准工具搭配re.compile预编译这些是后续学 FastAPI、Pydantic 模型、AI Agent 框架的绝对基础——框架源码里全是类和继承业务逻辑里全是正则和异常。地基打牢了上层才不会晃。
郑州网站建设
网页设计
企业官网