一、python函數作用域LEGB
python解釋器查找變量的原則(順序):
L→E→G→B
L:Local函數內部作用域
E:enclosing函數內部與內嵌函數之間
G:gobal全局作用域
B:build-in內置作用域
example:
value1 = 5
def my_func():
value2 = 6
print(id(value2))
def in_func():
a = max(value1, value2)
print(a)
return in_func
上面示例代碼中max函數為python的內建函數,在in_func
函數中,max
這個變量python解釋器首先會在in_func
這個函數中查找,即local函數內部作用域中查找,然后按順序再到E-G-B查找,最后在build-in內置作用域查找到。
二、閉包
1、概念:內部函數中對enclosing作用域的變量進行引用
上述例子中my_func
函數中的內部函數in_func
引用了enclosing作用域中的value2
變量,這就叫做閉包。
由于函數執行完后變量會被回收,當執行完my_func
這個函數后,value2
變量會被回收。那么如果我們再次調用in_func
這個函數時,需用引用到value2
這個變量,是否會報錯?
看以下代碼:
example:
value1 = 5
def my_func():
value2 = 6
print(id(value2)) #打印value2的ID值
def in_func():
a = max(value1, value2)
print(a)
return in_func
f = my_func() #my_func返回的是in_func,此時f指向in_func函數
f() #f()相當于in_func(),調用了infunc函數
print(f.__closure__)
ouput:
501351024
6
(<cell at 0x0000000000AF8D98: int object at 0x000000001DE20270>,)
如果內部函數引用了enclosing作用域的變量,會將變量添加到函數__closure__
的屬性中去。當再次查找這個變量時,會直接去函數__closure__
的屬性中查找。我們可以看到代碼的輸出結果第一行即為value2
的內存地址(501351024轉換為16進制:1DE20270)和__closure__
屬性中的int對象的地址是一樣的( int object at 0x000000001DE20270)。
2、那么閉包到底有什么用呢?
我們來看下以下兩段代碼:
(1)、
def func_100(value):
passline = 60
if value >= passline:
print('pass')
else:
print('failed')
def func_150(value):
passline = 90
if value >= passline:
print('pass')
else:
print('failed')
func100(59)
func150(89)
(2)、
def set_passline(passline):
def in_func(value):
if value >= passline:
print('pass')
else:
print('failed')
return in_func
f_100 = set_passline(60) #passline=60被存儲在f_100的__closure__屬性中
f_150 = set_passline(90) #passline=90被存儲在f_150的__closure__屬性中
f_100(59)
f_150(89)
兩段代碼都能正確判斷滿分是100或150時,分數是否及格。但是第二段代碼,由于運用閉包,代碼復用性更高。
3、閉包更高級的應用
將閉包的概念中的變量變成函數,同樣適用。即:內部函數中對enclosing作用域的函數進行引
example:
def my_sum(*args):
return(sum(args))
def my_average(*args):
return sum(args)/len(args)
def dec(func):
def in_dec(*args):
if len(args) == 0: #對參數進行判斷,如果沒有參數直接返回0
return 0
for i in args:
if not isinstance(i, int): #對參數進行判斷,如果有一個參數不是int類型,直接返回0
return 0
return func(*args) #此處對enclosing作用域的func函數進行引用
return in_dec
evo_my_sum = dec(my_sum)
evo_my_average = dec(my_average)
PS:此處求和函數(my_sum)和求平均值函數(my_average)只對參數是否int類型進行判斷。
我們來分析下代碼:evo_my_sum = dec(my_sum)
由于函數dec
返回的是in_dec
函數
所以evo_my_sum
指向的是in_dec
函數
=》evo_my_sum = in_dec
=》evo_my_sum(1, 2, 3) = in_dec(1, 2, 3)
那么in_dec(1, 2, 3)
即evo_my_sum(1, 2, 3)
會對求和的參數先進行判斷后,再調用my_sum
函數。
同樣道理:
evo_my_average
會對求平均的參數先進行判斷后,再調用my_average
函數。
這樣就可以對參數統一進行判斷后再各自調用不同的函數。
三、裝飾器
裝飾器是用來裝飾函數的,它返回一個函數對象。語法:@Decorator
現在我們已經定義了一個my_sum
函數
def my_sum(*args):
return(sum(args))
假設我們要增加my_sum
函數的功能,比如,在函數調用前對參數進行一個判斷,但又不希望修改my_sum
函數的定義,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。
我們要定義一個能判斷參數的decorator,如下:
def dec(func):
def in_dec(*args):
if len(args) == 0:
return 0
for i in args:
if not isinstance(i, int):
return 0
return func(*args)
return in_dec
按照python裝飾器的語法:
@dec
def my_sum(*args):
return(sum(args))
這樣,調用my_sum
函數,不僅會運行my_sum
函數本身,還會在運行my_sum
函數前,對參數進行判斷。
把@dec放到my_sum
函數的定義處,相當于執行了語句:
my_sum = dec(my_sum)
看到這里是否覺得有點熟悉?
其實這個語句和上述閉包的高級應用是一樣的,只不過那部分把my_sum
改成了evo_my_sum
。
裝飾器到這里還差最后一步:
my_sum
函數經過裝飾后,由于dec(my_sum)
返回的是in_dec
函數,此時my_sum.__name__
屬性將從‘my_sum’變成‘in_dec’,為了保證此屬性不變,需在in_dec
函數定義前加上語句@functools.wraps(func)
,保證my_sum.__name__
屬性不變。否則,有些依賴函數簽名的代碼執行就會出錯。一個完整的decorator的寫法如下:
import functools
def dec(func):
@functools.wraps(func)
def in_dec(*args):
if len(args) == 0:
return 0
for i in args:
if not isinstance(i, int):
return 0
return func(*args)
return in_dec
decorator可以增強函數的功能,定義起來雖然有點復雜,但使用起來非常靈活和方便。
以上是觀看慕課網《python裝飾器》以及廖雪峰教程python裝飾器的總結。