1. 程式人生 > >Python學習筆記五函數式編程(二)

Python學習筆記五函數式編程(二)

lis 運行 ast 計數 med tro append 避免 如果

參考教程:廖雪峰官網https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000

一、返回函數

高階函數除了可以接受函數作為參數之外,還可以把函數作為返回值。

通常我們也可以通過以下方式求和:

def calc_sum(*args):
    sum=0
    for n in args:
        sum=sum+n
    return sum

但如果有一種情況 ,不需要立刻得出求和結果,而是在後續的代碼中根據需要再計算,這種情況不返回求和的結果,而是返回求和的函數:

def lazy_sum(*args):
    
def funcsum(): sum=0 for n in args: sum=sum+n return sum return funcsum x=lazy_sum(1,3,5) #返回值賦給x,x是一個代入(2,3,5)元組局部變量的函數funcsum() #通過輸出可以看出x是一個函數 print(x) #需要調用x()時候才輸出求和結果 print(x())

對於上例,調用lazy_sum()時候傳入的參數(1,3,5)成為了新創建的funcsum()函數的內部變量,這樣的程序結構也稱為“閉包(Closure)”。

需要註意,即便傳入同樣的參數,返回的函數也是不同的:

x1=lazy_sum(1,3,5)
x2=lazy_sum(1,3,5)
print(x1==x2)  #False

需要註意的是,返回函數並不立即執行,直到需要調用它的時候才執行,根據執行的時候各個參數和變量的值輸出結果,看下面的例子:

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs   #返回一個函數列表

f1, f2, f3 
= count() fx=count() print(f1()) print(f2()) print(f3()) print(fx[0]()) print(fx[1]()) print(fx[2]())

以上輸出全為9,是因為在生成(返回)f1,f2,f3和函數列表fx的時候,它們內部的變量是i,而在執行的時候i已經變成了3。所以在使用閉包時需要牢記:返回函數不要引用任何循環變量,或者後續會發生變化的變量。

如果一定要使用循環,那只有再創建一個函數,把循環變量綁定到其參數上,這樣的綁定在循環過程中就實現了,可以保證循環後也不變:

def mycount():
    def f(j):
        def g():
            return j*j
        return g
    fs=[]
    for i in range(1,4):
        #此時i在循環中已經把值傳遞給g函數的參數j了
        fs.append(f(i))
    return fs

myf=mycount()
print(myf[0]())
print(myf[1]())
print(myf[2]())

練習:

#利用閉包返回一個計數器函數,每次調用它返回遞增整數
def createCounter():
    #註意這裏使用的變量是一個列表,這是避免一個報錯bug
    mycounter=[0]
    def counter():
        mycounter[0]=mycounter[0]+1
        return mycounter[0]
    return counter

‘‘‘
python3中也可以用nonlocal先聲明mycounter
這樣就可以避免報錯
def createCounter():
    mycounter=0
    def counter():
        nonlocal mycounter
        mycounter=mycounter+1
        return mycounter
    return counter
‘‘‘

tester=createCounter()
while input()!=3:
    print(tester())
    
# 測試:
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
    print(測試通過!)
else:
    print(測試失敗!)   

二、匿名函數

在Python中有時候不需要顯示定義函數,這樣也可以避免給函數起名的煩惱。

#關鍵字lambda就表示匿名函數,冒號前面的x是函數的參數
#返回值是x*x
f=lambda x:x*x
f(5)  #返回25

上面代碼中的lambda表達式等同於下面代碼:

def f(x):
    return x*x

一個lambda表達式在map()中的應用:

la=list(map(lambda x:x**2+1,[1,2,3,4,5]))
print(la)
#輸出[2,5,10,17,26]

練習:

#請用匿名函數改造下面的代碼:
def is_odd(n):
    return n % 2 == 1

L = list(filter(is_odd, range(1, 20)))

L1=list(filter(lambda x:x%2==1,range(1,20)))

print(L)
print(L1)

三、裝飾器

某些情況下,我們需要增強若幹個函數的功能,比如打印日誌,但又不希望改動函數內部的代碼,則有一種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)

例如,定義一個能打印日誌的裝飾器:

def log(func):
    def wrapper(*args,**kw):
        print(call %s % func.__name__)
        return func(*args,**kw)
    return wrapper

@log
def add(a,b):
    return a+b

print(add(3,6))

輸出結果如下:

call add
9

註意到通過‘@‘符號在函數定義前增加了語句,即給該函數增加了一個裝飾器功能。相當於執行了語句:now=log(add)。log(add)返回的是一個函數,該函數的功能不僅會運行函數本身,還運行了裝飾器的代碼。但這裏now變量指向了新的函數,即wrapper。

如果要在裝飾器中本身需要傳入參數,則需要編寫一個返回decorator的高階函數:

def log(text):
    def decorator(func):
        def wrapper(*args,**kw):
            print(%s %s(): % (text,func.__name__))
            return func(*args,**kw)
        return wrapper
    return decorator

@log(haha)#註意這裏參數的用法
def add(a,b):
    return a+b

print(add(3,9))
print(add.__name__)

我們可以發現,最後輸出的函數名變成了裝飾器內部返回的函數名了,為避免一些依賴函數名的代碼執行錯誤,可以通過Python內置的functools.wraps:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args,**kw):
        print(call %s: % func.__name__)
        return func(*args,**kw)
    return wrapper

@log
def add(a,b):
    return a+b

print(add(3,9))
print(add.__name__)

練習:

#請設計一個decorator,它可作用於任何函數上,並打印該函數的執行時間:
#-*- coding: utf-8 -*-
import time, functools

def metric(fn):
    @functools.wraps(fn)  #保持原始函數名
    def timedeco(*args,**kws):
        start_time=time.time()
        print(START TIME:+time.asctime(time.localtime(start_time)))
        x=fn(*args,**kws)
        end_time=time.time()
        print(END TIME:+time.asctime(time.localtime(end_time)))
        print(%s executed in %s ms % (fn.__name__, str(end_time-start_time)))
        return x
    return timedeco
        
# 測試
@metric
def fast(x, y):
    time.sleep(1.0012)
    return x + y;

@metric
def slow(x, y, z):
    time.sleep(1.1234)
    return x * y * z;

f = fast(11, 22)
print(f)
print(\n\n\n)
s = slow(11, 22, 33)
print(s)

if f != 33:
    print(測試失敗!)
elif s != 7986:
    print(測試失敗!)       
    

Python學習筆記五函數式編程(二)