作為程式設計師,起碼要知道的 Python 修飾器!

2021-01-11 CSDN

Python修飾器是個非常強大的概念,可以用一個函數去「包裝」另一個函數。修飾器的思想,就是把函數中除了正常行為之外的部分抽象出去。這樣有很多好處,如很容易進行代碼復用,並且能遵守科裡定律(即一次只做一件事)。學習怎樣編寫修飾器,可以大幅度增加代碼的可讀性。它能改變函數的行為,而無需實際去改變函數的代碼(如添加日誌行等)。而修飾器是Python中非常常用的工具,用過Flask、Click等框架的人,都應該很熟悉修飾器,但許多人只知道怎麼用,卻不知道該怎麼寫。如果你也不知道怎麼寫,那這篇文章,正是為你準備的!

修飾器的原理

首先我們來看一個Python修飾器的例子。下面是個非常簡單的例子,演示了修飾器的使用方法。

@my_decoratordefhello(): print('hello')

在Python中定義的函數實際上是個對象。

上面的函數Hello是個函數對象。@my_decorator實際上也是個函數,它接受hello對象,並返回另一個對象給解釋器。修飾器返回的對象會成為實際的hello函數。

本質上這跟正常的函數一樣,如hello = decorate(hello)。我們傳給函數decorate一個函數,decorate可以隨便怎樣去用這個函數,然後返回另一個對象。修飾器可以乾脆吞掉那個函數,或者如果需要,還可以返回某個不是函數的東西。

編寫自己的修飾器

前面說過,修飾器就是個簡單的函數,它接受函數作為輸入,返回一個對象。因此,編寫修飾器實際上只需要定義一個函數。

defmy_decorator(f):return5

任何函數都可以用作修飾器。這個例子中,修飾器被傳入一個函數,然後返回一個不同的東西。它完全吃掉了輸入的函數,並且永遠返回5。

@my_decoratordefhello():print('hello')

>>> hello()Traceback (most recent call last):File "<stdin>", line 1, in <module>TypeError:'int' object is not callable'int' object is not callable

由於修飾器返回的是INT,INT不是Callable,因此不能作為函數調用。別忘了,修飾器的返回值會替換掉Hello。

>>> hello5

絕大多數情況下,我們希望修飾器返回的對象能夠模擬被修飾的函數。這就是說,修飾器返回的對象應該也是個函數。

例如,假設我們希望在每次函數被調用時輸出一行文字,我們可以寫個函數輸出信息,然後再調用輸入的函數。但這個函數必須由修飾器返回。因此我們的函數得寫成嵌套的形式,如:

defmydecorator(f):# f is the function passed to us from pythondeflog_f_as_called():print(f'{f} was called.') f()return log_f_as_called

從上面的代碼可以看出,我們定義了個嵌套的函數,該嵌套函數被修飾器返回。這樣,Hello函數依然可以被當做函數調用,調用者並不知道Hello被修飾過了。現在Hello函數可以這樣定義:

@mydecoratordefhello():print('hello')

輸出如下:

>>> hello()<function hello at 0x7f27738d7510> was called.hello

(注意:<function hello at 0x7f27738d7510>中的數字代表內存地址,因此每個人的都會不一樣。)

正確包裝函數

如果有必要,函數可以被修飾多次。這種情況下,修飾器會引起連鎖反應。本質上,每個修飾器的返回值都會傳遞給上一層的修飾器,直到最頂層。例如,下面的代碼:

@a@b@cdefhello():print('hello')

解釋器實際上執行的是hello = a(b(c(hello))),所有修飾器都會互相包裝。你可以用之前定義的修飾器測試這一點,使用兩次就好:

@mydecorator@mydecoratordefhello():print('hello')>>> hello()<function mydec.<locals>.a at 0x7f277383d378> was called.<function hello at 0x7f2772f78ae8> was called.hello

你會注意到,第一個修飾器包裝了第二個,然後產生了不同的輸出。

有意思的是,第一行輸出的結果是<function mydec.<locals>.a at 0x7f277383d378>,而不是像第二行那樣輸出我們期待的信息:<function hello at 0x7f2772f78ae8>。

這是因為修飾器返回的是個新函數,這個新函數不叫Hello。作為例子來說這無所謂,但實際上這可能會讓測試失敗,或者讓試圖自省函數屬性的過程失敗。

