ο缶幊蹋簭哪_本到工程的關鍵一躍)
簡介本資源為2021年出版的《Python Object-Oriented Programming》第四版英文原版PDF由Steven F. Lott與Dusty Phillips合著面向希望系統(tǒng)掌握Python面向?qū)ο缶幊痰拈_發(fā)者。全書依照“類與對象基礎—封裝與繼承—多態(tài)與特殊方法—設計模式—測試與維護”的脈絡展開既覆蓋OOP核心概念也給出構(gòu)建健壯應用和可維護Python庫的實際方法適合從入門到進階的Python工程師及教學參考。資源共1個文件為PDF格式壓縮包大小約6.51MB便于在線閱讀或?qū)牍P記工具。目前已有43人學習下載。相比零散教程這份完整英文原版能幫助讀者建立面向?qū)ο笤O計思維同時理解異常處理、單元測試、版本控制與依賴管理等工程實踐細節(jié)適合作為長期案頭參考書。1. 先搞懂一個概念面向?qū)ο蟛皇切W是組織代碼的思維剛學Python的朋友都有過這種遭遇寫幾十行小腳本順順利利程序長到上千行就一團亂麻——改一個小功能崩了三個地方。這時候需要的不是更多技巧而是更好的組織代碼的方式。面向?qū)ο缶幊叹褪沁@種更好的方式它不玄。核心一句話把數(shù)據(jù)和操作數(shù)據(jù)的函數(shù)打包成獨立的小單元叫作對象。每個對象各管一攤協(xié)作時通過明確的接口說話。好比餐廳廚師炒菜、服務員傳菜、收銀員結(jié)賬各司其職?!禤ython面向?qū)ο缶幊痰谒陌妗氛侵v這件事從類與對象講到繼承、異常、數(shù)據(jù)類、設計模式再到測試與并發(fā)適合一寫復雜程序就發(fā)怵的初學者。2. 為什么必學面向?qū)ο髲哪_本到工程的關鍵一躍2.1 先看一個失控的例子加功能的腳本如何變成亂麻先復制一個很常見的場景。你接了個小任務統(tǒng)計班里同學的成績。第一版很快就寫完了students [(A同學, 85), (B同學, 92), (C同學, 58)] total sum(score for _, score in students) print(平均分:, total / len(students))一個列表、一行統(tǒng)計、一個打印干凈利落。然后需求來了要標出不及格的同學要按分數(shù)排序要按成績段統(tǒng)計人數(shù)還要知道誰比平均分低。你開始一個接一個地往里塞函數(shù)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 優(yōu) if score 80: return 良 if score 60: return 及格 return 不及格這段代碼現(xiàn)在還能看懂但隱患已經(jīng)埋下了所有函數(shù)都在操作同一個列表誰都能隨時改它。某天你決定在列表里加一項數(shù)據(jù)比如“班級”把原來的二元組變成三元組上面每個函數(shù)就都得跟著改漏改一個程序跑出來的結(jié)果就是錯的。更折磨的是這個列表被十個函數(shù)傳來傳去你想找“到底是誰改了里面的數(shù)據(jù)”只能靠肉眼逐行讀。這就是面向過程腳本的典型困境數(shù)據(jù)是“光著”的在任何地方都可能被改動功能一多你就像同時維護十根纏在一起的毛線。程序不是一下子爛掉的是每一行新代碼都在上面打了個結(jié)。2.2 對象幫你做了什么把數(shù)據(jù)和操作鎖進同一個盒子里面向?qū)ο蟮乃悸吠耆珦Q了個問法這個系統(tǒng)里最核心的“東西”是什么是學生。那就建一個“學生”類把學生自己的數(shù)據(jù)姓名、成績和所有針對這些數(shù)據(jù)的操作判斷是否及格、計算等級都放進同一個定義里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 優(yōu) if self.score 80: return 良 if self.score 60: return 及格 return 不及格改動體現(xiàn)在哪里數(shù)據(jù)的存儲格式name、score兩個字段被固定在這個類的內(nèi)部。外面想操作一個學生不需要直接碰它的內(nèi)部而是通過is_pass()、grade()這兩個方法。以后如果要在 Student 里加一個“班級”字段受影響的地方被限制在類的內(nèi)部外面調(diào)用is_pass()和grade()的代碼一行都不用動。這就是面向?qū)ο笞顦闼氐暮锰幱辛诉吔绺膭硬挪粫教幝?.3 你其實每天都在用面向?qū)ο髢?nèi)置類型就是對象有一個很多人沒意識到的點你早就天天在用面向?qū)ο笾皇菦]意識到而已。Python 里的一切都是對象。字符串有.strip()、.lower()方法列表有.append()、.sort()方法字典有.keys()、.values()方法。這些“點出來的函數(shù)”就是對象的方法text Hello, Python .strip().lower() print(text) # 輸出: hello, python你不需要知道字符串內(nèi)部是怎么存字符的只需要知道strip()能去掉空格、lower()能轉(zhuǎn)小寫。這種“別人把內(nèi)部藏好只給你幾個操作入口”的設計就是面向?qū)ο罄镎f的封裝。學 OOP 不是學一門離你很遠的新東西而是把你天天在用的東西從“會調(diào)用”升級成“會自己造”。2.4 主流庫都在講 OOP看不懂文檔的真正原因打開 pandas 的文檔你會看到 DataFrame、Series 這些“類”打開 Django 文檔你會看到 Model、View 這些“類”打開機器學習庫的文檔你會看到 estimator、transformer 這些“對象”概念。如果不懂類、實例、繼承、方法這些詞讀任何主流庫的文檔都像在看天書。學完面向?qū)ο笾竽銜l(fā)現(xiàn)文檔里翻來覆去說的就是那幾件事這個類有哪些屬性有哪些方法我要怎么創(chuàng)建實例怎么調(diào)用方法。這套思維一旦打通再接觸任何新庫學習成本都會直線下降。這也是為什么幾乎所有 Python 進階路線都會把面向?qū)ο蠓旁谧钋懊妗钦麄€ Python 生態(tài)的地基。3. 第一個例子跑起來寫一個學生成績管理類3.1 類就是一張圖紙先定義一個空殼子第 2 章的 Student 類也許讓你覺得“這不就是把函數(shù)塞進 class 里嗎”。別急跟著我一步一步寫你會看到這樣做的價值。先打開你的編輯器或者打開 Jupyter、IDLE 都行敲下第一行class Student: pass這里的pass是 Python 的占位符表示“代碼塊還沒寫先讓語法通過”。運行之后你會得到一個叫 Student 的類型但它目前什么都沒有。不過已經(jīng)可以靠它創(chuàng)建對象了s1 Student() s2 Student() print(s1 is s2) # 輸出 False說明是兩個完全獨立的實例Student()這個寫法叫“實例化”——按圖紙造出一個具體的學生。類本身是圖紙圖紙上畫著“學生都有哪些特征、哪些動作”實例是根據(jù)圖紙造出來的每一份真實對象。s1 和 s2 互不干擾。類只定義一次實例可以造無數(shù)個。3.2 給類加上“出生初始化”init登場一個真正有用的學生得有自己的姓名和成績。我們希望在創(chuàng)建實例的時候直接傳入這兩個值這時就要用到__init__方法。它在對象剛被創(chuàng)建時自動執(zhí)行專門負責給新對象灌入初始數(shù)據(jù)class Student: def __init__(self, name: str, score: int) - None: self.name name self.score score這里有三點要講清楚。第一__init__里的self是 Python 自動傳入的它指向“當前正在創(chuàng)建的這個實例”。self.name name的意思是把這個新實例的名字設為傳入的 name。第二name: str和score: int是類型注解書上叫 type hints它不會在運行時報錯但讓讀代碼的人一眼就知道該傳什么類型IDE 還能自動提示。第三- None表示這個方法不返回值。創(chuàng)建對象的方式也隨之變了s Student(A同學, 85) print(s.name) # 輸出: A同學 print(s.score) # 輸出: 85現(xiàn)在 s 這個實例已經(jīng)帶著自己的姓名和成績了。每個通過 Student 創(chuàng)建的實例都有一份獨立的 name 和 score——改 s1 不會碰 s2。這是“實例屬性”與后面要說的“類屬性”最大的區(qū)別也是新手最容易繞暈的地方。3.3 寫方法讓對象會判斷、會說話類里不能只有數(shù)據(jù)還得有行為。比如判斷一個學生是否及格給它寫一個方法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}分調(diào)用方式非常直白s.is_pass()返回 True 或 Falses.introduce()返回一句話。不需要往方法里傳 self——Python 調(diào)用時自動把 s 自己傳進去了。這跟前面字符串的lower()是同一套邏輯對象本身就是數(shù)據(jù)方法就是對象能做的動作。特別提醒方法內(nèi)部想使用實例自己的數(shù)據(jù)必須通過self.xxx去取。如果直接在方法里寫scorePython 會認為那是一個不存在的局部變量當場報錯。這本書反復強調(diào)這一點因為它是新手最常見的認知斷層。3.4 類型注解、docstring 與模塊組織讓代碼像文檔一樣清楚剛才的代碼里出現(xiàn)了- bool和- str這是方法的返回類型注解。加上之后代碼的可讀性明顯提升不用讀方法體光看簽名就知道is_pass返回布爾值、introduce返回字符串。這本書更推薦把 docstring 也寫上一句話說清楚“這個方法負責什么”class Student: def __init__(self, name: str, score: int) - None: 創(chuàng)建一個學生實例。 參數(shù) name: 學生姓名 score: 成績0~100 self.name name self.score score很多新手覺得寫 docstring 是浪費時間但等你三個月后回來看自己寫的代碼或者別人要接手你的代碼時這段文字的價值立刻超過代碼本身。這不是儀式感是血淚經(jīng)驗。當類慢慢變多你就要注意組織方式了。一個良好習慣是一個類放一個文件。比如把 Student 存成student.py在另一個main.py里寫from student import Student然后再創(chuàng)建實例。這樣你的項目不再是“一個大腳本”而是一個結(jié)構(gòu)清晰的包。書里用了一整節(jié)講模塊和包的拆法新手階段記住“按類拆文件”就夠用了。4. 新手避坑指南5個最常見的面向?qū)ο蠓嚞F(xiàn)場4.1 坑一方法定義忘了寫 self現(xiàn)象class Student: def __init__(self, name): self.name name def introduce(): # 忘了寫 self return f我是{self.name} # 這里直接報錯一調(diào)用s.introduce()就會報錯TypeError: introduce() takes 0 positional arguments but 1 was given。原因Python 調(diào)用實例方法時會自動把實例本身作為第一個參數(shù)傳入。方法定義里一個參數(shù)都沒留自然就裝不下了。這是零基礎學 OOP 的第一大坑幾乎每個人都會踩一次。解決實例方法的第一個參數(shù)寫self就這一個名字這是規(guī)范。把定義改成def introduce(self):方法體里用self.name問題立刻消失。4.2 坑二把可變列表放在類屬性里現(xiàn)象你想給所有學生共享一個“班級名單”于是在類的內(nèi)部寫了classmates []。然后發(fā)現(xiàn) A 同學往名單里加了一個人B 同學身上的名單也跟著多了這個人。原因類屬性是所有實例共享的。當你寫self.classmates.append(...)時列表是類的那一份所有實例都在往同一個列表里塞東西——你以為是每個人的其實是大家的。解決不要在類定義里放可變對象。把可變數(shù)據(jù)放在__init__里用self.classmates []新建一份實例專屬的列表。類屬性只放那些“所有學生都一樣”的常量比如max_score 100。4.3 坑三為了復用代碼強行用繼承現(xiàn)象你的項目里“狗”和“車”都要“啟動”功能于是寫了一個 Base 類讓兩個類都繼承它。后來發(fā)現(xiàn)狗和車的啟動邏輯根本不同到處都在重寫父類方法最后只能把 Base 改得面目全非。原因繼承表達的是“is-a”關系比如“學生”是一種“人”。為了省幾行代碼硬套繼承等于把兩個不相干的東西綁在一起后面的維護全是債。解決先問自己A 是不是 B 的一種如果不是優(yōu)先用組合——一個類里持有另一個類的實例比如 Car 類里有一個 Engine 類的屬性。這本書第 1 章專門講了這個取舍建議多看兩遍。4.4 坑四異常捕獲后直接吞掉現(xiàn)象代碼里寫著try: ... except: pass程序跑起來不報錯但結(jié)果卻是錯的查了半天才發(fā)現(xiàn)某個異常被靜默吃掉了。原因except Exception: pass讓你永遠看不到發(fā)生了什么。表面安靜實際上把問題藏進了黑匣子出了故障完全無從下手。解決至少用 logging 記錄錯誤或者讓異常自然地拋出讓調(diào)用者知道出了問題import logging try: score int(input(請輸入成績: )) except ValueError: logging.error(用戶輸入的不是數(shù)字) raise4.5 坑五可變默認參數(shù)現(xiàn)象你定義了一個方法def add_tag(self, tags[]):每次調(diào)用不傳 tags結(jié)果上一次調(diào)用留下的內(nèi)容還在列表里越積越多。原因默認參數(shù)是在函數(shù)定義時創(chuàng)建一次的默認的[]是一個共享對象所有實例都往同一個列表里追加。解決默認值寫None函數(shù)體內(nèi)再創(chuàng)建新列表def add_tag(self, tagsNone): if tags is None: tags [] tags.append(新手) return tags這五條是新手階段最常遇到的坎每一條我都親眼見過身邊的朋友踩過。提前知道它們能省下不少排查時間。5. 再往前走一步異常、數(shù)據(jù)類與測試的實戰(zhàn)用法5.1 異常處理讓程序出錯時也“有尊嚴”程序一定會出錯用戶輸入了非法值、文件不存在、網(wǎng)絡斷了。面向?qū)ο蟮某绦虿蛔非蟆坝肋h不出錯”而是追求“出錯時能給個明白話”。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(...)是自己主動拋出異常。更進一步你可以定義自己的異常類讓調(diào)用者能精確捕獲class ScoreError(Exception): 成績不合法時拋出的異常 def set_score(student, score): if score 0 or score 100: raise ScoreError(f成績必須在0-100之間收到{score}) student.score score自定義異常讓錯誤信息帶著業(yè)務含義而不是籠統(tǒng)的ValueError。在長期維護的項目里這種明確性非常重要排查問題時能省下大量時間。5.2 數(shù)據(jù)類一行解決掉大量重復代碼寫一個類最煩的就是__init__、__repr__、__eq__這些模板代碼。Python 3.7 加入的 dataclass數(shù)據(jù)類用裝飾器自動生成它們from dataclasses import dataclass dataclass class Student: name: str score: int graduated: bool False就這幾行你自動獲得了__init__按 name、score、graduated 順序接收參數(shù)、__repr__打印出一行易讀的文字和__eq__兩個學生姓名成績相同即為相等。不用再手寫一堆模板代碼字段一目了然。書里第 7 章詳細對比了普通類、namedtuple 和 dataclass 的適用場景新手階段記住“需要可變數(shù)據(jù)結(jié)構(gòu)的首選 dataclass”就夠了。5.3 給代碼上保險用 pytest 寫最小測試程序越寫越大手動測試已經(jīng)不夠。這時你需要一個極簡的工具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它會自動發(fā)現(xiàn)test_開頭的函數(shù)并執(zhí)行。輸出綠點就是全過紅色就是某個斷言失敗還會告訴你具體哪一行出了問題。給每個方法寫兩三個這樣的測試功能回歸的恐懼感會大大降低。5.4 一個設計模式速覽抽象基類書里后面幾章講設計模式先帶你瞥一眼最有用的一個抽象基類ABC。它適合做“接口約定”——規(guī)定所有子類必須實現(xiàn)哪些方法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 的類如果忘了實現(xiàn)compute一運行就會報錯。這等于把“必須實現(xiàn)某個方法”的約定寫進了代碼本身而不是寫在開發(fā)者的備忘錄里。理解抽象基類讀很多框架源碼時會豁然開朗。6. 下一步學什么從這本書走向獨立寫項目6.1 給零基礎的你一條不太費力的路《Python面向?qū)ο缶幊痰谒陌妗芬还?14 章新手不需要從頭到尾一口氣啃完。我建議按這條路徑走階段建議目標第1階段第1~3章面向?qū)ο笤O計基礎、Python類語法、繼承能把普通腳本改寫成類第2階段第4~5章異常、何時用OOP寫出健壯的輸入校驗第3階段第7章數(shù)據(jù)類、字典、列表、集合、隊列選對數(shù)據(jù)結(jié)構(gòu)第4階段第13章pytest測試養(yǎng)成測試習慣第5階段第8~9章字符串、序列化、文件路徑處理真實數(shù)據(jù)文件第 6、10、11、12 章可以先翻一翻不必深究。等你寫的程序開始超過兩千行再回來精讀設計模式和并發(fā)部分會有完全不同的收獲。6.2 一個親測有效的技巧跟緊書里的 k-NN 案例這本書最妙的設計是從第 1 章開始埋了一個貫穿全書的案例一個 k 近鄰k-NN機器學習小項目。每一章都會給這個案例增加一部分功能比如第 1 章做整體設計、第 3 章實現(xiàn)距離計算、第 7 章處理數(shù)據(jù)、第 13 章補測試。我第一次學的時候被這個案例從“設計思路”到“可跑代碼”的完整推進震撼到了。強烈建議不要把各章當獨立文章看而是跟著案例走一遍——每個階段都實際運行書里的代碼。你會發(fā)現(xiàn)前面學的概念在后一章都變成了某個真實功能的構(gòu)件這種“概念落地”的循環(huán)比單獨記知識點有效十倍。6.3 一點過來人的教訓我剛開始學 OOP 時總覺得自己寫的腳本挺順的不屑于用類。結(jié)果半年后回來維護自己的舊代碼錯改了三處才跑起來那叫一個后悔。從那以后我每次寫新項目都強制自己先走一遍“這個系統(tǒng)里有哪些對象、各自管什么數(shù)據(jù)”的思考再動手敲 class。這套流程已經(jīng)變成了肌肉記憶省下了大量返工時間。如果你拿到了這本書的電子文檔我建議先從第 1 章的案例看起不要從目錄開始硬啃。希望這本書的方法論也能幫你少走這樣一圈彎路希望幫到你。本文還有配套的精品資源點擊獲取