资讯详情

资讯详情

Python面向对象编程:从脚本到工程的关键一跃

简介本资源为2021年出版的《Python Object-Oriented Programming》第四版英文原版PDF由Steven F. Lott与Dusty Phillips合著面向希望系统掌握Python面向对象编程的开发者。全书依照“类与对象基础—封装与继承—多态与特殊方法—设计模式—测试与维护”的脉络展开既覆盖OOP核心概念也给出构建健壮应用和可维护Python库的实际方法适合从入门到进阶的Python工程师及教学参考。资源共1个文件为PDF格式压缩包大小约6.51MB便于在线阅读或导入笔记工具。目前已有43人学习下载。相比零散教程这份完整英文原版能帮助读者建立面向对象设计思维同时理解异常处理、单元测试、版本控制与依赖管理等工程实践细节适合作为长期案头参考书。1. 先搞懂一个概念面向对象不是玄学是组织代码的思维刚学Python的朋友都有过这种遭遇写几十行小脚本顺顺利利程序长到上千行就一团乱麻——改一个小功能崩了三个地方。这时候需要的不是更多技巧而是更好的组织代码的方式。面向对象编程就是这种更好的方式它不玄。核心一句话把数据和操作数据的函数打包成独立的小单元叫作对象。每个对象各管一摊协作时通过明确的接口说话。好比餐厅厨师炒菜、服务员传菜、收银员结账各司其职。《Python面向对象编程第四版》正是讲这件事从类与对象讲到继承、异常、数据类、设计模式再到测试与并发适合一写复杂程序就发怵的初学者。2. 为什么必学面向对象从脚本到工程的关键一跃2.1 先看一个失控的例子加功能的脚本如何变成乱麻先复制一个很常见的场景。你接了个小任务统计班里同学的成绩。第一版很快就写完了students [(A同学, 85), (B同学, 92), (C同学, 58)] total sum(score for _, score in students) print(平均分:, total / len(students))一个列表、一行统计、一个打印干净利落。然后需求来了要标出不及格的同学要按分数排序要按成绩段统计人数还要知道谁比平均分低。你开始一个接一个地往里塞函数def print_failed(students): for name, score in students: if score 60: print(f{name} 不及格) def sort_by_score(students): return sorted(students, keylambda x: x[1], reverseTrue) def grade(score): if score 90: return 优 if score 80: return 良 if score 60: return 及格 return 不及格这段代码现在还能看懂但隐患已经埋下了所有函数都在操作同一个列表谁都能随时改它。某天你决定在列表里加一项数据比如“班级”把原来的二元组变成三元组上面每个函数就都得跟着改漏改一个程序跑出来的结果就是错的。更折磨的是这个列表被十个函数传来传去你想找“到底是谁改了里面的数据”只能靠肉眼逐行读。这就是面向过程脚本的典型困境数据是“光着”的在任何地方都可能被改动功能一多你就像同时维护十根缠在一起的毛线。程序不是一下子烂掉的是每一行新代码都在上面打了个结。2.2 对象帮你做了什么把数据和操作锁进同一个盒子里面向对象的思路完全换了个问法这个系统里最核心的“东西”是什么是学生。那就建一个“学生”类把学生自己的数据姓名、成绩和所有针对这些数据的操作判断是否及格、计算等级都放进同一个定义里class Student: def __init__(self, name: str, score: int) - None: self.name name self.score score def is_pass(self) - bool: return self.score 60 def grade(self) - str: if self.score 90: return 优 if self.score 80: return 良 if self.score 60: return 及格 return 不及格改动体现在哪里数据的存储格式name、score两个字段被固定在这个类的内部。外面想操作一个学生不需要直接碰它的内部而是通过is_pass()、grade()这两个方法。以后如果要在 Student 里加一个“班级”字段受影响的地方被限制在类的内部外面调用is_pass()和grade()的代码一行都不用动。这就是面向对象最朴素的好处有了边界改动才不会到处漏。2.3 你其实每天都在用面向对象内置类型就是对象有一个很多人没意识到的点你早就天天在用面向对象只是没意识到而已。Python 里的一切都是对象。字符串有.strip()、.lower()方法列表有.append()、.sort()方法字典有.keys()、.values()方法。这些“点出来的函数”就是对象的方法text Hello, Python .strip().lower() print(text) # 输出: hello, python你不需要知道字符串内部是怎么存字符的只需要知道strip()能去掉空格、lower()能转小写。这种“别人把内部藏好只给你几个操作入口”的设计就是面向对象里说的封装。学 OOP 不是学一门离你很远的新东西而是把你天天在用的东西从“会调用”升级成“会自己造”。2.4 主流库都在讲 OOP看不懂文档的真正原因打开 pandas 的文档你会看到 DataFrame、Series 这些“类”打开 Django 文档你会看到 Model、View 这些“类”打开机器学习库的文档你会看到 estimator、transformer 这些“对象”概念。如果不懂类、实例、继承、方法这些词读任何主流库的文档都像在看天书。学完面向对象之后你会发现文档里翻来覆去说的就是那几件事这个类有哪些属性有哪些方法我要怎么创建实例怎么调用方法。这套思维一旦打通再接触任何新库学习成本都会直线下降。这也是为什么几乎所有 Python 进阶路线都会把面向对象放在最前面——它是整个 Python 生态的地基。3. 第一个例子跑起来写一个学生成绩管理类3.1 类就是一张图纸先定义一个空壳子第 2 章的 Student 类也许让你觉得“这不就是把函数塞进 class 里吗”。别急跟着我一步一步写你会看到这样做的价值。先打开你的编辑器或者打开 Jupyter、IDLE 都行敲下第一行class Student: pass这里的pass是 Python 的占位符表示“代码块还没写先让语法通过”。运行之后你会得到一个叫 Student 的类型但它目前什么都没有。不过已经可以靠它创建对象了s1 Student() s2 Student() print(s1 is s2) # 输出 False说明是两个完全独立的实例Student()这个写法叫“实例化”——按图纸造出一个具体的学生。类本身是图纸图纸上画着“学生都有哪些特征、哪些动作”实例是根据图纸造出来的每一份真实对象。s1 和 s2 互不干扰。类只定义一次实例可以造无数个。3.2 给类加上“出生初始化”init登场一个真正有用的学生得有自己的姓名和成绩。我们希望在创建实例的时候直接传入这两个值这时就要用到__init__方法。它在对象刚被创建时自动执行专门负责给新对象灌入初始数据class Student: def __init__(self, name: str, score: int) - None: self.name name self.score score这里有三点要讲清楚。第一__init__里的self是 Python 自动传入的它指向“当前正在创建的这个实例”。self.name name的意思是把这个新实例的名字设为传入的 name。第二name: str和score: int是类型注解书上叫 type hints它不会在运行时报错但让读代码的人一眼就知道该传什么类型IDE 还能自动提示。第三- None表示这个方法不返回值。创建对象的方式也随之变了s Student(A同学, 85) print(s.name) # 输出: A同学 print(s.score) # 输出: 85现在 s 这个实例已经带着自己的姓名和成绩了。每个通过 Student 创建的实例都有一份独立的 name 和 score——改 s1 不会碰 s2。这是“实例属性”与后面要说的“类属性”最大的区别也是新手最容易绕晕的地方。3.3 写方法让对象会判断、会说话类里不能只有数据还得有行为。比如判断一个学生是否及格给它写一个方法class Student: def __init__(self, name: str, score: int) - None: self.name name self.score score def is_pass(self) - bool: 成绩大于等于60分视为通过 return self.score 60 def introduce(self) - str: return f我是{self.name}成绩是{self.score}分调用方式非常直白s.is_pass()返回 True 或 Falses.introduce()返回一句话。不需要往方法里传 self——Python 调用时自动把 s 自己传进去了。这跟前面字符串的lower()是同一套逻辑对象本身就是数据方法就是对象能做的动作。特别提醒方法内部想使用实例自己的数据必须通过self.xxx去取。如果直接在方法里写scorePython 会认为那是一个不存在的局部变量当场报错。这本书反复强调这一点因为它是新手最常见的认知断层。3.4 类型注解、docstring 与模块组织让代码像文档一样清楚刚才的代码里出现了- bool和- str这是方法的返回类型注解。加上之后代码的可读性明显提升不用读方法体光看签名就知道is_pass返回布尔值、introduce返回字符串。这本书更推荐把 docstring 也写上一句话说清楚“这个方法负责什么”class Student: def __init__(self, name: str, score: int) - None: 创建一个学生实例。 参数 name: 学生姓名 score: 成绩0~100 self.name name self.score score很多新手觉得写 docstring 是浪费时间但等你三个月后回来看自己写的代码或者别人要接手你的代码时这段文字的价值立刻超过代码本身。这不是仪式感是血泪经验。当类慢慢变多你就要注意组织方式了。一个良好习惯是一个类放一个文件。比如把 Student 存成student.py在另一个main.py里写from student import Student然后再创建实例。这样你的项目不再是“一个大脚本”而是一个结构清晰的包。书里用了一整节讲模块和包的拆法新手阶段记住“按类拆文件”就够用了。4. 新手避坑指南5个最常见的面向对象翻车现场4.1 坑一方法定义忘了写 self现象class Student: def __init__(self, name): self.name name def introduce(): # 忘了写 self return f我是{self.name} # 这里直接报错一调用s.introduce()就会报错TypeError: introduce() takes 0 positional arguments but 1 was given。原因Python 调用实例方法时会自动把实例本身作为第一个参数传入。方法定义里一个参数都没留自然就装不下了。这是零基础学 OOP 的第一大坑几乎每个人都会踩一次。解决实例方法的第一个参数写self就这一个名字这是规范。把定义改成def introduce(self):方法体里用self.name问题立刻消失。4.2 坑二把可变列表放在类属性里现象你想给所有学生共享一个“班级名单”于是在类的内部写了classmates []。然后发现 A 同学往名单里加了一个人B 同学身上的名单也跟着多了这个人。原因类属性是所有实例共享的。当你写self.classmates.append(...)时列表是类的那一份所有实例都在往同一个列表里塞东西——你以为是每个人的其实是大家的。解决不要在类定义里放可变对象。把可变数据放在__init__里用self.classmates []新建一份实例专属的列表。类属性只放那些“所有学生都一样”的常量比如max_score 100。4.3 坑三为了复用代码强行用继承现象你的项目里“狗”和“车”都要“启动”功能于是写了一个 Base 类让两个类都继承它。后来发现狗和车的启动逻辑根本不同到处都在重写父类方法最后只能把 Base 改得面目全非。原因继承表达的是“is-a”关系比如“学生”是一种“人”。为了省几行代码硬套继承等于把两个不相干的东西绑在一起后面的维护全是债。解决先问自己A 是不是 B 的一种如果不是优先用组合——一个类里持有另一个类的实例比如 Car 类里有一个 Engine 类的属性。这本书第 1 章专门讲了这个取舍建议多看两遍。4.4 坑四异常捕获后直接吞掉现象代码里写着try: ... except: pass程序跑起来不报错但结果却是错的查了半天才发现某个异常被静默吃掉了。原因except Exception: pass让你永远看不到发生了什么。表面安静实际上把问题藏进了黑匣子出了故障完全无从下手。解决至少用 logging 记录错误或者让异常自然地抛出让调用者知道出了问题import logging try: score int(input(请输入成绩: )) except ValueError: logging.error(用户输入的不是数字) raise4.5 坑五可变默认参数现象你定义了一个方法def add_tag(self, tags[]):每次调用不传 tags结果上一次调用留下的内容还在列表里越积越多。原因默认参数是在函数定义时创建一次的默认的[]是一个共享对象所有实例都往同一个列表里追加。解决默认值写None函数体内再创建新列表def add_tag(self, tagsNone): if tags is None: tags [] tags.append(新手) return tags这五条是新手阶段最常遇到的坎每一条我都亲眼见过身边的朋友踩过。提前知道它们能省下不少排查时间。5. 再往前走一步异常、数据类与测试的实战用法5.1 异常处理让程序出错时也“有尊严”程序一定会出错用户输入了非法值、文件不存在、网络断了。面向对象的程序不追求“永远不出错”而是追求“出错时能给个明白话”。Python 的try/except用起来非常自然try: user_input input(请输入成绩) score int(user_input) if score 0 or score 100: raise ValueError(成绩必须在0-100之间) except ValueError as e: print(输入不合法, e)这里的raise ValueError(...)是自己主动抛出异常。更进一步你可以定义自己的异常类让调用者能精确捕获class ScoreError(Exception): 成绩不合法时抛出的异常 def set_score(student, score): if score 0 or score 100: raise ScoreError(f成绩必须在0-100之间收到{score}) student.score score自定义异常让错误信息带着业务含义而不是笼统的ValueError。在长期维护的项目里这种明确性非常重要排查问题时能省下大量时间。5.2 数据类一行解决掉大量重复代码写一个类最烦的就是__init__、__repr__、__eq__这些模板代码。Python 3.7 加入的 dataclass数据类用装饰器自动生成它们from dataclasses import dataclass dataclass class Student: name: str score: int graduated: bool False就这几行你自动获得了__init__按 name、score、graduated 顺序接收参数、__repr__打印出一行易读的文字和__eq__两个学生姓名成绩相同即为相等。不用再手写一堆模板代码字段一目了然。书里第 7 章详细对比了普通类、namedtuple 和 dataclass 的适用场景新手阶段记住“需要可变数据结构的首选 dataclass”就够了。5.3 给代码上保险用 pytest 写最小测试程序越写越大手动测试已经不够。这时你需要一个极简的工具pytest。安装之后pip install pytest写一个测试文件from student import Student def test_is_pass(): s Student(A同学, 85) assert s.is_pass() is True def test_is_pass_low_score(): s Student(B同学, 59) assert s.is_pass() is False在终端里运行pytest它会自动发现test_开头的函数并执行。输出绿点就是全过红色就是某个断言失败还会告诉你具体哪一行出了问题。给每个方法写两三个这样的测试功能回归的恐惧感会大大降低。5.4 一个设计模式速览抽象基类书里后面几章讲设计模式先带你瞥一眼最有用的一个抽象基类ABC。它适合做“接口约定”——规定所有子类必须实现哪些方法from abc import ABC, abstractmethod class Distance(ABC): abstractmethod def compute(self, p1, p2) - float: 计算两点的距离 class EuclideanDistance(Distance): def compute(self, p1, p2) - float: return ((p1[0] - p2[0]) ** 2 (p1[1] - p2[1]) ** 2) ** 0.5好处很明显任何继承 Distance 的类如果忘了实现compute一运行就会报错。这等于把“必须实现某个方法”的约定写进了代码本身而不是写在开发者的备忘录里。理解抽象基类读很多框架源码时会豁然开朗。6. 下一步学什么从这本书走向独立写项目6.1 给零基础的你一条不太费力的路《Python面向对象编程第四版》一共 14 章新手不需要从头到尾一口气啃完。我建议按这条路径走阶段建议目标第1阶段第1~3章面向对象设计基础、Python类语法、继承能把普通脚本改写成类第2阶段第4~5章异常、何时用OOP写出健壮的输入校验第3阶段第7章数据类、字典、列表、集合、队列选对数据结构第4阶段第13章pytest测试养成测试习惯第5阶段第8~9章字符串、序列化、文件路径处理真实数据文件第 6、10、11、12 章可以先翻一翻不必深究。等你写的程序开始超过两千行再回来精读设计模式和并发部分会有完全不同的收获。6.2 一个亲测有效的技巧跟紧书里的 k-NN 案例这本书最妙的设计是从第 1 章开始埋了一个贯穿全书的案例一个 k 近邻k-NN机器学习小项目。每一章都会给这个案例增加一部分功能比如第 1 章做整体设计、第 3 章实现距离计算、第 7 章处理数据、第 13 章补测试。我第一次学的时候被这个案例从“设计思路”到“可跑代码”的完整推进震撼到了。强烈建议不要把各章当独立文章看而是跟着案例走一遍——每个阶段都实际运行书里的代码。你会发现前面学的概念在后一章都变成了某个真实功能的构件这种“概念落地”的循环比单独记知识点有效十倍。6.3 一点过来人的教训我刚开始学 OOP 时总觉得自己写的脚本挺顺的不屑于用类。结果半年后回来维护自己的旧代码错改了三处才跑起来那叫一个后悔。从那以后我每次写新项目都强制自己先走一遍“这个系统里有哪些对象、各自管什么数据”的思考再动手敲 class。这套流程已经变成了肌肉记忆省下了大量返工时间。如果你拿到了这本书的电子文档我建议先从第 1 章的案例看起不要从目录开始硬啃。希望这本书的方法论也能帮你少走这样一圈弯路希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

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

立即咨询 →