所以,如果修飾器的思想是模擬被修飾的函數的行為,那麼它也應該模擬被修飾函數的樣子。幸運的是,有個Python標準庫functools模塊提供的修飾器wraps能做到這一點:

import functoolsdefmydecorator(f):@functools.wraps(f) # we tell wraps that the function we are wrapping is fdeflog_f_as_called(): print(f'{f} was called.') f()return log_f_as_called@mydecorator@mydecoratordefhello(): print('hello')>>> hello()<function hello at 0x7f27737c7950> was called.<function hello at 0x7f27737c7f28> was called.hello

現在,新的函數看起來跟它修飾的函數一模一樣。但是,我們這個修飾器依然只能修飾不返回任何值,並且不接受任何輸入的函數。如果想讓它更通用,就必須負責傳遞函數參數,並且返回同樣的值。可以這樣修改:

import functoolsdefmydecorator(f):@functools.wraps(f) # wraps is a decorator that tells our function to act like fdeflog_f_as_called(*args, **kwargs): print(f'{f} was called with arguments={args} and kwargs={kwargs}') value = f(*args, **kwargs) print(f'{f} return value {value}')return valuereturn log_f_as_called

現在每次調用都會產生輸出,包含函數接收到的所有輸入,以及函數的返回值。現在可以用它來修飾任意函數,獲得關於函數的輸入和輸出的調試信息,而用不著手動編寫日誌代碼了。

給修飾器增加變量

如果你寫的修飾器不是只給自己用,而是想在產品代碼裡使用,那你可能需要把所有print語句換成日誌輸出語句。那樣的話就需要定義日誌的級別。

都定義成DEBUG級別也許沒問題,但還是能根據函數選擇級別最好。我們可以給修飾器提供變量,以改變修飾器的行為。例如:

@debug(level='info')defhello():print('hello')

上面的代碼可以指定,被修飾的函數應該以info級別輸出日誌,而不是DEBUG級別。這個功能的實現方法是寫個函數,返回修飾器。

沒錯,修飾器也是個函數。所以這段代碼實質上是hello = debug('info')(hello)。兩對括號看起來很奇怪,不過本質上說,DEBUG是個返回函數的函數。因此,修改我們之前的修飾器,我們還需要一層嵌套,這樣代碼如下所示:

import functoolsdefdebug(level): defmydecorator(f)@functools.wraps(f)deflog_f_as_called(*args, **kwargs): logger.log(level, f'{f} was called with arguments={args} and kwargs={kwargs}') value = f(*args, **kwargs) logger.log(level, f'{f} return value {value}')return valuereturn log_f_as_calledreturn mydecorator

上面的修改將DEBUG變成了一個返回修飾器的函數,返回的修飾器會使用正確的日誌級別。這段代碼看起來不太好看,而且嵌套太多了。

有個很酷的小技巧我非常喜歡,就是給DEBUG添加默認的level參數,返回一個部分函數。部分函數是「不完整的函數調用」,它包含一個函數和一些參數,這樣部分函數可以作為一個整體來傳遞,而無需調用實際的函數。

import functoolsdefdebug(f=None, *, level='debug'):if f isNone:return functools.partial(debug, level=level)@functools.wraps(f) # we tell wraps that the function we are wrapping is fdeflog_f_as_called(*args, **kwargs): logger.log(level, f'{f} was called with arguments={args} and kwargs={kwargs}') value = f(*args, **kwargs) logger.log(level, f'{f} return value {value}')return valuereturn log_f_as_called

現在修飾器可以正常工作了:

@debugdefhello():print('hello')

這樣就會使用DEBUG級別。或者可以覆蓋log級別:

@debug('warning')defhello():print('hello')

原文:https://timber.io/blog/decorators-in-python/作者:Nick Humrich譯者:彎月,責編:胡巍巍

