[Advanced Python] 13 - "Decorator": syntactic sugar

單獨一章複習:Python 函數裝飾器廖雪峯講裝飾器html

 

基本概念


函數做爲變量

從函數中返回函數

子函數寫在外面貌似也能夠,可這樣就少了「封裝性」。python

def greet():
    return "now you are in the greet() function"

def welcome():
    return "now you are in the welcome() function"


def hi(name="yasoob"): 
    if name == "yasoob":
        return greet
    else:
        return welcome
 
a
= hi()
print(a) #outputs: <function greet at 0x7f2143c01500> print(a()) #outputs: now you are in the greet() function

 

將函數做爲參數傳給另外一個函數

def hi():
    return "hi yasoob!"
 
def doSomethingBeforeHi(func):
    print("I am doing some boring work before executing hi()")
    print(func())
 
doSomethingBeforeHi(hi)
#outputs:I am doing some boring work before executing hi()
#        hi yasoob!

 

 

你的第一個「裝飾器」

傳統方法

a_function_requiring_decoration經過a_new_decorator得到了加強版的本身。git

def a_new_decorator(a_func):
 
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")
 
        a_func()  print("I am doing some boring work after executing a_func()")
 
    return wrapTheFunction def a_function_requiring_decoration():
    print("I am the function which needs some decoration to remove my foul smell")
 
a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell" a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)  # <---- 函數(A)把本身"裝飾"了一下,加強了功能 #now a_function_requiring_decoration is wrapped by wrapTheFunction() a_function_requiring_decoration() #outputs:I am doing some boring work before executing a_func() # I am the function which needs some decoration to remove my foul smell # I am doing some boring work after executing a_func()

 

@語法糖策略

寫好a_new_decorator裝飾器函數後,只要在須要「被裝飾」的函數上添加@語法糖就行了。web

如此,就省掉了上述中的 函數(A)的步驟。閉包

一篇通俗易懂的博文:這是我見過最全面的Python裝飾器詳解!沒有學不會這種說法!app

@a_new_decorator def a_function_requiring_decoration():
    """Hey you! Decorate me!"""
    print("I am the function which needs some decoration to "
          "remove my foul smell")

 

However,只是表面上完美,抽象的還不夠完全。 框架

 

 

裝飾器裝飾「有參函數」

難點

上面的例子沒有參數,若是有參數會如何?函數

並且只會寫「一個裝飾器」,因此「參數個數不定」也須要考慮進去。ui

 

裝飾更多函數

# 萬能裝飾器,修飾各類參數
def
celebrator(func):   def inner(*args, **kwargs):     print('我是新功能')     func(*args, **kwargs)   return inner
@celebrator
def myprint(a):   print(a) myprint('python小白聯盟')

 

有無返回值

def celebrator(func):
  def inner(*args, **kwargs):
    print('我是新功能')
    ret = func(*args, **kwargs)
    return ret   
return inner

裝飾的函數,若是有返回值,則一致;若是沒有返回值,則返回 None。spa

 

 

雙重語法糖

嵌套裝飾器

最外層的語法糖先執行。

 

帶參數的語法糖

使用帶參的語法糖來進行簡化;也就是再嵌套一層函數,同時也保證了「參數的獨立性」

有點相似於「閉包」的如下這個例子,保持了子函數的參數的獨立性。

def count():
-------------------------
def f(j):          # 由於通過了函數的封裝,因此保持了函數內部變量的獨立性 def g(): return j*j return g ------------------------- fs = [] for i in range(1, 4): fs.append(f(i))    # f(i)馬上被執行,所以i的當前值被傳入f() return fs f1, f2, f3 = count()

 

 

functools.wraps

以前帶來的問題

以下可見,返回的函數名字是裝飾器中的wrap函數的名字,這是個封裝缺陷。

print(a_function_requiring_decoration.__name__)
# Output: wrapTheFunction

 

解決方案

經過wraps告訴wrap函數:「你的扮演人物的身份是誰」。

注意:@wraps接受一個函數來進行裝飾,並加入了複製函數名稱、註釋文檔、參數列表等等的功能。這可讓咱們在裝飾器裏面訪問在裝飾以前的函數的屬性。

from functools import wraps def a_new_decorator(a_func):
    @wraps(a_func) 
def wrapTheFunction(): print("I am doing some boring work before executing a_func()") a_func() print("I am doing some boring work after executing a_func()") return wrapTheFunction @a_new_decorator def a_function_requiring_decoration(): """Hey yo! Decorate me!""" print("I am the function which needs some decoration to " "remove my foul smell") print(a_function_requiring_decoration.__name__) # Output: a_function_requiring_decoration

 

 

 

使用場景


兩個案例

受權(Authorization)

裝飾器能有助於檢查某我的是否被受權去使用一個web應用的端點(endpoint)。它們被大量使用於Flask和Django web框架中。

這裏是一個例子來使用基於裝飾器的受權:

from functools import wraps
 
def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            authenticate()
        return f(*args, **kwargs)
    return decorated

 

日誌(Logging)

日誌是裝飾器運用的另外一個亮點。這是個例子:

from functools import wraps
 
def logit(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging
 
@logit
def addition_func(x):
   """Do some math."""
   return x + x
 
 
result = addition_func(4)
# Output: addition_func was called

 

 

裝飾器類

【在類的章節再詳解】

咱們的應用的某些部分還比較脆弱時,異常也許是須要更緊急關注的事情。比方說有時你只想打日誌到一個文件。而有時你想把引發你注意的問題發送到一個email,同時也保留日誌,留個記錄。這是一個使用繼承的場景,但目前爲止咱們只看到過用來構建裝飾器的函數。

幸運的是,類也能夠用來構建裝飾器。那咱們如今以一個類而不是一個函數的方式,來從新構建logit。

一個裝飾類例子

from functools import wraps
 
class logit(object):
    def __init__(self, logfile='out.log'):
        self.logfile = logfile
 
    def __call__(self, func):
        @wraps(func)
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打開logfile並寫入
            with open(self.logfile, 'a') as opened_file:
                # 如今將日誌打到指定的文件
                opened_file.write(log_string + '\n')
            # 如今,發送一個通知
            self.notify()
            return func(*args, **kwargs)
        return wrapped_function
 
    def notify(self):
        # logit只打日誌,不作別的
        pass

 

添加email功能

如今,咱們給 logit 建立子類,來添加 email 的功能。

class email_logit(logit):
    '''
    一個logit的實現版本,能夠在函數調用時發送email給管理員
    '''
    def __init__(self, email='admin@myproject.com', *args, **kwargs):
        self.email = email
        super(email_logit, self).__init__(*args, **kwargs)
 
    def notify(self):
        # 發送一封email到self.email
        # 這裏就不作實現了
        pass

 

 

End.

相關文章
相關標籤/搜索