python之裝飾器

複製代碼
1、函數名的應用
1、函數名是一個特殊的變量,函數名存放的是函數的內存地址
def func():
    print('hello')


print(func)  # <function func at 0x000001A228B01E18>


2、函數名能夠做爲一個變量
def func():
    print('hello')


f = func  # 把函數名當成變量賦值給另一個變量
f()  # 經過變量f調用函數


3、函數名能夠做爲容器類型的元素
def func1():
    print('func1')
    

def func2():
    print('func2')
    
    
def func3():
    print('func3')
    
    
def func4():
    print('func4')


list1 = [func1, func2, func3, func4]
for f in list1:
    f()


4、函數名能夠做爲函數的參數
def func1():
    print('func1')


def func2(arg):
    print('func2')
    arg()  # 執行傳遞進來的arg

    
func2(func1)  # 把func1當成參數傳遞給func2


5、函數名能夠做爲函數的返回值
def func1():
    print('func1')

    def func2():
        print('func2')
    return func2  # 把func2當成返回值返回


ret = func1()  # 調用func1,把返回值賦值給ret
ret()  # 調用ret



2、閉包
1、定義
內層函數對外層函數(非全局)的變量的引用,這個內層函數就成爲閉包。
在Python中,咱們可使用__closure__來檢測函數是不是閉包。
有cell元素的是閉包。


例如:
def func():
    name = '番薯'

    def func2():
        print(name)  # 引用外層函數的變量
    func2()
    print(func2.__closure__)  # (<cell at 0x000002591EA794F8: str object at 0x000002591EACB500>)

func()
print(func.__closure__)  # None


2、閉包的例子
2-1、這裏並無引用外層函數的變量,而是把外層函數的變量傳給func2,因此不算閉包
def func1():
    name = '番薯'

    def func2(arg):
        print(arg)
    func2(name)
    print(func2.__closure__)
    
    
func1()


2-2def func1():
    name = '番薯'

    def func2():
        print(name)  # 引用外層函數的變量,造成閉包
    func2()
    print(func2.__closure__)


func1()


2-3def func1(name):
    def func2():
        print(name)  # 引用外層函數的變量,造成閉包
    func2()
    print(func2.__closure__)


func1('番薯')


3、閉包的應用
3-1、把內部函數(閉包)當成返回值返回就可使用閉包了
def func1():
    name = '番薯'

    def func2():
        print(name)

    return func2  # 把內部函數當成是返回值返回


ret = func1()  # 把返回值賦值給變量ret
ret()  # 調用內部函數


3-2、多層嵌套的閉包
def func1():
    def func2():
        def func3():
            print('func3')
        return func3
    return func2


f2 = func1()  # func2
f3 = f2()  # func3
f3()


4、閉包的好處
能夠在任什麼時候間從外界訪問到內部函數。
可是咱們知道一個函數執行完畢後,這個函數中的變量以及局部命名空間中的內容都是會被銷燬的。
在閉包中若是變量被銷燬了,那內部函數就不能正常執行了。
因此一旦這個內部函數引用了外層函數中的變量造成了閉包,那麼這個變量將不會隨着外層函數的結束而銷燬,它會在內存中保留。
也就是說,閉包能夠保留變量的引用。



3、裝飾器初識
1、開放封閉原則
軟件設計的原則: 開閉原則, 又被成爲開放封閉原則。
開放封閉原則是指對擴展代碼的功能是開放的,
可是對修改源代碼是封閉的。
這樣的軟件設計思路能夠保證咱們更好的開發和維護咱們的代碼。


好比:你開了一家老字號(下面的代碼就至關於源碼)
def old_shop():
    print('離百年老字號還差99年')


old_shop()


而後你的老店要新增空調(至關於新增功能):
def old_shop():
    print('新增空調')
    print('離百年老字號還差99年')


old_shop()

這樣添加功能確實是能夠的,可是違背了開放封閉原則,直接在源碼上進行修改了。
那怎麼辦呢?新建一個函數不就行了
def old_shop_with_conditioner():
    print('新增空調')
    print('離百年老字號還差99年')


old_shop_with_conditioner()

可是,問題又來了,你的老字號很火,開了不少分店,每家分店都是調用了以前的old_shop函數開店,
那麼你修改了以後,全部調用原來函數的分店都須要從新調用新的函數,這麼作實際上是很番薯的行爲。

