3 個可以使你的 Python 代碼更優(yōu)雅、可讀坚踩、直觀和易于維護的工具

魔術(shù)方法

魔術(shù)方法可以看作是 Python 的管道荡灾。它們被稱為“底層”方法,用于某些內(nèi)置的方法瞬铸、符號和操作批幌。你可能熟悉的常見魔術(shù)方法是?__init__(),當我們想要初始化一個類的新實例時嗓节,它會被調(diào)用荧缘。

你可能已經(jīng)看過其他常見的魔術(shù)方法,如?__str__?和?__repr__赦政。Python 中有一整套魔術(shù)方法胜宇,通過實現(xiàn)其中的一些方法耀怜,我們可以修改一個對象的行為恢着,甚至使其行為類似于內(nèi)置數(shù)據(jù)類型,例如數(shù)字财破、列表或字典掰派。

讓我們創(chuàng)建一個?Money?類來示例:

class? Money:? ? currency_rates? =? {? ? ? ? '$':? 1,? ? ? ? '€':? 0.88,? ? }? ? def __init__(self,? symbol,? amount):? ? ? ? self.symbol? =? symbol? ? ? ? self.amount? =? amount? ? def __repr__(self):? ? ? ? return? '%s%.2f'? %? (self.symbol,? self.amount)? ? def convert(self,? other):? ? ? ? """ Convert other amount to our currency """? ? ? ? new_amount? =? (? ? ? ? ? ? other.amount? /? self.currency_rates[other.symbol]? ? ? ? ? ? *? self.currency_rates[self.symbol])? ? ? ? return? Money(self.symbol,? new_amount)

該類定義為給定的貨幣符號和匯率定義了一個貨幣匯率,指定了一個初始化器(也稱為構(gòu)造函數(shù))左痢,并實現(xiàn)?__repr__靡羡,因此當我們打印這個類時系洛,我們會看到一個友好的表示,例如?$2.00略步,這是一個帶有貨幣符號和金額的?Money('$', 2.00)?實例描扯。最重要的是,它定義了一種方法趟薄,允許你使用不同的匯率在不同的貨幣之間進行轉(zhuǎn)換绽诚。

打開 Python shell,假設(shè)我們已經(jīng)定義了使用兩種不同貨幣的食品的成本杭煎,如下所示:

>>>? soda_cost? =? Money('$',? 5.25)>>>? soda_cost? ? $5.25>>>? pizza_cost? =? Money('€',? 7.99)>>>? pizza_cost? ? €7.99

我們可以使用魔術(shù)方法使得這個類的實例之間可以相互交互恩够。假設(shè)我們希望能夠?qū)⑦@個類的兩個實例一起加在一起,即使它們是不同的貨幣羡铲。為了實現(xiàn)這一點蜂桶,我們可以在?Money?類上實現(xiàn)?__add__?這個魔術(shù)方法:

classMoney:# ... previously defined methods ...def__add__(self,other):""" Add 2 Money instances using '+' """new_amount=self.amount+self.convert(other).amountreturnMoney(self.symbol,new_amount)

現(xiàn)在我們可以以非常直觀的方式使用這個類:

>>>? soda_cost? =? Money('$',? 5.25)>>>? pizza_cost? =? Money('€',? 7.99)>>>? soda_cost? +? pizza_cost? ? $14.33>>>? pizza_cost? +? soda_cost? ? €12.61

當我們將兩個實例加在一起時,我們得到以第一個定義的貨幣符號所表示的結(jié)果也切。所有的轉(zhuǎn)換都是在底層無縫完成的扑媚。如果我們想的話,我們也可以為減法實現(xiàn)?__sub__贾费,為乘法實現(xiàn)?__mul__等等钦购。閱讀模擬數(shù)字類型魔術(shù)方法指南來獲得更多信息。

