1. 程式人生 > >Python基礎--函數進階與裝飾器

Python基礎--函數進階與裝飾器

python 函數 裝飾器

函數作用域
python函數運行的時候,會創建自己的scope,即作用域(或者說函數有自己的namespace,即命名空間)。執行函數時,如果在函數體中遇到了變量名,python首先會在該函數的namespace內尋找該變量。如果找不到就會跳出函數在全局找(如果是嵌套函數就會找上一級函數,依次往上找,然後是在內建中找,在找不到就會報錯)。

首先定義一個簡單的函數
def test():
a = 1
print(1)

test()
技術分享圖片
如果在外面打印a會怎樣那?
技術分享圖片
效果
技術分享圖片

說明a的作用域存在於函數內
如果在外面定義一個a 函數內部是可以使用的
技術分享圖片

在函數內部可以修改a的值,但是無法改變外面的值
技術分享圖片
其實函數沒有改變外面的值,只是在函數內部重新創建了一個和外部變量一樣的值

把地址打印出來可以證明
a = 1
def test():
a = 2
print(a)
print(id(a))
test()
print(a)
print(id(a))
技術分享圖片

地址是不同的

如果在函數內使用的外部變量,那麽就是直接使用的外部變量(不會創建新的變量)
技術分享圖片

全局變量是在整個py文件中聲明,全局範圍內都可以訪問
局部變量是在某個函數中聲明的,只能在該函數中調用它,如果試圖在超出範圍的地方調用,就會報錯。
函數可以調用全局變量,(就是直接使用的全局變量)
如果局部變量和全局變量一樣,那麽可以在函數內部對全局變量修改,(僅限於函數內部,對外部不起作用),這種情況其實是在內部重新創建的臨時變量只不過和全局變量一樣,本質上沒有對全局變量產生任何影響。

函數基礎可以參考
http://blog.51cto.com/linuxubuntu/2085265
函數的作用域也叫局部作用域,它的範圍是整個函數,出了函數不起作用

作用域鏈
name = "Jack"
def f1():
name = "Mike"
def f2():
name = "Tom"
print(name)
f2()
print(name)
f1()
print(name)
#執行過程為:調用f1()函數,定義name 和 f2()函數,
#執行f2()函數,打印f2()中的name
#然後打印f1()中的name,最後打印全局變量的name
技術分享圖片

技術分享圖片

Python中有作用域鏈,

變量會由內到外找,先去自己作用域去找,
自己沒有再去上級去找,直到找不到報錯
例如:
把內部變量去掉後
name = "Jack"
def f1():
#name = "Mike"
def f2():
#name = "Tom"
print(name)
f2()
print(name)
f1()
print(name)

技術分享圖片

技術分享圖片

打印地址
技術分享圖片

技術分享圖片

可以看出全部是全局變量的地址
類似
def f1():
name = "Jack"
def f2():
print(name)
f2()
f1()
叫做嵌套函數,就是一個函數裏面有一個或多個函數
函數可以使用外部變量但是無法修改外部變量,外部變量也無法修改函數內部的變量。
如果想在外部對函數內部的數據修改要怎麽做,可以在內部返回函數地址,進行修改
函數內部的函數在修改完數據後直接返回地址,這樣內存中的數據不會被回收,達到在外部調用修改的目的

正常是這樣
def f1():
b = 1
def f2(b):
b += 1
print(b)
f2(b)
f1()
f1()
f1()
f1()
f1()
技術分享圖片
技術分享圖片
對代碼修改後:
def f1():
def f2(b):
b += 1
return b
return f2
print(f1())
print(f1()(1))
技術分享圖片
技術分享圖片
可以看到調用f1()得到的是一個函數地址
可以把f1()賦值給一個變量,然後執行
這就是簡單的閉包
閉包就是你調用了一個函數A,這個函數A返回了一個函數B給你。這個返回的函數B就叫做閉包。你在調用函數A的時候傳遞的參數就是自由變量。
這裏面調用f1的時候就產生了一個閉包——f2,並且該閉包持有自由變量——b,因此這也意味著,當函數f1的生命周期結束之後,b這個變量依然存在,因為它被閉包引用了,所以不會被回收。

