閉包,裝飾器

一、什麼是閉包:

在嵌套函數中,內部函數用到了外部函數的變量,則稱內部函數爲閉包編程

python中的閉包從表現形式上定義(解釋)爲:若是在一個內部函數裏,對在外部做用域(但不是在全局做用域)的變量進行引用,那麼內部函數就被認爲是閉包(closure).緩存

造成閉包的條件:
通俗來講:就是定義在某一個函數內部的函數閉包

閉包例子:app

# ENV>>> Python 3.6
    # NO.1
    def line_conf(a, b):
        def line(x):
            return a * x + b
        return line
    
    # NO.2
    def line_conf():
        a = 1
        b = 2
 
        def line(x):
            print(a * x + b)
        return line
 
    # NO.3
    def _line_(a,b):
        def line_c(c):
            def line(x):
                return a*(x**2)+b*x+c
            return line
        return line_c

閉包的做用: 保存函數的狀態信息,使函數的局部變量信息依然能夠保存下來函數

二、裝飾器

在說裝飾器以前,咱們先說⼀個軟件設計的原則: 開閉原則, ⼜被成爲開放封閉原則。工具

開放封閉原則是指對擴展代碼的功能是開放的,可是對修改源代碼是封閉的。這樣的軟件設計思路能夠保證咱們更好的開發和維護咱們的代碼。性能

python中的裝飾器就是對一個函數調用先後增長一些新的代碼測試

裝飾器就是在不修改被裝飾對象源代碼與調用方式的前提下,爲被裝飾對象添加新功能的工具設計

不改變調用方式

裝飾器本質上是一個函數,他可讓其餘函數在不須要作任何代碼處理的前提下增長額外的功能,裝飾器的返回值也是一個函數對象。它常常用於有切面需求的場景,好比:插入日誌、性能測試、事務處理、緩存、權限校驗等場景,裝飾器是解決這類問題的絕佳設計。有了裝飾器,咱們就能夠抽離出大量與函數功能自己無關的雷同代碼到裝飾器中並繼續重用。歸納的講,裝飾器的做用就是爲已經存在的對象添加額外的功能。

def use_logging(func):
 
    def wrapper():
        logging.warn("%s is running" % func.__name__)
        return func()   # 把 foo 當作參數傳遞進來時,執行func()就至關於執行foo()
    return wrapper
 
def foo():
    print('i am foo')
 
foo = use_logging(foo)  # 由於裝飾器 use_logging(foo) 返回的是函數對象 wrapper,這條語句至關於  
                        # foo = wrapper
foo()                   # 執行foo()就至關於執行 wrapper()

use_logging 就是一個裝飾器,它一個普通的函數,它把執行真正業務邏輯的函數 func 包裹在其中,看起來像 foo 被 use_logging 裝飾了同樣,use_logging 返回的也是一個函數,這個函數的名字叫 wrapper。在這個例子中,函數進入和退出時 ,被稱爲一個橫切面,這種編程方式被稱爲面向切面的編程

有了 @ ,咱們就能夠省去foo = use_logging(foo)這一句了,直接調用 foo() 便可獲得想要的結果。大家看到了沒有,foo() 函數不須要作任何修改,只需在定義的地方加上裝飾器,調用的時候仍是和之前同樣,若是咱們有其餘的相似函數,咱們能夠繼續調用裝飾器來修飾函數,而不用重複修改函數或者增長新的封裝。這樣,咱們就提升了程序的可重複利用性,並增長了程序的可讀性。

裝飾器在 Python 使用如此方便都要歸因於 Python 的函數能像普通的對象同樣能做爲參數傳遞給其餘函數,能夠被賦值給其餘變量,能夠做爲返回值,能夠被定義在另一個函數內。

*args、**kwargs

可能有人問,若是個人業務邏輯函數 foo 須要參數怎麼辦?好比:

def foo(name):
    print("i am %s" % name)

帶參數的裝飾器

裝飾器還有更大的靈活性,例如帶參數的裝飾器,在上面的裝飾器調用中,該裝飾器接收惟一的參數就是執行業務的函數 foo 。裝飾器的語法容許咱們在調用時,提供其它參數,好比@decorator(a)。這樣,就爲裝飾器的編寫和使用提供了更大的靈活性。好比,咱們能夠在裝飾器中指定日誌的等級,由於不一樣業務函數可能須要的日誌級別是不同的。

