高階函數(shù)
前言
變量可以指向函數(shù)
>>> f = abs
>>> f(-10)
10
函數(shù)名也是變量
>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):?
?File"<stdin>", line1, in <module>
Type Error:'int' object is not callable
傳入函數(shù)
一個(gè)函數(shù)就可以接收另一個(gè)函數(shù)作為參數(shù)庶喜,這種函數(shù)就稱之為高階函數(shù)
def add(x, y, f):
????return f(x) + f(y)
map/reduce
map
map()函數(shù)接收兩個(gè)參數(shù)椭微,一個(gè)是函數(shù),一個(gè)是Iterable牲蜀,map將傳入的函數(shù)依次作用到序列的每個(gè)元素革砸,并把結(jié)果作為新的Iterator返回
>>> def f(x):
...? ? return x*x
...
>>> r=map(f,[1,2,3,4,5,6,7,8,9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> list(map(str,[1,2,3,4,5,6,7,8,9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']
reduce
reduce把一個(gè)函數(shù)作用在一個(gè)序列[x1, x2, x3, ...]上除秀,這個(gè)函數(shù)必須接收兩個(gè)參數(shù),reduce把結(jié)果繼續(xù)和序列的下一個(gè)元素做累積計(jì)算算利。
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
>>> from functools import reduce
>>> def add(x,y):
...? ? return x+y
...
>>> reduce(add,[1,3,5,7,9])
25
把序列[1, 3, 5, 7, 9]變換成整數(shù)13579册踩,reduce用法:
>>> from functools import reduce
>>> def fn(x,y):
...? ? return x*10+y
...
>>> reduce(fn,[1,3,5,7,9])
13579
練習(xí)
第一題:
>>> def normalize(name):
...? ? a= name[0].upper()
...? ? b= name[1:].lower()
...? ? name=a+b
...? ? return name
...
>>> L1 = ['adam', 'LISA', 'barT']
>>> L2 = list(map(normalize, L1))
>>> print(L2)
['Adam', 'Lisa', 'Bart']
第二題:
>>> def prod(x,y):
...? ? return x*y
...
>>> reduce(prod,[3,5,7,9])
第三題:
?
filter
filter()函數(shù)用于過濾序列
例如效拭,在一個(gè)list中暂吉,刪掉偶數(shù),只保留奇數(shù):
>>> def is_odd(n):
...? ? return n%2==1
...
>>> list(filter(is_odd,[1,2,4,5,6,9,10,15]))
[1, 5, 9, 15]
用filter求素?cái)?shù)
計(jì)算素?cái)?shù)的一個(gè)方法是埃氏篩法缎患。
首先慕的,列出從2開始的所有自然數(shù),構(gòu)造一個(gè)序列挤渔。
取序列的第一個(gè)數(shù)2肮街,它一定是素?cái)?shù),然后用2把序列的2的倍數(shù)篩掉
取新序列的第一個(gè)數(shù)3判导,它一定是素?cái)?shù)嫉父,然后用3把序列的3的倍數(shù)篩掉
取新序列的第一個(gè)數(shù)5,然后用5把序列的5的倍數(shù)篩掉
不斷篩下去眼刃,就可以得到所有的素?cái)?shù)绕辖。
用Python來實(shí)現(xiàn)這個(gè)算法,可以先構(gòu)造一個(gè)從3開始的奇數(shù)序列:
def_odd_iter():
????n =1
????while True:?
? ? ? ? n = n +2
????????yield n
然后定義一個(gè)篩選函數(shù):
def_not_divisible(n):
????return lambda x: x % n >0
最后擂红,定義一個(gè)生成器仪际,不斷返回下一個(gè)素?cái)?shù):
def primes():
????yield 2?
????it = _odd_iter()? ? # 初始序列
? ? whileTrue:?
? ? ? ? n = next(it)? ? ? # 返回序列的第一個(gè)數(shù)
????????yield n?
? ? ? ? it = filter(_not_divisible(n), it)? ? # 構(gòu)造新序列
這個(gè)生成器先返回第一個(gè)素?cái)?shù)2,然后昵骤,利用filter()不斷產(chǎn)生篩選后的新的序列树碱。由于primes()也是一個(gè)無限序列,所以調(diào)用時(shí)需要設(shè)置一個(gè)退出循環(huán)的條件:
# 打印1000以內(nèi)的素?cái)?shù):
for n in primes():
????if n <1000:?
? ? ? ? print(n)
????else:
????????break
練習(xí)
回?cái)?shù)是指從左向右讀和從右向左讀都是一樣的數(shù)涉茧,例如12321赴恨,909。請(qǐng)利用filter()篩選出回?cái)?shù):
def?is_palindrome(n):
????return?str(n)?==?str(n)[::-1]
sorted
sorted()也是一個(gè)高階函數(shù)伴栓。用sorted()排序的關(guān)鍵在于實(shí)現(xiàn)一個(gè)映射函數(shù)伦连。
>>> sorted([36,5,-12,9,-21])
[-21, -12, 5, 9, 36]
絕對(duì)值排序:
>>> sorted([36,5,-12,9,-21],key=abs)
[5, 9, -12, -21, 36]
字符串排序雨饺,按照ASCII的大小比較的,由于'Z' < 'a'惑淳,結(jié)果额港,大寫字母Z會(huì)排在小寫字母a的前面:
>>> sorted(['bob','about','Zoo','Credit'])
['Credit', 'Zoo', 'about', 'bob']
忽略大小寫的排序:
>>> sorted(['bob','about','Zoo','Credit'], key=str.lower)
['about','bob','Credit','Zoo']
反向排序:
>>> sorted(['bob','about','Zoo','Credit'], key=str.lower,reverse=True)
['Zoo', 'Credit', 'bob', 'about']
練習(xí)
L是用tuple來表示一對(duì)名字和分?jǐn)?shù)的。
key是用來進(jìn)行比較的元素歧焦,只有一個(gè)參數(shù)移斩,具體的函數(shù)的參數(shù)就是取自于可迭代對(duì)象中,指定可迭代對(duì)象中的一個(gè)元素來進(jìn)行排序绢馍。
具體的函數(shù)的參數(shù)就是取自于可迭代對(duì)象中向瓷,指定可迭代對(duì)象中的一個(gè)元素來進(jìn)行排序。
那么我們看代碼中的t表示啥舰涌,表示的是不是可迭代對(duì)象(list)中的一個(gè)元素(tuple類型)
一個(gè)tuple的表示形式是('名字':分?jǐn)?shù))?? ('Bob': 75)
?那么t[0]和t[1]猖任,前一個(gè)名字,后一個(gè)分?jǐn)?shù)瓷耙。轉(zhuǎn)換到key中去就是按照名字排序朱躺,和按照分?jǐn)?shù)排序。
>>> L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
>>> def by_name(t):
...? ? ? return t[0]
...
>>> sorted(L, key=by_name)
>>> def by_score(t):
...? ? return -t[1]? ? ? ?#成績(jī)降序排列
...
>>> sorted(L, key=by_score)
[('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]
返回函數(shù)
函數(shù)作為返回值
通常情況下的求和:
def calc_sum(*args):
????ax =0
????for n in args:?
????????ax = ax + n
????return ax
需要再計(jì)算的話搁痛,可以不返回求和的結(jié)果长搀,而是返回求和的函數(shù):
def lazy_sum(*args):
????def sum():
????????ax =0
????????for n in args:?
????????????ax = ax + n
????????????return ax
????return sum
在函數(shù)lazy_sum中又定義了函數(shù)sum,并且鸡典,內(nèi)部函數(shù)sum可以引用外部函數(shù)lazy_sum的參數(shù)和局部變量源请,當(dāng)lazy_sum返回函數(shù)sum時(shí),相關(guān)參數(shù)和變量都保存在返回的函數(shù)中彻况。(閉包程序)
閉包
返回函數(shù)不要引用任何循環(huán)變量巢钓,或者后續(xù)會(huì)發(fā)生變化的變量!A贫狻!硫朦!
>>> def count():
...? ? fs=[]
...? ? for i in range(1,4):
...? ? ? ? ? ? def f():
...? ? ? ? ? ? ? ? ? ? return i*i
...? ? ? ? ? ? fs.append(f)
...? ? return fs
...
>>> f1,f2,f3=count()
>>> f1()
9
>>> f2()
9
>>> f3()
9
輸出結(jié)果不是1贷腕,4,9咬展。原因就在于返回的函數(shù)引用了變量i泽裳,但它并非立刻執(zhí)行。等到3個(gè)函數(shù)都返回時(shí)破婆,它們所引用的變量i已經(jīng)變成了3涮总,因此最終結(jié)果為9。
如果一定要引用循環(huán)變量怎么辦祷舀?方法是再創(chuàng)建一個(gè)函數(shù)瀑梗,用該函數(shù)的參數(shù)綁定循環(huán)變量當(dāng)前的值烹笔,無論該循環(huán)變量后續(xù)如何更改,已綁定到函數(shù)參數(shù)的值不變:
>>> def count():
...? ? def f(j):
...? ? ? ? ? ? def g():
...? ? ? ? ? ? ? ? ? ? return j*j
...? ? ? ? ? ? return g
...? ? fs=[]
...? ? for i in range(1,4):
...? ? ? ? ? ? fs.append(f(i))
...? ? return fs
...
>>> f1, f2, f3=count()
>>> f1()
1
>>> f2()
4
>>> f3()
9
作業(yè)
暫缺
匿名函數(shù)
>>> list(map(lambda x: x*x,[1,2,3,4,5,6,7,8,9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
關(guān)鍵字lambda表示匿名函數(shù)怜浅,冒號(hào)前面的x表示函數(shù)參數(shù)油湖。匿名函數(shù)只能有一個(gè)表達(dá)式长捧,不用寫return,返回值就是該表達(dá)式的結(jié)果允蜈。
匿名函數(shù)沒有名字,不必?fù)?dān)心函數(shù)名沖突蒿柳。此外饶套,匿名函數(shù)也是一個(gè)函數(shù)對(duì)象,也可以把匿名函數(shù)賦值給一個(gè)變量垒探,再利用變量來調(diào)用該函數(shù):
>>> f=lambda x: x*x
>>> f
<function <lambda> at 0x000001CE0FDCEE50>
>>> f(6)
36
也可以把匿名函數(shù)作為返回值返回妓蛮,比如:
def build(x, y):
????return lambda: x * x + y * y
作業(yè)
用匿名函數(shù)改寫:
def is_odd(n):
? ? return n % 2 == 1
L = list(filter(is_odd, range(1, 20)))
改寫后:
L=list(filter(lambda n: n%2==1, range(1, 20)))
print(L)
裝飾器
函數(shù)對(duì)象可以被賦值給變量,所以叛复,通過變量也能調(diào)用該函數(shù)仔引。
>>> def now():
...? ? print('2015-3-25')
...
>>> f=now
>>> f()
2015-3-25
函數(shù)對(duì)象有一個(gè)__name__屬性,可以拿到函數(shù)的名字:
>>> now.__name__? ? ? ? #兩個(gè)下劃線name兩個(gè)下劃線
'now'
>>> f.__name__
'now'
在代碼運(yùn)行期間動(dòng)態(tài)增加功能的方式褐奥,稱之為“裝飾器”(Decorator)
>>> def log(func):
...? ? def wrapper(*arg,**kw):
...? ? ? ? ? ? print('call %s():' % func.__name__)
...? ? ? ? ? ? return func(*arg,**kw)
...? ? return wrapper
借助Python的@語法咖耘,把decorator置于函數(shù)的定義處
>>> @log
... def now():
...? ? print('2015-3-25')
相當(dāng)于執(zhí)行了語句:now =log(now)
調(diào)用now()函數(shù),不僅會(huì)運(yùn)行now()函數(shù)本身撬码,還會(huì)在運(yùn)行now()函數(shù)前打印一行日志:
>>> now()
call now():
2015-3-25
>>> def log(text):
...? ? def decorator(func):
...? ? ? ? ? ? def wrapper(*args,**kw):
...? ? ? ? ? ? ? ? ? ? print('%s %s():' % (text, func.__name__))
...? ? ? ? ? ? ? ? ? ? return func(*args,**kw)
...? ? ? ? ? ? return wrapper
...? ? return decorator
這個(gè)3層嵌套的decorator用法如下:
>>> @log('execute')
... def now():
...? ? print('2015-3-25')
執(zhí)行結(jié)果如下:
>>> now()
execute now():
2015-3-25
此時(shí):
>>> now.__name__
'wrapper'
import functools
>>> import functools
>>> def log(func):
...? ? @functools.wraps(func)
...? ? def wrapper(*args, **kw):
...? ? ? ? ? ? print('claa %s():' % func.__name__)
...? ? ? ? ? ? return func(*args,**kw)
...? ? return wrapper
或者針對(duì)帶參數(shù)的decorator:
>>> import functools
>>> def log(text):
...? ? def decorator(func):
...? ? ? ? ? ? @functools.wrap(func)
...? ? ? ? ? ? def wrapper(*args, **kw):
...? ? ? ? ? ? ? ? ? ? print('%s %s():' % (text, func.__name__))
...? ? ? ? ? ? ? ? ? ? return func(*args, **kw)
...? ? ? ? ? ? return wrapper
...? ? return decorator
作業(yè)
暫缺
偏函數(shù)
把一個(gè)函數(shù)的某些參數(shù)給固定锥埂(也就是設(shè)置默認(rèn)值),返回一個(gè)新的函數(shù)呜笑,調(diào)用這個(gè)新函數(shù)會(huì)更簡(jiǎn)單夫否。
通過設(shè)定參數(shù)的默認(rèn)值,可以降低函數(shù)調(diào)用的難度叫胁。而偏函數(shù)也可以做到這一點(diǎn)凰慈。f
unctools.partial就是幫助我們創(chuàng)建一個(gè)偏函數(shù)的,不需要我們自己定義int2()驼鹅,可以直接使用下面的代碼創(chuàng)建一個(gè)新的函數(shù)int2:
>>> import functools
>>> int2=functools.partial(int, base=2)
>>> int('1000000')
1000000
>>> int2('1000000')
64
僅僅是把base參數(shù)重新設(shè)定默認(rèn)值為2微谓,但也可以在函數(shù)調(diào)用時(shí)傳入其他值:
>>> int2('1000000', base=10)
1000000
創(chuàng)建偏函數(shù)時(shí),實(shí)際上可以接收函數(shù)對(duì)象输钩、*args和**kw這3個(gè)參數(shù)豺型,當(dāng)傳入:
int2 = functools.partial (int, base=2)
實(shí)際上固定了int()函數(shù)的關(guān)鍵字參數(shù)base,也就是:
int2('10010')
相當(dāng)于:
kw = {'base':2}
int('10010', **kw)
傳入:
max2 =functools.partial(max,10)
實(shí)際上會(huì)把10作為*args的一部分自動(dòng)加到左邊买乃,也就是:
max2(5, 6, 7)
args = (10, 5, 6, 7)
max(*args)
結(jié)果為10姻氨。