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-2、 def func1(): name = '番薯' def func2(): print(name) # 引用外層函數的變量,造成閉包 func2() print(func2.__closure__) func1() 2-3、 def 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())