def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warn":
                logging.warn("%s is running" % func.__name__)
            elif level == "info":
                logging.info("%s is running" % func.__name__)
            return func(*args)
        return wrapper

    return decorator

@use_logging(level="warn")
def foo(name='foo'):
    print("i am %s" % name)

foo()

類裝飾器

沒錯,裝飾器不只能夠是函數,還能夠是類,相比函數裝飾器,類裝飾器具備靈活度大、高內聚、封裝性等優勢。使用類裝飾器主要依靠類的__call__方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。

class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self):
        print ('class decorator runing')
        self._func()
        print ('class decorator ending')

@Foo
def bar():
    print ('bar')

bar()

@語法糖:

@ 符號就是裝飾器的語法糖,它放在函數開始定義的地方,這樣就能夠省略最後一步再次賦值的操做。

def use_logging(func):

    def wrapper():
        logging.warn("%s is running" % func.__name__)
        return func()
    return wrapper

@use_logging
def foo():
    print("i am foo")

    foo()

疊加裝飾器

在同一個被裝飾對象中,添加多個裝飾器,並執行

@裝飾1
@裝飾2
@裝飾3
def 被裝飾對象():
    pass

裝飾器在調用 被裝飾對象時 纔會執行添加的功能

疊加裝飾器:

  • 裝飾的順序:由下到上裝飾
  • 執行的順序:由上往下
注意: 不管inner中出現任何判斷,最後都要返回「調用後的被裝飾對象」 func(*args, **kwargs)

需求:爲裝飾對象添加統計時間 與 登錄認證功能

import time
user_info = {
    'urer':None
}

#登錄功能
def login():
    username = input("請輸入帳號:").strip()
    password = input("請輸入密碼:").strip()
    with open('user.txt', 'r', encoding='utf-8') as f:
        print(line)
            name, pwd = line.strip('\n').split(':')  # [tank, 123]
            
    if username == name and password == pwd:
        print('登陸成功!')
        user_info['user'] = username
        return True
    else:
        print('登錄失敗!')
        return False
# 登陸認證裝飾器
def login_auth(func):  # func---》 download_movie
    def inner1(*args, **kwargs):

        '''
        注意: 不管inner中出現任何判斷,
        最後都要返回「調用後的被裝飾對象」 func(*args, **kwargs)
        '''

        # 登陸認證
        if user_info.get('user'):
            res = func(*args, **kwargs)
            return res

        else:
            flag = login()
            # 添加用戶是否登陸判斷
            if flag:
                res = func(*args, **kwargs)
                return res
            else:
                login()
                return func(*args, **kwargs)

    return inner1
# 統計時間裝飾器
def time_record(func):
    def inner2(*args, **kwargs):
        print('開始統計...')
        start_time = time.time()
        res = func(*args, **kwargs)
        end_time = time.time()
        print(f'消耗時間爲: {end_time - start_time}')
        return res
    return inner2
# 下載電影功能
'''
    - 疊加裝飾器:
        - 裝飾的順序: 由下到上裝飾
        - 執行的順序: 由上往下
'''
@time_record  # inner2 = time_record(inner1地址)
@login_auth  # inner1 = login_auth(download_movie)
def download_movie():
    print('正在下載電影...')
    time.sleep(2)
    print('下載電影完成...')
    return 'GTWZ.mp4

# login()
# 執行的順序: 先執行time_record功能,再執行login_auth功能
# 統計登陸時間 + 下載時間
# download_movie()
# 裝飾順序
# @login_auth  # inner1 = login_auth(inner2)
# @time_record  # inner2 = time_record(download_movie)
# def download_movie():
#     print('正在下載電影...')
#     time.sleep(2)
#     print('下載電影完成...')
#     return 'GTWZ.mp4'


# 執行順序:
# 先執行login_auth, 再執行time_record
# 只統計下載電影的時間
# login()  # 先調用登陸,模擬用戶已登陸
download_movie()
相關文章
相關標籤/搜索