Python函数式编程-高阶函数、匿名函数、装饰器、偏函数

本篇文章我们来介绍下Python函数式编程的知识。最主要的一点,Python中的函数是对象,可以复制给变量!好了,我们来介绍几个Python函数式编程中的要点,包括高阶函数、匿名函数、装饰器、偏函数等等。精彩内容,不容错过!

1、高阶函数

函数本身也可以赋值给变量,即:变量可以指向函数。如果一个变量指向了一个函数,那么,可以通过该变量来调用这个函数。

f = abs
f(-10)  # 10

既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数

def add_abs(x,y,f):
    return f(x) + f(y)
f = abs
add_abs(-5,6,f) # 11

接下来,我们介绍几个重点的高阶函数。

map函数

map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。我们之前介绍过了,Iterator是惰性序列,需要通过list()函数让它把返回结果变为list。

def f(x):
    return x * x
r = list(map(f,[1,2,3,4,5,6,7,8,9,10]))
r

输出结果为:

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

reduce函数

reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算:

def f(x,y):
    return x + y
reduce(f,[1,2,3,4,5]) # 15

filter函数

和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。filter()函数返回的是一个Iterator,也就是一个惰性序列,同样需要通过list()函数让它把返回结果变为list。

def is_odd(n):
    return n%2==1
list(filter(is_odd,[1,2,3,4,5,6,7,8,9]))

结果为:

[1, 3, 5, 7, 9]

sorted函数

sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序或者按照小写字母顺序进行排序:

print(sorted([36,5,-12,9,-21],key=abs))
print(sorted(['bob', 'about', 'Zoo', 'Credit'],key=str.lower))

结果为:

[5, 9, -12, -21, 36]
['about', 'bob', 'Credit', 'Zoo']

2、函数作为返回值

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
我们来实现一个可变参数的求和,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数:

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

f = lazy_sum(1,3,5,7,9)
f()

在这个例子中,我们在函数lazy_sum中又定义了函数sum,
并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,
当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,
这种称为“闭包(Closure)”的程序结构拥有极大的威力。

请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:

f1 = lazy_sum(1,3,5,7,9)
f2 = lazy_sum(1,3,5,7,9)
f1 == f2 # False

另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:

def count():
    fs = []
    for i in range(1,4):
        def f():
            return i * i
        fs.append(f)
    return fs

f1,f2,f3 = count()
print(f1())
print(f2())
print(f3())

输出结果为:

9
9
9

全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

def count():
    fs = []
    def sub(j):
        def f():
            return j * j
        return f
    for i in range(1,4):
        fs.append(sub(i))
    return fs
f1,f2,f3 = count()
print(f1())
print(f2())
print(f3()

结果为:

1
4
9

3、匿名函数lambda

当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。相信大家对于匿名函数一定不陌生,其实就是我们常说的lambda函数:

list(map(lambda x:x * x,[1,2,3,4,5,6,7,8,9]))

def build(x,y):
    return lambda:x * x + y * y

reduce(lambda x,y:x + y,[1,2,3,4,5,6,7,8,9])

4、装饰器

在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。
所以,假设我们要定义一个能够打印当前函数名的decorator:

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

@log
def helloworld():
    print('hello world')

helloworld()

执行结果为:

call helloworld()
hello world

下面的例子中,正是由于wrapper中把 func(args,*kwargs)进行return,因此函数得以执行。

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:


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

@log('execute')
def helloworld():
    print('hello world')

helloworld()

上面代码的执行过程相当于helloworld = log('execute')(helloworld)

我们讲了函数也是对象,它有name等属性,但你去看经过decorator装饰之后的函数,它们的name已经从原来的'helloworld'变成了'wrapper':

helloworld.__name__  #'wrapper'

如果需要把原始函数的name等属性复制到wrapper()函数中,使用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 helloworld():
    print('hello world')

helloworld.__name__

此时的输出就变为了'helloworld'

5、偏函数

一般的函数有许多需要定义的参数,假设我们想要固定其中的某些参数,返回一些新的函数,我们就可以使用functools.partial帮助我们创建一个偏函数,从而使得调用变得简单

import functools
int2 = functools.partial(int, base=2)
int2('10010') # 18

当然我们也可以穿入一个函数字典:

kw = { 'base': 2 }
int('10010', **kw)

当传入的参数没有对应的key时,它默认时作为*args的一部分自动加到左边,因此下面的函数相当于比较max(10,5,6,7),返回10:

max2 = functools.partial(max, 10)
max2(5, 6, 7)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容