我們學(xué)習(xí)到?__add__?映射到內(nèi)置運算符?+褂萧。其他魔術(shù)方法可以映射到像?[]?這樣的符號押桃。例如,在字典中通過索引或鍵來獲得一項导犹,其實是使用了?__getitem__?方法:

>>>? d? =? {'one':? 1,? 'two':? 2}>>>? d['two']2>>>? d.__getitem__('two')2

一些魔術(shù)方法甚至映射到內(nèi)置函數(shù)唱凯,例如?__len__()?映射到?len()。

class? Alphabet:? ? letters? =? 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'? ? def __len__(self):? ? ? ? return? len(self.letters)>>>? my_alphabet? =? Alphabet()>>>? len(my_alphabet)? ? 26

自定義迭代器

對于新的和經(jīng)驗豐富的 Python 開發(fā)者來說谎痢,自定義迭代器是一個非常強大的但令人迷惑的主題磕昼。

許多內(nèi)置類型,例如列表节猿、集合和字典票从,已經(jīng)實現(xiàn)了允許它們在底層迭代的協(xié)議。這使我們可以輕松地遍歷它們滨嘱。

>>>forfoodin['Pizza','Fries']:print(food+'. Yum!')Pizza.Yum!Fries.Yum!

我們?nèi)绾蔚覀冏约旱淖远x類峰鄙?首先,讓我們來澄清一些術(shù)語太雨。

要成為一個可迭代對象吟榴,一個類需要實現(xiàn)?__iter__()

__iter__()?方法需要返回一個迭代器

要成為一個迭代器,一個類需要實現(xiàn)?__next__()(或在 Python 2中是?next())囊扳,當沒有更多的項要迭代時吩翻,必須拋出一個?StopIteration?異常兜看。

呼!這聽起來很復(fù)雜狭瞎,但是一旦你記住了這些基本概念细移,你就可以在任何時候進行迭代。

我們什么時候想使用自定義迭代器熊锭?讓我們想象一個場景葫哗,我們有一個?Server?實例在不同的端口上運行不同的服務(wù),如?http?和?ssh球涛。其中一些服務(wù)處于?active?狀態(tài)劣针,而其他服務(wù)則處于?inactive?狀態(tài)。

classServer:services=[{'active':False,'protocol':'ftp','port':21},{'active':True,'protocol':'ssh','port':22},{'active':True,'protocol':'http','port':80},]

當我們遍歷?Server?實例時亿扁,我們只想遍歷那些處于?active?的服務(wù)捺典。讓我們創(chuàng)建一個?IterableServer?類:

class? IterableServer:? ? ? def __init__(self):? ? ? ? ? ? self.current_pos? =? 0? ? ? def __next__(self):? ? ? ? ? ? pass? # TODO: 實現(xiàn)并記得拋出 StopIteration

首先,我們將當前位置初始化為?0从祝。然后襟己,我們定義一個?__next__()?方法來返回下一項。我們還將確保在沒有更多項返回時拋出?StopIteration牍陌。到目前為止都很好擎浴!現(xiàn)在,讓我們實現(xiàn)這個?__next__()?方法毒涧。

class? IterableServer:? ? def? __init__(self):? ? ? ? self.current_pos? =? 0.? # 我們初始化當前位置為 0? ? def? __iter__(self):? # 我們可以在這里返回 self贮预,因為實現(xiàn)了 __next__? ? ? ? return? self? ? def? __next__(self):? ? ? ? while? self.current_pos? <? len(self.services):? ? ? ? ? ? service? =? self.services[self.current_pos]? ? ? ? ? ? self.current_pos? +=? 1? ? ? ? ? ? if? service['active']:? ? ? ? ? ? ? ? return? service['protocol'],? service['port']? ? ? ? raise? StopIteration? ? next? =? __next__? # 可選的 Python2 兼容性

