歡迎光臨
每天分享高質量文章

你必須學寫 Python 裝飾器的五個理由

來源:Python程式員

ID:pythonbuluo

你必須學寫Python裝飾器的五個理由

—-裝飾器能對你所寫的程式碼產生極大的正面作用

作者:Aaron Maxwell,2016年5月5日

Python裝飾器是很容易使用的。任何一個會寫Python函式的人都能夠學會使用裝飾器,比如下麵這個:

@somedecorator
def some_function():
  print("Check it out, I"m using decorators!")

但是,寫出一個裝飾器是一個完全不一樣的技能。而且這也不是,你不得不理解下麵這些:


  • 閉包

  • 如何將函式作為”第一類”引數來使用

  • 變數引數

  • 引數解包

  • 甚至是Python是如何裝載原始碼的一些細節

所有這些都需要花很多時間去理解和掌握。而且當你已經有這麼一堆事情要學的時候,這些值得你花時間嗎?

對我來說,這個問題的答案已然是上千次的肯定,是的,我會學習!”


寫裝飾器的最重要的好處是什麼呢?在你每天的開發中,裝飾器讓你做什麼做起來是很容易並且很強大的呢?


分析、日誌以及指導


尤其是在大型軟體中,我們通常需要專門來測試到底發生了什麼,以及記錄那些能量化不同行為的指標。透過在裝飾器內部的函式或者方法裡面封裝這些重要的事件,這個裝飾器能通俗易懂且容易地處理剛才這些所講的需求。比如:

from myapp.log import logger
   def log_order_event(func):
       def wrapper(*args, **kwargs):
           logger.info("Ordering: %s", func.__name__)
           order = func(*args, **kwargs)
           logger.debug("Order result: %s", order.result)
           return order
       return wrapper

   @log_order_event
   def order_pizza(*toppings):
       # let"s get some pizza!

同樣的方式可以被用來計數或者其他指標。


驗證與執行檢查


Python的型別系統是相當型別化了的,但是也是很動態的。對於它的這些所有的好處,也意味著某一些bugs能夠悄悄產生,而這些bugs能夠在編譯的時候被更型別化的語言(比如Java)所捕獲。即使更長遠看,你可能需要強化更複雜的,在資料進出的時候能個性化檢查。裝飾器能讓你易於處理所有這些,並能一次性地應用它到很多函式上。


假設:你有一堆函式,每個函式都傳回一個字典,這個字典包含一個稱作“summary”的欄位。這個欄位的值不能超過80個字元長度;如果違反了,就是不對的。這裡給出一個裝飾器,當條件不滿足的時候它能夠丟擲一個值錯誤(ValueError),如下:

def validate_summary(func):
       def wrapper(*args, **kwargs):
           data = func(*args, **kwargs)
           if len(data["summary"]) > 80:
               raise ValueError("Summary too long")
           return data
       return wrapper

   @validate_summary
   def fetch_customer_data():
       # ...

   @validate_summary
   def query_orders(criteria):
       # ...

   @validate_summary
   def create_invoice(params):
       # ...

建立框架


一旦你掌握了裝飾器的程式設計,你將能夠受益於使用裝飾器的簡單語法,而這讓你增加語意給你的程式碼以便容易使用它。這就是下一個能夠擴充套件Python自身語法的最好的工具。


實際中,很多流行的開源框架都在使用裝飾器。網頁應用框架Flask就使用了裝飾器將URLs的路由交給那些處理HTTPS請求的函式。

# For a RESTful todo-list API.
   @app.route("/tasks/", methods=["GET"])
   def get_all_tasks():
       tasks = app.store.get_all_tasks()
       return make_response(json.dumps(tasks), 200)

   @app.route("/tasks/", methods=["POST"])
   def create_task():
       payload = request.get_json(force=True)
       task_id = app.store.create_task(
           summary = payload["summary"],
           description = payload["description"],
       )
       task_info = {"id": task_id}
       return make_response(json.dumps(task_info), 201)

   @app.route("/tasks//")
   def task_details(task_id):
       task_info = app.store.task_details(task_id)
       if task_info is None:
           return make_response("", 404)
       return json.dumps(task_info)

在這裡,你有一個被叫做app的全域性的物件,它有一個被稱作route(路由)的方法並接受特定引數。這個路由方法傳回一個被應用到處理函式的裝飾器。在這個“面罩”下發生了一些很錯綜複雜的的事情,但是從Flask的使用者角度看,所有這些複雜性是完全被隱藏起來的了。


以這樣的方式使用裝飾器在stock Python中也有體現。舉個例子,完全使用物件系統是有賴於@classmethod和@property裝飾器的:

class WeatherSimulation:
       def __init__(self, **params):
            self.params = params

       @classmethod
       def for_winter(cls, **other_params):
           params = {"month": "Jan", "temp": "0"}
           params.update(other_params)
           return cls(**params)

       @property
       def progress(self):
           return self.completed_iterations() / self.total_iterations()

這個類有3個不同的定義宣告。但是,他們的語意是各不相同的。


1:constructor是一個正常方法

2:for_winter是一個類方法且提供一種類似於“車間”的東西

3:progess是隻讀、動態屬性


對於日常來說,@classmethod和@property兩個裝飾器如此簡單以致可以很容易擴充套件Python的物件語意


復用那些不可能復用的程式碼


Python提供給你一些很強大的工具用以封裝程式碼為一個易用的形式,並帶有充分的函式表示語法,支援函式式程式設計以及全面的物件系統。但是,裝飾器也有它所不能捕獲的某些形式的程式碼復用。


比如使用一個不可靠的API。你給那些透過HTTP對話的JSON發出一些請求的時候,API可以99.9%的時候工作正常。但是,有一小部分請求將使得伺服器傳回一個內部錯誤,然後你需要重試這些請求。在這個情況下,你將寫一個重試邏輯,比如:

resp = None
   while True:
       resp = make_api_call()
       if resp.status_code == 500 and tries < MAX_TRIES:
           tries += 1
           continue
       break
   process_response(resp)

現在,假設你有十多個類似於make_api_call的函式,並且他們被所有程式碼呼叫。那麼你是想要每次呼叫它們的時候寫一個while迴圈呢?還是每次增加一個API呼叫函式的時候都把這段程式碼再寫一遍?無論哪種選擇都會產生大量的重覆程式碼,除非你用裝飾器。用了裝飾器事情就簡單了。

# 加了裝飾器的函式會傳回一個Response物件,
# 這個物件有個一二status_code的屬性,
# 200表示成功;500表示伺服器錯誤。

def retry(func):
   def retried_func(*args, **kwargs):
       MAX_TRIES = 3
       tries = 0
       while True:
           resp = func(*args, **kwargs)
           if resp.status_code == 500 and tries < MAX_TRIES:
               tries += 1
               continue
           break
       return resp
   return retried_func

上述例子可以讓你方便使用裝飾器@retry

@retry
   def make_api_call():
       # ....

提升你的職業生涯


編寫裝飾器在一開始並不容易。它雖然不像火箭科學但是也需要你花很多努力去學習,去排除一些細微差異。很多開發者也從來不會透過這些麻煩而學習掌握裝飾器編寫。但是學習裝飾器的確會給你優勢。當你是你的團隊裡面學習如何寫好裝飾器的那個人的時候,並且你寫的裝飾器能解決一些實際問題的時候,其他開發者將會使用你的裝飾器。因為,一旦這些裝飾器編寫的困難的部分被完成了,裝飾器就會很容易使用。這就對你所寫的程式碼產生極大的正面作用。這也會讓你成為一個重要角色。


不論你如何編寫裝飾器,你會對下麵你所要做的事情而感到興奮,比如你即將能使用裝飾器來做一些事情,以及裝飾器是如何能永遠改變你寫Python程式碼的方式。

英文原文:https://www.oreilly.com/ideas/5-reasons-you-need-to-learn-to-write-python-decorators  
譯者:gvicky  

《Python人工智慧和全棧開發》2018年07月23日即將在北京開課,120天衝擊Python年薪30萬,改變速約~~~~

*宣告:推送內容及圖片來源於網路,部分內容會有所改動,版權歸原作者所有,如來源資訊有誤或侵犯權益,請聯絡我們刪除或授權事宜。

– END –


更多Python好文請點選【閱讀原文】哦

↓↓↓

贊(0)

分享創造快樂