那麼如何在不改變函數的結構和調用方式的基礎上,動態的給函數添加功能呢?
能夠利用閉包
def old_shop():
    print('離百年老字號還差99年')


def a(func):
    def b():
        print('新增空調')
        func()
    return b


ret = a(old_shop)  # 內層的b函數
ret()

而後問題又來了,如今雖然沒有直接修改源碼,可是函數名仍是改變了,那又怎麼辦?
重命名不就好了嗎:
def old_shop():
    print('離百年老字號還差99年')


def a(func):
    def b():
        print('新增空調')
        func()
    return b


old_shop = a(old_shop)  # 內層的b函數
old_shop()

這樣不就遵循了開發封閉原則了嗎,即沒有修改源碼,擴展代碼又是開放的,也沒有改變函數原來的調用方式


2、裝飾器語法糖
剛纔上面的代碼只是裝飾器的原理和雛形,Python中針對於上面的功能提供了一個快捷的寫法,俗稱裝飾器語法糖。
使用裝飾器語法糖的寫法,實現一樣功能的代碼以下:
def a(func):  # a是咱們定義的裝飾器函數,func是被裝飾的函數(old_shop)
    def b():
        print('新增空調')
        func()
    return b


@a    # 至關於把被裝飾的函數old_shop當成參數傳給a,而後把返回值b再從新賦值給被裝飾的函數名old_shop
def old_shop():
    print('離百年老字號還差99年')


old_shop()  # 至關於調用了內層函數b



4、裝飾器進階
1、裝飾帶返回值的函數
def wrapper(func):
    def inner():
        print('新功能')  # 你要新增的功能
        result = func()  # 拿到被裝飾函數的返回值
        return result  # 返回被裝飾的函數的返回值
    return inner


@wrapper
def f():
    return 'Hello'


ret = f()
print(ret)


2、裝飾帶參數的函數
def wrapper(func):
    def inner(x, y):  # 實際執行函數的參數
        print('新功能')
        r = func(x, y)
        return r
    return inner


@wrapper
def my_sum(x, y):
    return x + y


ret = my_sum(1, 2)  # inner(10, 20)
print(ret)

可是通常來講,咱們把參數設置成動態參數會更便於拓展
如果三個數相加,或者四個數相加,只須要修改my_sum的參數就能夠了
def wrapper(func):
    def inner(*args, **kwargs):
        print('新功能')
        r = func(*args, **kwargs)
        return r
    return inner


@wrapper
def my_sum(x, y, z):
    return x + y + z


ret = my_sum(1, 2, 3)
print(ret)


3、帶參數的裝飾器
裝飾器若是要帶參數的話,能夠嵌套更多層:
def outer(arg):
    def wrapper(func):
        def inner(*args, **kwargs):
            print('歡迎來到%s' % arg)
            func(*args, **kwargs)
        return inner
    return wrapper


@outer('英雄聯盟')  # 會先執行outer,而後返回函數名wrapper,至關於@wrapper,在閉包內還能使用最外層的函數變量
def lol():
    print('這裏是召喚師峽谷')


@outer('地下城與勇士')  # @wrapper
def dnf():
    print('這裏是阿拉德大陸')


lol()
dnf()


4、裝飾器修復技術

被裝飾的函數最終都會失去原本的__doc__等信息,就是說,若是這個函數被裝飾了,
那麼它裏面的文檔信息(註釋信息,一般註釋信息很重要,註釋寫明瞭改函數的功能和參數的信息等)就會消失,
 Python給咱們提供了一個修復被裝飾函數的工具,用於找回這些信息。
 from functools import wraps


def wrapper(func):
    @wraps(func)
    def inner(*args, **kwargs):
        print('這是新功能')
        func(*args, **kwargs)
    return inner


@wrapper
def f1(x, y):
    """
    這裏寫這個函數的主要功能
    :param x: 這個參數的類型
    :param y: 這個參數的類型
    :return: 返回值
    """
    print('我是帥哥')


print(f1.__doc__)  # 打印這個函數的文檔信息(註釋內容)
print(f1.__name__)  # 打印這個函數名



5、多個裝飾器裝飾同一函數
def wrapper1(func):
    print('w1')

    def inner1():
        print('inner1')
        return '<i>{}</i>'.format(func())
    return inner1


def wrapper2(func):
    print('w2')

    def inner2():
        print('inner2')
        return '<b>{}</b>'.format(func())
    return inner2