我們對列表中的服務(wù)進行遍歷,而當前的位置小于服務(wù)的個數(shù)契讲,但只有在服務(wù)處于活動狀態(tài)時才返回仿吞。一旦我們遍歷完服務(wù),就會拋出一個?StopIteration?異常捡偏。

因為我們實現(xiàn)了?__next__()?方法唤冈,當它耗盡時,它會拋出?StopIteration银伟。我們可以從?__iter__()?返回?self你虹,因為?IterableServer?類遵循?iterable?協(xié)議。

現(xiàn)在我們可以遍歷一個?IterableServer?實例彤避,這將允許我們查看每個處于活動的服務(wù)傅物,如下所示:

>>>forprotocol,portinIterableServer():print('service %s is running on port %d'%(protocol,port))servicesshisrunningonport22servicehttpisrunningonport21

太棒了,但我們可以做得更好忠藤!在這樣類似的實例中挟伙,我們的迭代器不需要維護大量的狀態(tài)楼雹,我們可以簡化代碼并使用?generator(生成器)?來代替模孩。

classServer:services=[{'active':False,'protocol':'ftp','port':21},{'active':True,'protocol':'ssh','port':22},{'active':True,'protocol':'http','port':21},]def__iter__(self):forserviceinself.services:ifservice['active']:yieldservice['protocol'],service['port']

yield?關(guān)鍵字到底是什么尖阔?在定義生成器函數(shù)時使用 yield。這有點像?return榨咐,雖然?return在返回值后退出函數(shù)介却,但?yield?會暫停執(zhí)行直到下次調(diào)用它。這允許你的生成器的功能在它恢復(fù)之前保持狀態(tài)块茁。查看?yield 的文檔以了解更多信息齿坷。使用生成器,我們不必通過記住我們的位置來手動維護狀態(tài)数焊。生成器只知道兩件事:它現(xiàn)在需要做什么以及計算下一個項目需要做什么永淌。一旦我們到達執(zhí)行點,即?yield?不再被調(diào)用佩耳,我們就知道停止迭代遂蛀。

這是因為一些內(nèi)置的 Python 魔法。在?Python 關(guān)于 __iter__() 的文檔中我們可以看到干厚,如果?__iter__()?是作為一個生成器實現(xiàn)的李滴,它將自動返回一個迭代器對象,該對象提供?__iter__()和?__next__()?方法蛮瞄。閱讀這篇很棒的文章所坯,深入了解迭代器,可迭代對象和生成器挂捅。

方法魔法

由于其獨特的方面芹助,Python 提供了一些有趣的方法魔法作為語言的一部分。

其中一個例子是別名功能闲先。因為函數(shù)只是對象周瞎,所以我們可以將它們賦值給多個變量。例如:

>>>? def foo():? ? ? ? ? return? 'foo'>>>? foo()'foo'>>>? bar? =? foo>>>? bar()'foo'

我們稍后會看到它的作用饵蒂。

Python 提供了一個方便的內(nèi)置函數(shù)href="https://docs.python.org/3/library/functions.html#getattr">稱為 getattr()声诸,它接受?object, name, default?參數(shù)并在?object?上返回屬性?name。這種編程方式允許我們訪問實例變量和方法退盯。例如:

>>>? class? Dog:? ? ? ? sound? =? 'Bark'? ? ? ? def? speak(self):? ? ? ? ? ? print(self.sound? +? '!',? self.sound? +? '!')>>>? fido? =? Dog()>>>? fido.sound'Bark'>>>? getattr(fido,? 'sound')'Bark'>>>? fido.speak>>>>? getattr(fido,? 'speak')>>>>? fido.speak()Bark!? Bark!>>>? speak_method? =? getattr(fido,? 'speak')>>>? speak_method()Bark!? Bark!

這是一個很酷的技巧彼乌,但是我們?nèi)绾卧趯嶋H中使用?getattr?呢?讓我們看一個例子渊迁,我們編寫一個小型命令行工具來動態(tài)處理命令慰照。