裝飾器

裝飾器本質上是一個Python函數,就是在不改變原來函數的情況下新增功能。
例如:
冬天為了禦寒穿上棉衣(基本需求),為了加強保暖效果可以帶上帽子防風眼鏡手套等配置(新增需求)
#給下面函數加一項輸入的功能
def f1():
print(‘f1‘)
def f2():
print(‘f2‘)
def f3():
print(‘f3‘)
第一種方式:
每個函數裏加入同樣的代碼,效率太差
第二種方式:
將新功能封裝成函數在每個函數下調用,效率提高了,但是違反開放封閉原則
開放封閉原則,其核心的思想是:
軟件實體應該是可擴展,而不可修改的。也就是說,對擴展是開放的,而對修改是封閉的。
因此,開放封閉原則主要體現在兩個方面:
對擴展開放,意味著有新的需求或變化時,可以對現有代碼進行擴展,以適應新的情況。
對修改封閉,意味著類一旦設計完成,就可以獨立完成其工作,而不要對類進行任何修改。
簡單的說就是
封閉:已實現的功能代碼塊不做修改
開放:對現有代碼進行擴展
在python 中一切皆對象,所以在Python中,函數本身也是對象,在全局域,函數對象被函數名引用著,因此,完全可以用其他變量名引用這個函數對象,相當於改個名字。
比如:
def test():
print("test")
調用時可以直接調用
test()
也可fu=test
fun()
這樣調用
技術分享圖片
def test():
print("test")
fu = test
fu()

既然函數名可以賦值給變量,那麽可不可以把賦值過得變量當做參數傳遞給函數那,答案是可以的

把代碼改一下
def msg(fun):
fun()
inputword = input("請輸入信息:")
print("輸出信息為:",inputword)

def f1():
print(‘f1‘)

msg(f1)

技術分享圖片
調用後正常輸出f1函數的功能
技術分享圖片
這樣功能可以加上了,不過改變了調用方式
上面調用的是f1()這裏改為msg(f1),
既然把函數賦值給變量可以加個括號可以運行,那麽能不能把msg的地址重新賦值給變量那?通過上面的閉包我們可以知道是可以的

把代碼改一下
def msg(fun):
def func():
fun()
inputword = input("請輸入信息:")
print("輸出信息為:",inputword)
return func

def f1():
print(‘f1‘)

f1 = msg(f1)
f1()#這樣調用後就可以正常執行,其實這裏執行的是func函數

技術分享圖片

技術分享圖片
技術分享圖片
這樣就實現的功能並且不用改變調用方式

這樣就實現了裝飾器,不過在python中用語法糖@來實現實現方式為:
def msg(fun):#在調用f1時執行過程為 msg(fun)相當於msg(f1) fun相當於f1
def func():
fun()
inputword = input("請輸入信息:")
print("輸出信息為:",inputword)
return func@msg
br/>@msg
print(‘f1‘)

#f1 = msg(f1)

f1()#這樣調用後就可以正常執行,其實這裏執行的是func函數
技術分享圖片
技術分享圖片

打印出地址
技術分享圖片

技術分享圖片

可以看出func的地址和f1的地址一樣

如果被裝飾的函數有參數怎麽辦那?那就在裏面的函數加上參數
def msg(fun):
def func(arg):
fun(arg)
inputword = input("請輸入信息:")
print("輸出信息為:",inputword)
return func@msg
br/>@msg
print(arg)
f1(‘hello world‘)
技術分享圖片
技術分享圖片
如果是多個參數就要用到*args and **kwargs

def dec(fun):
def warpper(*args, *kwargs):
fun(
args, **kwargs)
print("通用裝飾器案例")
return warpper@dec
br/>@dec
print(arg1,arg2)
f1([1,2,3],{‘a‘:"A",‘b‘:"b"})

技術分享圖片

技術分享圖片

Python基礎--函數進階與裝飾器