@wrapper1
@wrapper2
def f1():
    return "小明"


ret = f1()
print(ret)

結果:
w2
w1
inner1
inner2
<i><b>小明</b></i>

分析:
在裝飾階段會直接執行裝飾函數,並拿到返回值,即
@wrapper2  --->  wrapper2(f1)  ---> print('w2') ---> return inner2 ---> 把變量f1從新指向inner2
@wrapper1 --->  wrapper1(f1)[此時的f1其實是inner2]  ---> print('w1') ---> return inner1 ---> 把變量f1從新指向inner1
而後執行f1()至關於執行inner1()
print('inner1')
return '<i>{}</i>'.format(func())
此時的func是傳進來的參數inner2,因此又去執行inner2
print('inner2')
return '<b>{}</b>'.format(func())
此時的func是傳進來的參數f1[被裝飾的f1],因此拿到返回值<b>小明</b>,拼接到inner1的返回值那裏,最後
<i><b>小明</b></i>



5、數碼暴龍進化裝飾器
1、類裝飾器
咱們除了可使用函數裝飾函數外,還能夠用類裝飾函數。
class Page(object):
    def __init__(self, a=None):
        self.a = a
        self.mode = "裝飾"

    def __call__(self, *args, **kwargs):
        if self.mode == "裝飾":
            self.func = args[0]  # 默認第一個參數是被裝飾的函數
            self.mode = "調用"
            return self
        # 當self.mode == "調用"時,執行下面的代碼(也就是調用使用類裝飾的函數時執行)
        if self.a:
            print("歡迎來到{}頁面。".format(self.a))
        else:
            print("歡迎來到首頁。")
        self.func(*args, **kwargs)


@Page()
def index(name):
    print("Hello {}.".format(name))


@Page("電影")
def movie(name):
    print("Hello {}.".format(name))


if __name__ == '__main__':
    index('番薯')
    movie('番薯')


2、裝飾類
上面全部的例子都是裝飾一個函數,返回一個可執行函數。Python中的裝飾器除了能裝飾函數外,還能裝飾類。
可使用裝飾器,來批量修改被裝飾類的某些方法
# 定義一個類裝飾器
class D(object):
    def __call__(self, cls):
        class Inner(cls):
            # 重寫被裝飾類的f方法
            def f(self):
                print('Hello 番薯')
        return Inner


@D()
class C(object):  # 被裝飾的類
    # 有一個實例方法
    def f(self):
        print("Hello world.")


if __name__ == '__main__':
    c = C()
    c.f()

    
    
6、裝飾器小結(重)
1、裝飾器的標準結構
from functools import wraps


def wrapper(func):  # func:被裝飾的函數
    @wraps(func)  # 把func指向的函數的__doc__、__name__等屬性複製到inner上面
    def inner(*args, **kwargs):  # *args和**kwargs是被裝飾函數的參數
        print('新功能')
        r = func(*args, **kwargs)
        print('新功能也能夠在這裏')
        return r
    return inner


@wrapper  # 此時會執行wrapper,因此wrapper必須定義在這一行以前
def hello():
    print('Hello World!')


hello()


2、帶參數的裝飾器
from functools import wraps


def outer(k=None):
    def wrapper(func):
        @wraps(func)
        def inner(*args, **kwargs):
            if k == 'start':
                print('節目開始')
                r = func(*args, **kwargs)
                return r
            else:
                print('節目還未開始')
        return inner
    return wrapper


@outer('start')
def hello():
    """這裏是hello函數"""
    print('Hello World!')


hello()
print(hello.__doc__)
print(hello.__name__)


3、多個裝飾器同時裝飾一個函數
"""
給Hello World!包兩層標籤,
<div><p>Hello World!</p></div>
"""
from functools import wraps


def wrapper1(func):  # 包p標籤
    @wraps(func)
    def inner1(*args, **kwargs):
        r = func(*args, **kwargs)
        return '<p>{}</p>'.format(r)
    return inner1


def wrapper2(func):  # 包div標籤
    @wraps(func)
    def inner2(*args, **kwargs):
        r = func(*args, **kwargs)
        return '<div>{}</div>'.format(r)
    return inner2


@wrapper2
@wrapper1
def hello():
    return "Hello World!"


print(hello())










    
複製代碼
相關文章
相關標籤/搜索