classOperations:defsay_hi(self,name):print('Hello,',name)defsay_bye(self,name):print('Goodbye,',name)defdefault(self,arg):print('This operation is not supported.')if__name__=='__main__':operations=Operations()# 假設(shè)我們做了錯誤處理command,argument=input('> ').split()func_to_call=getattr(operations,command,operations.default)func_to_call(argument)

腳本的輸出是:

$pythongetattr.py>say_hiNinaHello,Nina>blahblahThisoperationisnotsupported.

接下來,我們來看看?partial琉朽。例如毒租,functool.partial(func, *args, **kwargs)?允許你返回一個新的?partial 對象,它的行為類似?func箱叁,參數(shù)是?args?和?kwargs墅垮。如果傳入更多的?args惕医,它們會被附加到?args。如果傳入更多的?kwargs算色,它們會擴展并覆蓋?kwargs抬伺。讓我們通過一個簡短的例子來看看:

>>>? from? functools? import? partial>>>? basetwo? =? partial(int,? base=2)>>>? basetwo>>>? basetwo('10010')18# 這等同于>>>? int('10010',? base=2)

讓我們看看在我喜歡的一個href="https://github.com/mozilla/agithub">名為 agithub 的庫中的一些示例代碼中,這個方法魔術(shù)是如何結(jié)合在一起的灾梦,這是一個(名字起得很 low 的) REST API 客戶端峡钓,它具有透明的語法,允許你以最小的配置快速構(gòu)建任何 REST API 原型(不僅僅是 GitHub)若河。我發(fā)現(xiàn)這個項目很有趣能岩,因為它非常強大,但只有大約 400 行 Python 代碼萧福。你可以在大約 30 行配置代碼中添加對任何 REST API 的支持捧灰。agithub?知道協(xié)議所需的一切(REST、HTTP统锤、TCP)毛俏,但它不考慮上游 API。讓我們深入到它的實現(xiàn)中饲窿。

以下是我們?nèi)绾螢?GitHub API 和任何其他相關(guān)連接屬性定義端點 URL 的簡化版本煌寇。在這里查看完整代碼

class? GitHub(API):? ? ? def __init__(self,? token=None,? *args,? **kwargs):? ? ? ? ? ? props? =? ConnectionProperties(api_url? =? kwargs.pop('api_url',? 'api.github.com'))? ? ? ? ? ? self.setClient(Client(*args,? **kwargs))? ? ? ? ? ? self.setConnectionProperties(props)

然后逾雄,一旦配置了訪問令牌阀溶,就可以開始使用?GitHub API

>>>? gh? =? GitHub('token')>>>? status,? data? =? gh.user.repos.get(visibility='public',? sort='created')>>>? # ^ 映射到 GET /user/repos>>>? data...? ['tweeter',? 'snipey',? '...']

請注意鸦泳,你要確保 URL 拼寫正確银锻,因為我們沒有驗證 URL。如果 URL 不存在或出現(xiàn)了其他任何錯誤做鹰,將返回 API 拋出的錯誤击纬。那么,這一切是如何運作的呢钾麸?讓我們找出答案更振。首先,我們將查看一個?>API 類的簡化示例:

class? API:? ? ? # ... other methods ...? ? ? def __getattr__(self,? key):? ? ? ? ? ? return? IncompleteRequest(self.client).__getattr__(key)? ? ? __getitem__? =? __getattr__

在?API?類上的每次調(diào)用都會調(diào)用?IncompleteRequest 類作為指定的?key饭尝。

