新聞中心
3 個可以使你的 Python 代碼更優(yōu)雅、可讀、直觀和易于維護的工具。
網(wǎng)站建設哪家好,找成都創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁設計、網(wǎng)站建設、微信開發(fā)、重慶小程序開發(fā)、集團企業(yè)網(wǎng)站建設等服務項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了隆昌免費建站歡迎大家使用!
Python 提供了一組獨特的工具和語言特性來使你的代碼更加優(yōu)雅、可讀和直觀。為正確的問題選擇合適的工具,你的代碼將更易于維護。在本文中,我們將研究其中的三個工具:魔術方法、迭代器和生成器,以及方法魔術。
魔術方法
魔術方法可以看作是 Python 的管道。它們被稱為“底層”方法,用于某些內(nèi)置的方法、符號和操作。你可能熟悉的常見魔術方法是 __init__(),當我們想要初始化一個類的新實例時,它會被調(diào)用。
你可能已經(jīng)看過其他常見的魔術方法,如 __str__ 和 __repr__。Python 中有一整套魔術方法,通過實現(xiàn)其中的一些方法,我們可以修改一個對象的行為,甚至使其行為類似于內(nèi)置數(shù)據(jù)類型,例如數(shù)字、列表或字典。
讓我們創(chuàng)建一個 Money 類來示例:
class Money:currency_rates = {'$': 1,'€': 0.88,}def __init__(self, symbol, amount):self.symbol = symbolself.amount = amountdef __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,假設我們已經(jīng)定義了使用兩種不同貨幣的食品的成本,如下所示:
>>> soda_cost = Money('$', 5.25)>>> soda_cost$5.25>>> pizza_cost = Money('€', 7.99)>>> pizza_cost€7.99
我們可以使用魔術方法使得這個類的實例之間可以相互交互。假設我們希望能夠?qū)⑦@個類的兩個實例一起加在一起,即使它們是不同的貨幣。為了實現(xiàn)這一點,我們可以在 Money 類上實現(xiàn) __add__ 這個魔術方法:
class Money:# ... previously defined methods ...def __add__(self, other):""" Add 2 Money instances using '+' """new_amount = self.amount + self.convert(other).amountreturn Money(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ù)字類型或魔術方法指南來獲得更多信息。
我們學習到 __add__ 映射到內(nèi)置運算符 +。其他魔術方法可以映射到像 [] 這樣的符號。例如,在字典中通過索引或鍵來獲得一項,其實是使用了 __getitem__ 方法:
>>> d = {'one': 1, 'two': 2}>>> d['two']2>>> d.__getitem__('two')2
一些魔術方法甚至映射到內(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é)議。這使我們可以輕松地遍歷它們。
>>> for food in ['Pizza', 'Fries']:print(food + '. Yum!')Pizza. Yum!Fries. Yum!
我們?nèi)绾蔚覀冏约旱淖远x類?首先,讓我們來澄清一些術語。
- 要成為一個可迭代對象,一個類需要實現(xiàn)
__iter__() __iter__()方法需要返回一個迭代器- 要成為一個迭代器,一個類需要實現(xiàn)
__next__()(或在 Python 2中是next()),當沒有更多的項要迭代時,必須拋出一個StopIteration異常。
呼!這聽起來很復雜,但是一旦你記住了這些基本概念,你就可以在任何時候進行迭代。
我們什么時候想使用自定義迭代器?讓我們想象一個場景,我們有一個 Server 實例在不同的端口上運行不同的服務,如 http 和 ssh。其中一些服務處于 active 狀態(tài),而其他服務則處于 inactive 狀態(tài)。
class Server:services = [{'active': False, 'protocol': 'ftp', 'port': 21},{'active': True, 'protocol': 'ssh', 'port': 22},{'active': True, 'protocol': 'http', 'port': 80},]
當我們遍歷 Server 實例時,我們只想遍歷那些處于 active 的服務。讓我們創(chuàng)建一個 IterableServer 類:
class IterableServer:def __init__(self):self.current_pos = 0def __next__(self):pass # TODO: 實現(xiàn)并記得拋出 StopIteration
首先,我們將當前位置初始化為 0。然后,我們定義一個 __next__() 方法來返回下一項。我們還將確保在沒有更多項返回時拋出 StopIteration。到目前為止都很好!現(xiàn)在,讓我們實現(xiàn)這個 __next__() 方法。
class IterableServer:def __init__(self):self.current_pos = 0. # 我們初始化當前位置為 0def __iter__(self): # 我們可以在這里返回 self,因為實現(xiàn)了 __next__return selfdef __next__(self):while self.current_pos < len(self.services):service = self.services[self.current_pos]self.current_pos += 1if service['active']:return service['protocol'], service['port']raise StopIterationnext = __next__ # 可選的 Python2 兼容性
我們對列表中的服務進行遍歷,而當前的位置小于服務的個數(shù),但只有在服務處于活動狀態(tài)時才返回。一旦我們遍歷完服務,就會拋出一個 StopIteration 異常。
因為我們實現(xiàn)了 __next__() 方法,當它耗盡時,它會拋出 StopIteration。我們可以從 __iter__() 返回 self,因為 IterableServer 類遵循 iterable 協(xié)議。
現(xiàn)在我們可以遍歷一個 IterableServer 實例,這將允許我們查看每個處于活動的服務,如下所示:
>>> for protocol, port in IterableServer():print('service %s is running on port %d' % (protocol, port))service ssh is running on port 22service http is running on port 21
太棒了,但我們可以做得更好!在這樣類似的實例中,我們的迭代器不需要維護大量的狀態(tài),我們可以簡化代碼并使用 generator(生成器) 來代替。
class Server:services = [{'active': False, 'protocol': 'ftp', 'port': 21},{'active': True, 'protocol': 'ssh', 'port': 22},{'active': True, 'protocol': 'http', 'port': 21},]def __iter__(self):for service in self.services:if service['active']:yield service['protocol'], service['port']
yield 關鍵字到底是什么?在定義生成器函數(shù)時使用 yield。這有點像 return,雖然 return 在返回值后退出函數(shù),但 yield 會暫停執(zhí)行直到下次調(diào)用它。這允許你的生成器的功能在它恢復之前保持狀態(tài)。查看 yield 的文檔以了解更多信息。使用生成器,我們不必通過記住我們的位置來手動維護狀態(tài)。生成器只知道兩件事:它現(xiàn)在需要做什么以及計算下一個項目需要做什么。一旦我們到達執(zhí)行點,即 yield 不再被調(diào)用,我們就知道停止迭代。
這是因為一些內(nèi)置的 Python 魔法。在 Python 關于 __iter__() 的文檔中我們可以看到,如果 __iter__() 是作為一個生成器實現(xiàn)的,它將自動返回一個迭代器對象,該對象提供 __iter__() 和 __next__() 方法。閱讀這篇很棒的文章,深入了解迭代器,可迭代對象和生成器。
方法魔法
由于其獨特的方面,Python 提供了一些有趣的方法魔法作為語言的一部分。
其中一個例子是別名功能。因為函數(shù)只是對象,所以我們可以將它們賦值給多個變量。例如:
>>> def foo():return 'foo'>>> foo()'foo'>>> bar = foo>>> bar()'foo'
我們稍后會看到它的作用。
Python 提供了一個方便的內(nèi)置函數(shù)稱為 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)處理命令。
class Operations:def say_hi(self, name):print('Hello,', name)def say_bye(self, name):print ('Goodbye,', name)def default(self, arg):print ('This operation is not supported.')if __name__ == '__main__':operations = Operations()# 假設我們做了錯誤處理command, argument = input('> ').split()func_to_call = getattr(operations, command, operations.default)func_to_call(argument)
腳本的輸出是:
$ python getattr.py> say_hi NinaHello, Nina> blah blahThis operation is not supported.
接下來,我們來看看 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)
讓我們看看在我喜歡的一個名為 agithub 的庫中的一些示例代碼中,這個方法魔術是如何結(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 和任何其他相關連接屬性定義端點 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。否則,它從Client 類獲取 HTTP 方法對應的正確函數(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.... # {....}
這真心是一些方法魔術!
了解更多
Python 提供了大量工具,使你的代碼更優(yōu)雅,更易于閱讀和理解。挑戰(zhàn)在于找到合適的工具來完成工作,但我希望本文為你的工具箱添加了一些新工具。而且,如果你想更進一步,你可以在我的博客 nnja.io 上閱讀有關裝飾器、上下文管理器、上下文生成器和命名元組的內(nèi)容。隨著你成為一名更好的 Python 開發(fā)人員,我鼓勵你到那里閱讀一些設計良好的項目的源代碼。Requests 和 Flask 是兩個很好的起步的代碼庫。
分享名稱:日常Python編程代碼優(yōu)雅之道
文章來源:http://fisionsoft.com.cn/article/dhdohde.html


咨詢
建站咨詢