相關焦點

  • 好程式設計師Python培訓分享numpy簡介
    好程式設計師Python培訓分享numpy簡介:一、numpy簡介:NumPy是一個功能強大的Python庫,主要用於對多維數組執行計算。NumPy這個詞來源於兩個單詞-- Numerical和Python。NumPy提供了大量的庫函數和操作,可以幫助程式設計師輕鬆地進行數值計算。
  • Python程式設計師最常犯的10個錯誤,你中招了嗎?
    同時作為一門腳本語言,它兼容部分現有的組件和服務。Python還支持模塊和各種庫的擴展,有助於實現模塊化編程和提高代碼復用率。關於本文剛接觸這門語言的新手可能會對Python簡潔靈活的語法有些不適應,或是低估了Python強大的性能。
  • 人生苦短,我用Python,那麼問題來了,普通人要學python嗎?
    最近在教育店集中地兒瞎晃悠,震驚的發現這年頭六歲娃兒都要學編程了,當時我的表情是這樣的。回到家抱著冷嘲熱諷的心,我特意百度搜索了下新聞,結果我的表情是這樣的:1、Python將納入浙江省高考!從 2018 年起浙江省信息技術教材程式語言將會更換為 Python。
  • 《Python程式設計師面試算法寶典》PDF超清版開源了文末附下載方式
    全面介紹Python程式設計師面試筆試技巧和方法,教你如何以「不變應萬變」。√ 兩萬多行代碼,100多個知識點,全面覆蓋Python程式設計師各類面試題型。√ 15年開發經驗、實戰技巧總結,站在「巨人」的肩膀上,讓學習走捷徑。
  • 如果不懂Numpy,請別說自己是Python程式設計師
    在那之前,我一直覺得自己是一個合(you)格(xiu)的 python 程式設計師,似乎無所不能。但磁層頂模型的顯示效果令我沮喪——儘管這個模型只有十幾萬個頂點,拖拽、縮放卻非常卡頓。最終,我把頂點數量刪減到兩萬左右,以兼顧模型質量和響應速度,才勉強交付了這個任務。從此我開始懷疑 python 的性能,甚至一度懷疑 python 是否還是我的首選工具。
  • 不懂NumPy 算什麼 Python 程式設計師?|CSDN 博文精選
    越來越多的基於 Python 的科學和數學軟體包使用 NumPy 數組,雖然這些工具通常都支持 Python 的原生數組作為參數,但它們在處理之前會還是會將輸入的數組轉換為 NumPy 的數組,而且也通常輸出為 NumPy 數組。在 Python 的圈子裡,NumPy 的重要性和普遍性日趨增強。
  • Python已是曇花一現,但你卻還在堅持嗎?看看他們的理由
    Python,是一門最適合人工智慧的程式語言,並且,這門語言十分適合新手學習,正因為如此,讓發展了近三十年的python,在最近幾年火爆全網。可為什麼說python已是曇花一現呢?因為有人找不到工作。在國內真正用python來開發核心業務的公司不多,大部分都是應用在一些非核心的業務上。這也就導致國內目前對python程式設計師的需求似乎並不是很大。
  • 相比於Java,python到底有哪些優勢?
    由於在AI的帶動下python更是異軍突起,撼動了許多老大哥的地位。可唯獨java穩如泰山,不可動搖!自然而然的就會出現python與Java的討論聲。本文的目的在於討論python和java相比到底有哪些優勢,至於缺點暫且不提!
  • 新手請進:每個Python程式設計師都應該知道的10個縮寫詞
    OOP(面向對象編程)要介紹的第一個縮寫是OOP——面向對象編程,這就是Python所基於的設計。大家都知道編程本身是關於編碼的,但是程序本身應該是關於數據的。程序需要獲取輸入數據、處理數據和輸出數據。
  • 搭上python號小火箭,程序運行越來越快!
    是時候證明給那些python黑粉,讓他們看看如何提升Python程序性能並使其像坐上火箭一樣運行飛快!圖源:Unsplash時序分析優化之前,首先要找到是哪部分代碼拖慢了整個程序的運行。對特定函數計時已經知道拖慢程序運行的函數,下一步可使用簡單的修飾器,專門對該函數計時,不測量其餘代碼。
  • 普通人學Python有意義嗎?學Python有前途嗎?-開課吧Python
    Pythonpython憑藉著第三方庫數量的龐大,其幾乎可以說是萬能的,對於普通人來說,數據表格excel基本上都有需要製作,而有一些編程基礎的,就可以使用庫openpyxl來實現excel表格的自動處理和生成,同時除了excel之外,針對word,ppt等python都有對應的庫。
  • python基礎教程之python是什麼?
    如果你聽說過TIOBE排行榜,你就能知道程式語言的大致流行程度。這是最近10年最常用的10種程式語言的變化圖:python是什麼--python的功能總的來說,這幾種程式語言各有千秋。如果一個資深程式設計師向你炫耀他寫的晦澀難懂、動不動就幾萬行的代碼,你可以盡情地嘲笑他。那Python適合開發哪些類型的應用呢?
  • Python為什麼這麼火?小孩子適合學習python編程嗎?
    「人生苦短,我選Python」——魯迅程式語言由於學習門檻比較高,一直以來似乎只有程式設計師之間會互相討論,普通人也很難對冰冷的語法和算法什麼的感興趣。但自從Python出現後,程式語言和我們日常生活中的鴻溝被悄悄打破了,越來越多的人開始使用它,甚至開始愛上它。
  • 資深程式設計師大佬告訴你,如何成為一個C++高級程式設計師
    語言我們要成為一個程式設計師,學的東西會很多很雜,但是最開始一定要從語言開始學習,而學習語言最關鍵的莫過於選好一本書,學校的教材就算了,根本沒 有寫得好的。在此隆重推薦《C++ Primer》,這本書很厚,內容也很豐富,對知識的講解不僅僅停留在表面。如果這本書能有耐心看完,語言方面基本就沒有什麼大問題了,對以後的學習也打 下了一個很好的基礎。2.
  • 如何自學成 Python 大神?這裡有些建議
    再次強調,關鍵點還是要在於對編程保持持續性,讓你的大腦保持住對語言語法的了解,並改善你解決問題的思維過程。 實踐創建自己的項目,或加入開源社區( https://coolpythoncodes.com/julien-danjou )和 Github,這些都是編程的必經之路。
  • 中科大統計學python_python 中科大 - CSDN
    他的課程深入淺出,在介紹強化學習概念的過程中穿插了很多例子,對初學者非常友好,建議作為第一個觀看的入門視頻課程。,也知道小白入門的痛點在哪裡,所以這有可能是大家見到的最簡潔的python入門教程,每節課視頻長度5到10分鐘,再花個一兩個小時敲一敲代碼就足夠了。
  • python基礎課程 第5章 奇妙的內建函數
    從我最早立志於成為程式設計師寫第一行代碼開始,經常在書本裡和前輩嘴裡了解了這麼一句話 「不要重複你自己 (Don't Repeat Yourself )」,也就是所謂的DRY原則,並以此作為自己的程式設計師準則之一。DRY原則的意思是指不要為一個相同的功能或者類似的功能重複編寫兩份代碼。
  • python是什麼:Python相關內容了解
    今天來聊聊一篇關於python是什麼:Python相關內容了解的文章,現在就為大家來簡單介紹下python是什麼:Python相關內容了解,希望對各位小夥伴們有所幫助。如果你聽說過TIOBE排行榜,你就能知道程式語言的大致流行程度。這是最近10年最常用的10種程式語言的變化圖:Python的功能:總的來說,這幾種程式語言各有千秋。C語言是可以用來編寫作業系統的貼近硬體的語言,所以,C語言適合開發那些追求運行速度、充分發揮硬體性能的程序。而Python是用來編寫應用程式的高級程式語言。
  • 四六級考試沒過,作為程式設計師們的你為何要學習英語?該如何學習?
    前言作為在中國工作的程式設計師,不懂得英語似乎也不妨礙找到好工作,升職加薪。但程式設計師這個工種則稍有不同,因為程序,尤其是高級語言,基本上都是由英語和數字表達式構成的。英語對於程式設計師十分重要。為什麼要學習英語學好英語你可以直接閱讀各種經典書籍的原文版。
  • 據說不知道這些大神的程式設計師不是真正的程式設計師
    簡評:據說不知道這些大神的程式設計師不是真正的程式設計師,這些大神有的可以憑藉一本未完成的書獲得ACM圖靈獎,有的微軟開出百萬年薪蓋茨親自來挖人,更甚者用自己發明的語言重新開發一套作業系統。這些大神不僅極大地促進了計算機行業的發展和軟體技術的革新,而且也讓我們這些後生的程式設計師能夠在他們的技術鋪墊上,利用他們開發的平臺工具或是語言更好地開發軟體。所以作為程式設計師的我們在學習技術的同時也應該多去了解這些大神背後的故事,在敬仰之餘也去學習下他們不斷進取,富有開創性的精神。   D.E Knuth(高納德.