class? IncompleteRequest:? ? ? # ... other methods ...? ? ? def __getattr__(self,? key):? ? ? ? ? ? if? key in? self.client.http_methods:? ? ? ? ? ? ? ? ? htmlMethod? =? getattr(self.client,? key)? ? ? ? ? ? ? ? ? return? partial(htmlMethod,? url=self.url)? ? ? ? ? ? else:? ? ? ? ? ? ? ? ? self.url? +=? '/'? +? str(key)? ? ? ? ? ? ? ? ? return? self? ? ? __getitem__? =? __getattr__class? Client:? ? ? http_methods? =? ('get')? # 還有 post, put, patch 等等肯腕。? ? ? def get(self,? url,? headers={},? **params):? ? ? ? ? ? return? self.request('GET',? url,? None,? headers)

如果最后一次調(diào)用不是 HTTP 方法(如?get、post?等)钥平,則返回帶有附加路徑的?IncompleteRequest实撒。否則,它從f="https://github.com/mozilla/agithub/blob/dbf7014e2504333c58a39153aa11bbbdd080f6ac/agithub/base.py#L102-L231">Client 類獲取 HTTP 方法對應(yīng)的正確函數(shù),并返回?partial知态。

如果我們給出一個不存在的路徑會發(fā)生什么捷兰?

>>>? status,? data? =? this.path.doesnt.exist.get()>>>? status...? 404

因為?__getattr__?別名為?__getitem__:

>>>? owner,? repo? =? 'nnja',? 'tweeter'>>>? status,? data? =? gh.repos[owner][repo].pulls.get()>>>? # ^ Maps to GET /repos/nnja/tweeter/pulls>>>? data....? # {....}

這真心是一些方法魔術(shù)!

了解更多

Python 提供了大量工具肴甸,使你的代碼更優(yōu)雅,更易于閱讀和理解囚巴。挑戰(zhàn)在于找到合適的工具來完成工作原在,但我希望本文為你的工具箱添加了一些新工具。而且彤叉,如果你想更進一步庶柿,你可以在我的博客?nnja.io?上閱讀有關(guān)裝飾器、上下文管理器秽浇、上下文生成器和命名元組的內(nèi)容浮庐。隨著你成為一名更好的 Python 開發(fā)人員,我鼓勵你到那里閱讀一些設(shè)計良好的項目的源代碼柬焕。Requests?和?Flask?是兩個很好的起步的代碼庫审残。

如果覺得有幫助,記得關(guān)注斑举、轉(zhuǎn)發(fā)搅轿、收藏喲~

你想要的Python學(xué)習(xí)資料盡在這里

加群:711944363?即可免費獲取富玷!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末璧坟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子赎懦,更是在濱河造成了極大的恐慌雀鹃,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件励两,死亡現(xiàn)場離奇詭異黎茎,居然都是意外死亡,警方通過查閱死者的電腦和手機当悔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門工三,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人先鱼,你說我怎么就攤上這事俭正。” “怎么了焙畔?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵掸读,是天一觀的道長。 經(jīng)常有香客問我,道長儿惫,這世上最難降的妖魔是什么澡罚? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮肾请,結(jié)果婚禮上留搔,老公的妹妹穿的比我還像新娘。我一直安慰自己铛铁,他們只是感情好隔显,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著饵逐,像睡著了一般括眠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倍权,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天掷豺,我揣著相機與錄音,去河邊找鬼薄声。 笑死当船,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的默辨。 我是一名探鬼主播生年,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼廓奕!你這毒婦竟也來了抱婉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤桌粉,失蹤者是張志新(化名)和其女友劉穎蒸绩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铃肯,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡患亿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了押逼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片步藕。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖挑格,靈堂內(nèi)的尸體忽然破棺而出咙冗,到底是詐尸還是另有隱情,我是刑警寧澤漂彤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布雾消,位于F島的核電站灾搏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏立润。R本人自食惡果不足惜狂窑,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望桑腮。 院中可真熱鬧泉哈,春花似錦、人聲如沸破讨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽添忘。三九已至采呐,卻和暖如春若锁,著一層夾襖步出監(jiān)牢的瞬間搁骑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工又固, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留仲器,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓仰冠,卻偏偏與公主長得像乏冀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子洋只,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容