第七章 函数式编程

函数式编程

不知不觉到了书的最后一章,等我酝酿一会...就一会......
ok!缓冲结束

7.1 又见函数

1. Python中的函数式

何为函数式编程?
函数是纯粹的,因为其单独存在而不受其他函数影响,当然这是在要求其变量皆是不可变的情况下。函数式编程便是以函数为中心对代码进行封装,且要求变量都是不可变更的。
对于纯函数,消除了竞跑条件的可能,方便了并行运算。函数式编程的思路是自上而下的——先提出一个大问题,用一个函数来解决,然后这个函数内部再用其他函数来解决小问题。

2. 并行运算

是指多条指令同时执行。每台单处理器的计算机同时只能执行一条指令,这种工作方式称为串行运行
大规模的并行运算通常是在有多个主机组成的集群上进行的。而单机则可以通过“分时复用”来模拟多主机的并行处理(处理器在进程间频繁切换)而实现并行。
区分多进程与多线程:程序运行成为一个进程,进程有内存空间以存储自身的运行状态、数据以及相关代码。一个进程内部,可以有多个线程(任务)线程之间可以共享一个进程的内存空间。一个进程可以有很多线程,每条线程并行执行不同的任务。线程是程序中一个单一的顺序控制流程,在单个程序中同时运行多个线程完成不同的工作,称为多线程。

7.2 被解放的函数

1. 函数作为参数

在函数式编程中,函数是第一级对象(函数可以像普通对象一样使用)。
那么,函数也可以成为其他函数的参数,以函数为参数的函数也就是我们后面说的高阶函数。
代码:

def square_sum(a,b):
    return a**2 + b**2
def cubic_sum(a,b):
    return a**3 + b**3
def argument_demo(f,a,b):
    return f(a,b)

