<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Python的Functools模塊簡介

          共 6564字,需瀏覽 14分鐘

           ·

          2021-06-13 16:16

          模塊中有什么?

          functools模塊是Python的標(biāo)準(zhǔn)庫的一部分,它是為高階函數(shù)而實(shí)現(xiàn)的。高階函數(shù)是作用于或返回另一個(gè)函數(shù)或多個(gè)函數(shù)的函數(shù)。一般來說,對這個(gè)模塊而言,任何可調(diào)用的對象都可以作為一個(gè)函數(shù)來處理。

          functools 提供了 11個(gè)函數(shù): 

          • cached_property()

          • cmp_to_key()

          • lru_cache()

          • partial()

          • partialmethod()

          • reduce()

          • singledispatch()

          • singledispatchmethod()

          • total_ordering()

          • update_wrapper()

          • wraps()

          在整篇文章中,我們將更深入地研究每個(gè)函數(shù),并給出一些有用的示例。你可以在GitHub上找到文章中使用的代碼片段。享受吧!


          備注:本文基于Python 3.8.2 (CPython)。有些函數(shù)在CPython的早期版本中可能不存在。

          functools中 的函數(shù)

          @cached_property - 緩存實(shí)例方法

          想象一下,你有一個(gè)大型數(shù)據(jù)集,為了分析它,你實(shí)現(xiàn)了一個(gè)保存整個(gè)數(shù)據(jù)集的類。此外,你還實(shí)現(xiàn)了一些函數(shù)來計(jì)算諸如手頭數(shù)據(jù)集的標(biāo)準(zhǔn)偏差之類的信息。問題:你每次調(diào)用該方法時(shí),它都會(huì)重新計(jì)算標(biāo)準(zhǔn)偏差—這需要時(shí)間啊!這就是@cached_property派上用場的地方了。


          它的目的是將類的一個(gè)方法轉(zhuǎn)換為一個(gè)屬性,該屬性的值只計(jì)算一次,然后被緩存為實(shí)例生命周期中的一個(gè)普通屬性。其行為與內(nèi)置的@property 裝飾器[2]非常相似,只是增加了緩存過程。讓我們來看一下來自Python文檔中的例子:



          在當(dāng)前的場景中,我們在一個(gè)DataSet實(shí)例中存儲(chǔ)了一個(gè)(很大的)數(shù)字序列。此外,我們還定義了兩個(gè)方法,分別用來計(jì)算標(biāo)準(zhǔn)偏差和方差。我們將@cached_property裝飾器[3]分別應(yīng)用于這兩個(gè)函數(shù),以將它們轉(zhuǎn)換為緩存屬性。這意味著值確實(shí)只計(jì)算了一次,然后就被緩存了。


          備注:DataSet的每個(gè)實(shí)例都需要有一個(gè)帶有不可變映射的__dict__屬性。這是裝飾器能夠正確工作所必需的。


          cmp_to_key() - 一個(gè)轉(zhuǎn)換函數(shù)

          在繼續(xù)之前,我們首先需要理解比較函數(shù)和鍵函數(shù)之間的區(qū)別。


          比較函數(shù)是任何可調(diào)用的對象,它會(huì)接受兩個(gè)參數(shù),比較它們并根據(jù)所提供的參數(shù)順序返回一個(gè)數(shù)字。負(fù)數(shù)表示第一個(gè)參數(shù)小于第二個(gè)參數(shù),零表示它們相等,正數(shù)表示第一個(gè)參數(shù)大于第二個(gè)參數(shù)。Python中的一個(gè)簡單實(shí)現(xiàn)可能是這樣的:



          相反,鍵函數(shù)是一個(gè)可調(diào)用對象,它接受一個(gè)參數(shù)并返回另一個(gè)用作排序鍵的值。這個(gè)分組的一個(gè)突出代表是operator.itemgetter()鍵函數(shù)[4],你可能從日常的編碼中已經(jīng)了解了它。鍵函數(shù)通常會(huì)被提供給像sort()、min()和max()之類的內(nèi)置函數(shù)。


          實(shí)際上,cmp_to_key()會(huì)將一個(gè)比較函數(shù)轉(zhuǎn)換為一個(gè)鍵函數(shù)。cmp_to_key()函數(shù)的實(shí)現(xiàn)是為了支持從Python2到Python3的轉(zhuǎn)換,因?yàn)樵赑ython2中存在一個(gè)用于比較和排序的名為cmp()的函數(shù)(以及一個(gè)雙下劃線方法__cmp__())。

          @lru_cache() - 通過緩存增加代碼性能

          @lru_cache()是一個(gè)裝飾器,它用一個(gè)記憶化的可調(diào)用對象來包裝一個(gè)函數(shù),這個(gè)可調(diào)用對象可以保存最近的maxsize次調(diào)用(默認(rèn)值:128)。


          備注:簡單來說,記憶化意味著保存一個(gè)函數(shù)調(diào)用的結(jié)果,如果這個(gè)函數(shù)再次使用相同的參數(shù)被調(diào)用時(shí),則返回該結(jié)果。有關(guān)更多信息,請參閱Dan Bader關(guān)于Python中記憶化的文章[5]。


          如果你有昂貴的或I/O綁定的函數(shù),而這些函數(shù)會(huì)被周期性地使用相同的參數(shù)進(jìn)行調(diào)用,那么這一點(diǎn)特別有用。LRU緩存代表最近最少使用的緩存,指的是這樣一個(gè)緩存,它會(huì)在條目達(dá)到最大大小時(shí)刪除最近最少使用的元素。如果maxsize設(shè)置為None,則LRU特性會(huì)被禁用。


          讓我們來看兩個(gè)例子。在第一個(gè)示例中,我們定義了一個(gè)函數(shù)get_pep(),它接受一個(gè)PEP編號(Python增強(qiáng)提案)并返回這個(gè)PEP的內(nèi)容,如果該P(yáng)EP存在的話。



          如你所見,我們將@lru_cache()裝飾器添加到了函數(shù)中,并將緩存的最大大小設(shè)置為32。我們在使用許多PEP一個(gè)for循環(huán)中調(diào)用get_pep()。如果你仔細(xì)查看list_of_peps,你可以看到有兩個(gè)數(shù)字在列表中出現(xiàn)了兩次甚至三次:8和320。


          一旦你執(zhí)行了這個(gè)腳本,你就會(huì)發(fā)現(xiàn)所獲取的PEP會(huì)在不打印出其URL的情況下立即出現(xiàn),這些PEP我們已經(jīng)從python.org請求過了。這是由于我們沒有調(diào)用函數(shù)并再次從網(wǎng)站獲取它,而是從我們的緩存中獲取它。



          在這個(gè)腳本的最后,我們打印了get_pep()的緩存信息。這表明我們有三次命中,這意味著Python使用了一個(gè)緩存值三次,而不是再次調(diào)用該函數(shù)(一次使用數(shù)字8,兩次使用320)。另外8次調(diào)用未命中,因此調(diào)用了函數(shù)并將結(jié)果添加到了緩存中。因此,最終的緩存由8個(gè)條目組成。


          在第二個(gè)例子中,我們有一個(gè)想要加速的斐波那契數(shù)列的遞歸實(shí)現(xiàn)。


          在這個(gè)例子中,我們計(jì)算了一個(gè)長度為16的斐波那契數(shù)列,并打印生成的序列以及fib()函數(shù)的緩存信息。



          你可能會(huì)對緩存的命中次數(shù)和未命中次數(shù)感到驚訝。但是,請考慮以下情況:首先,我們計(jì)算n=0時(shí)的結(jié)果。因?yàn)槲覀兊木彺嬷羞€沒有條目,所以需要計(jì)算結(jié)果,這將使未命中增加1,并導(dǎo)致hits=0 和 misses=1。當(dāng)你以n=1調(diào)用fib()時(shí),又會(huì)出現(xiàn)這種情況。接著,fib()會(huì)被以n=2調(diào)用。我們通過計(jì)算n=1和n=0的結(jié)果并將它們相加來遞歸地計(jì)算結(jié)果。我們已經(jīng)計(jì)算了這兩個(gè)結(jié)果,所以我們可以從緩存中獲取它們。因此,我們只有一個(gè)新的未命中,因?yàn)槲覀冞€沒有n=2的條目。這個(gè)過程會(huì)一直持續(xù)到所有16個(gè)n都被傳遞給fib(),最后的結(jié)果只有16次未命中。


          你想知道在本例中我們使用@lru_cache()節(jié)省了多少時(shí)間嗎?我們可以使用Python的timeit .timeit()函數(shù)來測試它,這個(gè)函數(shù)會(huì)向我們展示一些不可思議的東西:



          通過使用 @lru_cache(),fib()函數(shù)快了約100000倍-哇偶!這絕對是一個(gè)你想記住的裝飾器。


          @total_ordering - 通過使用裝飾器來減少代碼行數(shù)

          用Python編程通常需要編寫自己的類。在某些情況下,你希望能夠比較該類的不同實(shí)例。根據(jù)你想要比較它們的方式,你最終可能會(huì)實(shí)現(xiàn)像__lt__()、__le__()、__gt__()、 __ge__() 或__eq__() 這樣的函數(shù),以便能夠使用相應(yīng)的<、<=、>、>=和==操作符。另一方面,你可以使用@total_ordering裝飾器。這樣,你只需要實(shí)現(xiàn)一個(gè)或多個(gè)豐富的比較排序方法,這個(gè)裝飾器就會(huì)為你提供其余的方法。此外,我也建議你定義 __eq__()方法。


          假設(shè)你有一個(gè)Pythonista類,你希望能夠按字典順序?qū)λ鼈冞M(jìn)行排序。要做到這一點(diǎn),你需要實(shí)現(xiàn)豐富的比較排序方法。但是,我們并沒有實(shí)現(xiàn)所有這些方法,而是只實(shí)現(xiàn)了__lt__()方法和__eq__()方法。通過使用@ total_ordering修飾符,其他方法可以被自動(dòng)定義。

          執(zhí)行該腳本將打印出True,因?yàn)閏在v之前。注意,盡管我們沒有顯式地實(shí)現(xiàn)__ge__(),但我們也可以使用>操作符。


          如果希望根據(jù)不同的屬性比較實(shí)例,@total_ordering裝飾器是一種減少代碼行數(shù)和調(diào)整代碼的位置的好方法。但是,使用@total_ordering裝飾器會(huì)增加開銷,從而導(dǎo)致執(zhí)行速度變慢。此外,派生的比較方法的堆棧跟蹤更為復(fù)雜。因此,如果你需要非常高性能的代碼,你就不應(yīng)該使用該裝飾器,而應(yīng)該自己去實(shí)現(xiàn)所需的方法。


          partial() - 簡化簽名

          使用partial()你可以創(chuàng)建partial對象。這些對象的行為類似于傳遞給partial()的函數(shù),該函數(shù)使用提供給partial()的(關(guān)鍵字)參數(shù)進(jìn)行調(diào)用。因此,與原始函數(shù)相比,新創(chuàng)建的(partial)對象具有一個(gè)簡化的簽名。

          這里是一個(gè)例子:

          我們基于內(nèi)置的int()函數(shù)創(chuàng)建了一個(gè)partial對象。在本例中,我們提供base=2作為關(guān)鍵字參數(shù)。因此,新創(chuàng)建的basetwo對象的行為就像我們用base=2調(diào)用int()一樣。但是,我們?nèi)匀豢梢酝ㄟ^向base2提供一個(gè)base參數(shù)來覆蓋這種行為。因此,執(zhí)行basetwo("10010", base=10)計(jì)算的結(jié)果與int("10010")相同。

          我們來看另一個(gè)例子。


          這個(gè)函數(shù)會(huì)計(jì)算二維空間中兩點(diǎn)之間的歐氏距離。我們可以創(chuàng)建一個(gè)partial對象,它只接受一個(gè)參數(shù)(一個(gè)點(diǎn))并計(jì)算我們所提供的點(diǎn)與點(diǎn)(0,0)之間的歐式距離。


          partialmethod() - 方法的partial()

          partialmethod()是一個(gè)函數(shù),它會(huì)返回partialmethod描述符。你可以將它看作方法的partial()函數(shù)。這意味著它不是可調(diào)用的,而只是定義新方法的一種方式。我非常喜歡Python文檔[6]中的示例,所以我們來看看它。

          我們定義一個(gè)表示單個(gè)單元格的類Cell。它有一個(gè)alive屬性和一個(gè)將alive設(shè)置為True或False的實(shí)例方法set_state()。此外,我們還創(chuàng)建了兩個(gè)partialmethod描述符set_alive()和set_dead(),它們會(huì)分別用True和False調(diào)用set_state()。這允許我們創(chuàng)建Cell類的一個(gè)新實(shí)例,調(diào)用set_alive()將該單元格的狀態(tài)更改為True并打印出該屬性的值。


          reduce() - 基于多個(gè)值計(jì)算單個(gè)值

          假設(shè)你有一個(gè)由數(shù)字組成的可迭代對象,并希望將其縮減為單個(gè)值。在本例中,結(jié)果值是所提供的可迭代對象的所有元素的和。實(shí)現(xiàn)此目的的一種方法是使用reduce()。


          如你所見,我們定義了一個(gè)包含數(shù)字1到5的列表。我們通過以operator.add()作為第一個(gè)參數(shù),以該列表為第二個(gè)參數(shù)調(diào)用reduce()函數(shù)來計(jì)算這個(gè)列表中所有元素的和。當(dāng)然,你也可以使用內(nèi)置的sum()函數(shù),但是如果你想計(jì)算所有元素的乘積呢?你惟一需要更改的是將operator.add()函數(shù)替換為operator.mul() - 搞定!


          @singledispatch - 函數(shù)重載

          根據(jù)定義,@singledispatch裝飾器會(huì)將一個(gè)函數(shù)轉(zhuǎn)換為一個(gè)單分派泛函數(shù)。在@singledispatch的情況下,分派發(fā)生在第一個(gè)參數(shù)的類型上。


          備注: 泛函數(shù)是由多個(gè)函數(shù)組成的函數(shù),這些函數(shù)為不同的類型實(shí)現(xiàn)了相同的操作。在調(diào)用期間應(yīng)該使用哪個(gè)實(shí)現(xiàn)由分派算法[7]決定。

          備注: 單分派是泛函數(shù)分派的一種形式,其中,實(shí)現(xiàn)是基于單個(gè)參數(shù)[8]的類型進(jìn)行選擇的。


          簡單來說,@singledispatch允許你在Python中重載函數(shù)。讓我們以一個(gè)例子來說明它。


          在這個(gè)例子中,我們定義了一個(gè)函數(shù)mul(),它接受兩個(gè)參數(shù)并返回它們的乘積。然而,在Python中,兩個(gè)字符串相乘會(huì)引發(fā)一個(gè)TypeError。我們可以通過注冊_()函數(shù)來提供一個(gè)補(bǔ)丁。執(zhí)行腳本后的結(jié)果是:



          @singledispatchmethod - 方法重載

           @singledispatchmethod 裝飾器解決了與@singledispatch裝飾器相同的任務(wù),只不過它是針對方法的。



          Negator類有一個(gè)名為neg()的實(shí)例方法。在默認(rèn)情況下,neg()函數(shù)會(huì)引發(fā)一個(gè)NotImplementedError。但是,對于整數(shù)和布爾類型,該函數(shù)會(huì)被重載,并在這些情況下返回否定。執(zhí)行腳本后的結(jié)果是:



          update_wrapper() - 隱藏包裝器函數(shù)

          update_wrapper()函數(shù)背后的思想是以一種方式更新一個(gè)包裝器函數(shù)(顧名思義),使其看起來像包裝后的函數(shù)。為了實(shí)現(xiàn)這一點(diǎn),update_wrapper()將包裝后的函數(shù)__module__, __name__, __qualname__, __annotations__和 __doc__賦給包裝器函數(shù)。此外,它還會(huì)更新該包裝器函數(shù)的__dict__。


          讓我們以一個(gè)實(shí)際的例子看一下@wraps裝飾器。


          @wraps - update_wrapper()的便捷函數(shù)

          @wraps是一個(gè)裝飾器,它充當(dāng)一個(gè)調(diào)用update_wrapper()的便捷函數(shù)。確切地說,它與調(diào)用partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)是一樣的。在閱讀了關(guān)于update_wrapper()和@wraps的技術(shù)細(xì)節(jié)之后,你可能會(huì)問自己我們?yōu)槭裁葱枰[藏我們的包裝器函數(shù)。


          下面的代碼片段定義了一個(gè)裝飾器@show_args。它會(huì)在函數(shù)自身被調(diào)用之前打印出用來調(diào)用該函數(shù)的參數(shù)和關(guān)鍵字參數(shù)。



          現(xiàn)在,我們可以定義一個(gè)函數(shù)add(),它會(huì)返回兩個(gè)傳遞的整數(shù)的和。此外,我們還會(huì)將新編寫的裝飾器應(yīng)用于它,因?yàn)槲覀儗υ摵瘮?shù)的參數(shù)和關(guān)鍵字參數(shù)比較感興趣。在腳本的最后,我們打印了一個(gè)簡單加法的結(jié)果以及該函數(shù)的文檔字符串和名稱。



          你是否期望看到一個(gè)與打印的文檔字符串和名稱不同的文檔字符串和名稱呢? 這是因?yàn)槲覀儧]有訪問包裝后的函數(shù)的文檔字符串和名稱,而是訪問了包裝器函數(shù)的文檔字符串和名稱。這里@wraps就派上用場了。我們需要在代碼中更改的惟一的東西就是將這個(gè)裝飾器應(yīng)用到wrapper()函數(shù)。



          如果我們現(xiàn)在運(yùn)行該腳本,我們會(huì)看到預(yù)期的輸出:



          總  結(jié)

          恭喜,你已經(jīng)順利閱讀完了這篇文章!現(xiàn)在,你已經(jīng)對functools模塊所包含的函數(shù)有了大致的了解。此外,你還實(shí)現(xiàn)了一些示例,其中的這些函數(shù)非常有用。


          希望你享受閱讀這篇文章。記得與你的朋友和同事分享哦。如果你還沒有,請考慮在Twitter上關(guān)注我,我是@DahlitzF,或者訂閱我的時(shí)事通訊,這樣你就不會(huì)錯(cuò)過以后的文章了。保持好奇心,持續(xù)編碼!


          參考資料

          1. functools文檔?

          2. 內(nèi)置property()函數(shù) 

          3. Python裝飾器入門

          4. itemgetter()文檔

          5. Python 中的記憶化:如何緩存函數(shù)結(jié)果

          6. partialmethod()文檔  ?

          7. 泛函數(shù) - 詞條 

          8. 單一分派 - 詞條 

          英文原文:https://florian-dahlitz.de/blog/introduction-to-functools
          譯者:天天向
          ···  END  ···

          推薦閱讀:
          一、Number(數(shù)字)
          Python基礎(chǔ)之?dāng)?shù)字(Number)超級詳解
          Python隨機(jī)模塊22個(gè)函數(shù)詳解
          Python數(shù)學(xué)math模塊55個(gè)函數(shù)詳解
          二、String(字符串)
          Python字符串的45個(gè)方法詳解
          Pandas向量化字符串操作
          三、List(列表)
          超級詳解系列-Python列表全面解析
          Python輕量級循環(huán)-列表推導(dǎo)式
          四、Tuple(元組)
          Python的元組,沒想象的那么簡單
          五、Set(集合)
          全面理解Python集合,17個(gè)方法全解,看完就夠了
          六、Dictionary(字典)
          Python字典詳解-超級完整版
          七、內(nèi)置函數(shù)
          Python初學(xué)者必須吃透這69個(gè)內(nèi)置函數(shù)!
          八、正則模塊
          Python正則表達(dá)式入門到入魔
          筆記 | 史上最全的正則表達(dá)式
          八、系統(tǒng)操作
          Python之shutil模塊11個(gè)常用函數(shù)詳解
          Python之OS模塊39個(gè)常用函數(shù)詳解
          九、進(jìn)階模塊
          【萬字長文詳解】Python庫collections,讓你擊敗99%的Pythoner
          高手如何在Python中使用collections模塊

          掃描關(guān)注本號↓

          瀏覽 74
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  北条麻妃在线直播 | 五月天婷婷激情综合网 | 欧美亚洲乱伦 | 黄色在线观看国产高清 | 综合一和综合二图片小说 |