print(argument_demo(square_sum,3,5))   #打印出34
print(argument_demo(cubic_sum,3,5)        #打印出152

作为参数的函数能起到回调(callback)作用,当某个事件发生时,回调函数就会被调用。GUI回调例子:

import tkinter as tk
def callback():
    listbox.insert(tk.END,"Hello World!")

if __name__ == "__main__":
    master = tk.Tk()
    
    button = tk.Button(master,text="OK",command = callback)
    button.pack()

    listbox = tk.Listbox(master)
    listbox.pack()
    tk.mainloop()

运行效果如图:
GUI回调

2. 函数作为返回值

上面讲了函数作为参数,既然是对象,那么函数也可作为返回值。例如:

def line_conf():
    def line(x):
        return 2*x+1
    return line

my_line = line_conf()
print(my_line(5))    #打印出11

注意一点,在函数内部定义的函数,只能在函数隶属范围内进行调用,若在域外进行调用,则会报错。

3. 闭包

当上面的例子中的line()函数引用外部变量,这个变量就作为line()的环境变量,当最后返回line的时候,会带有这个变量的信息。
一个函数与它的环境变量合在一起,就构成一个闭包
若同一环境变量在函数定义前后有不同的赋值,那么闭包会返回后者,即内部函数返回时的外部对象的值
例子:

def line_conf():
    b = 15

    def line(x):
        return 2*x+b
    b = 5
    return line

if __name__  == "__main__":
    my_line = line_conf()
    print(my_line.__closure__)
    print(my_line.__closure__[0].cell_contents)   #打印出5

注:if __name__ == '__main__'的意思是:当.py文件被直接运行时,if __name__ == '__main__'之下的代码块将被运行;
当.py文件以模块形式被导入时,if __name__ == '__main__'之下的代码块不被运行。
参考:Python中if name == 'main',init和self 的解析


这里的“5”即是返回闭包时的环境变量b的取值。
闭包可以提高代码的可复用性。即一个函数以环境变量为参数,例如对于一个线性函数y=ax+b,则以a,b为环境变量,当调用函数输入a,b参数时,就会返回一个函数。闭包其实是创建了一群形式相似的函数。
闭包还能起到减参的作用。一个函数有自变量和参数组成,闭包提供了预设参数,从而起到减参作用。

7.3 小女子的梳妆匣

1. 装饰器

装饰器(decorator)是一种高级python语法。顾名思义就是用来添点东西,加点功能,其作用对象可以是函数,也可以是类。
首先我们对函数进行装饰:

def square_sum(a,b):
    print("input:",a,b)
    return a**2+b**2
def square_diff(a,b):
    print("input:",a,b)
    return a**2 - b**2
if __name__ == "__main__":
    print(square_sum(3,4))
    print(square_diff(3,4))
'''
输出结果:
input: 3 4
25
input: 3 4
-7
'''

这是不用装饰器进行装饰的。我们可以改用装饰器,定义功能拓展本身,再把装饰器用于函数。(这样一来就可以复用

def decorator_demo(old_func):
    def new_func(a,b):
        print("input:",a,b)
        return old_func(a,b)
    return new_func

@decorator_demo
def square_sum(a,b):
    return a**2+b**2

if __name__ == "__main__":
    print(square_sum(3,4))
'''
输出结果:
input: 3 4
25
'''

装饰器以def定义,用@进行调用。
一个实用的装饰器(返回程序运行时间):

import time
def dtime(ofunc):
    def nfunc(*arg,**dict_arg):
        t1 = time.time()
        r = ofunc(*arg,**dict_arg)
        t2 = time.time()
        print("time:",t2-t1)
        return r
    return nfunc

稍微给个装饰例子和结果图:
例子
2. 带参装饰器

这个与上面的大同小异,只是在定义装饰器时添加了参数。即def 装饰器名(参数),调用时@装饰器名(参数值),如此便为装饰器的编写和使用提供了更大的灵活性。那么带参的装饰器有什么用呢?其作用就在于这个参数能起到区分函数的作用,比如HTTP请求的函数需要什么权限("用户"和"管理员"作为参数),就可以把相应的参数值传给装饰器。

3. 装饰类

上面讲了装饰函数,即装饰器接收一个函数,并返回一个函数,起到加工函数的作用。类也一样。
给个例子:

def dclass(someclass):
    class nclass(object):
        def __init__(self,age):
            self.total_display = 0                    #total_display属性以记录调用display()的次数
            self.wrapped       = someclass(age)       #self.wrapped以记录原来类生成的对象
        def display(self):
            self.total_display +=1
            print("total display",self.total_display)
            self.wrapped.display()
    return nclass

@dclass
class Bird:
    def __init__(self,age):
        self.age = age
    def display(self):
        print("My age is:",self.age)

if __name__ == "__main__":
    el = Bird(5)
    for i in range(3):
        el.display()
'''
输出结果:
total display 1
My age is: 5
total display 2
My age is: 5
total display 3
My age is: 5
'''

7.4 高阶函数

1. lambda与map

能接收其他函数作为参数的函数,被称为高阶函数。
代表性的高阶函数有:map()、filter()、reduce()
除了可以用def来定义有名函数,还可以用lambda语法来定义匿名函数(适用于简短函数的定义):

lambda_sum = lambda x,y:x+y
print(lambda_sum(3,4))    #打印出7

map()函数的第一个参数是函数,第二个参数是一个可循环对象(例如列表),其作用就是将函数逐个作用于可循环对象,返回一个迭代器

ls = [1,2,3,4,5]
r = map(lambda x:x**2,ls)
print(list(r))                  #打印 [1, 4, 9, 16, 25]

注意map()返回的是一个迭代器(上述例子中输出r结果:<map object at 0x000001BB33FF1BC8>),并非具体结果。
当函数参数变多时,可循环对象也要与参数数目一致。

2. filter函数

这函数与map()函数是一样的,只是filter()函数是用来筛选数据的,第一个参数是逻辑判断类型的函数,第二个参数便是可循环对象(筛选目标集)。例子:

def bj(a):
    if a <= 60:
        return False
    else:
        return True

for i in filter(bj,[100,34,65,59,46]):
    print(i)             #打印出100 65

可见,其返回的是判定为True对应的值(数据)

3. reduce函数

其位于functools包中,需要进行引入。第一个参数为函数,只是这函数必须能接收两个参数,第二个参数依旧是可循环对象。其作用是将函数对象累进作用于各个参数。返回一个数值。看例子:

from functools import reduce
ls = []
for i in range(100):
    i = i+1
    ls.append(i)
r = reduce(lambda x,y:x+y,ls)
print(r)                  #打印出5050

这看着很熟悉,就是对1到100进行累加而已。简单地讲就是把可循环对象中的第一个元素作为x,第二个作为y,然后进行函数的运算,再将其运算结果作为x,第三个元素作为y,再作运算,直至最后得出结果。

4. 并行处理

对于并行运算,通过map()方法来分配任务,例如启动10个进程,并行地处理100个线程。多线程的map()方法把100个线程分配给10个进程。运行时间大大缩短。

7.5 自上而下

1. 便捷表达式

生成器表达式、列表解析式、词典解析式这些都是便捷表达式。
依次例子如下:

gen = (x for x in range(4))
ls = [x**2 for x in range(10)]
'''返回[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]'''

x = [1,2,3]
y = [4,5,7]
ls1 = [x**2 for (x,y) in zip(x,y) if y<6]
'''返回:[1, 4]'''
dict = {k: v for k,v in enumerate("wyWarmawy") if v not in "wy"}
'''返回{2: 'W', 3: 'a', 4: 'r', 5: 'm', 6: 'a'}'''
2. 懒惰求值

迭代器的工作方式正是函数式编程中的懒惰求值。只有在需要时,迭代器才会计算具体值。懒惰求值可以快速运算庞大的数据,但不是计算出具体值。如果不需要穷尽所有数据元素,那么懒惰求值将节省内存空间与时间。

3. itertools包

此包中提供了很多有用的生成器

count(5,2)          #从整数5开始迭代,每次增加2
cycle("abc")        #重复序列的元素
repeat(5,10)        #重复5,重复10次
chain([1,2,4],[1,6,8])       #连接两个迭代器为一个:1,2,4,1,6,8
product("abc",[1,2])        #多个迭代器集合的笛卡儿积(相当于嵌套循环):('a', 1), ('a', 2), ('b', 1), ('b', 2), ('c', 1), ('c', 2)
permutations("abcd",2) #从“abcd”选两个元素,将所有结果排序返回新的迭代器(区分顺序)
combinations("abcd",2) #同上(不区分顺序即ab与ba只返回ab)
combinations_with_replacement("abcd",2) #与上类似,只是它允许选择重复元素,如aa,bb,cc,dd

包中有用的高阶函数:

starmap(pow,[(1,1),(2,2),(3,3)])   #  pow将依次作用于列表中每个元组
takewhile(lambda x:x<5,[1,3,6,7,1])  #当函数返回True时,则收集元素,若出现False则停止
dropwhile(lambda x:x<5,[1,3,5,6,7])   #当函数返回False时,才开始收集该元素以及后面所有元素

另外,包中提供了groupby函数,groupby()函数能将一个key()函数作用域原迭代器的各个元素,从而获得各个函数的键值。根据key()函数的结果将拥有元素进行分组。返回组数个的迭代器。
例如:

from itertools import groupby
def hclass(h):
    if h >175:
        return "tall"
    if h < 160:
        return "short"
    else:
        return "middle"

group = [167,187,178,154,148,167,198,164]
group = sorted(group,key = hclass)

for m,n in groupby(group,key = hclass):
    print(m)
    print(list(n))
'''
输出结果:
middle
[167, 167, 164]
short
[154, 148]
tall
[187, 178, 198]
'''

注意:分组前需要对原迭代器中的元素根据key()函数进行排序。
包中的其他工具:

compress("ABCD",[1,1,0,1])   #根据第二个参数来选择保留第一个参数中的元素,即0代表假,不进行保留,反之保留
islice()   #类似于slice(),返回迭代器
izip()     #类似于zip(),返回迭代器

slice()函数
zip()函数

                                                                           

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

推荐阅读更多精彩内容

  • 一 又见函数 1 python中的函数式 要点:①函数式编程以函数为中心进行代码封装。②函数是第一级对象,能像普通...
    大饼与我阅读 236评论 0 2
  • 7.1 又见函数 7.2 被解放的函数 7.3 小女子的梳妆匣 7.4 高阶函数 7.5自上而下 .又见函数 。P...
    mAbbQi阅读 366评论 0 0
  • , 7.1又见函数 1.python中的函数式 函数式编程以函数为中心进行代码封装。函数式编程强调了函数的纯粹性。...
    lammmya阅读 183评论 0 0
  • 写在前面的话 代码中的# > 表示的是输出结果 输入 使用input()函数 用法 注意input函数输出的均是字...
    FlyingLittlePG阅读 2,684评论 0 8
  • 今天是8月5日,星期一,农历七月初五。心中要有景,才能春暖花开;心中要有愿,才能成就事业;心中要有理,才能走遍天下...
    大宝儿Zhang阅读 197评论 0 0