zoukankan      html  css  js  c++  java
  • 15.函数式编程和偏函数

    五、函数式编程

     

    匿名函数与lambda

    1、python允许用lambda关键字创造匿名函数

    2、匿名是因为不需要以标准的def方式来声明

    3、一个完整的lambda语句代表了一个表达式,这个表达式的定义体必须和声明放在同一行

    lambda [arg1[, arg2, ... argN]]: expression

     例子1:

    >>> a = lambda x, y: x + y
    >>> print a(3, 4)
    7

    例子2:

    >>> def add(x,y):           #定义一个加法函数
        return x+y              #返回两个参数的相加的值
     
    >>> z=add(3,4)               
    >>> print z 
    7                                     #调用加法函数返回7
    >>> lambda x,y:x+y
    <function <lambda> at 0x0000020F385B86A8>   
    #可以看到lambda是一个   function(函数)类对象
    >>> f=lambda x,y:x+y          #功能实现的跟add(x,y)一样       
    >>> f(1,2)
    3
    >>> f(3,4)
    7
    >>> def multiply(x,y):
        return x*y
     
    >>> multiply(3,4)
    12
    >>> multiply=lambda x,y:x*y
    >>> multiply(3,4)
    12
    >>> def subtract(x,y):
        return x-y
     
    >>> subtract(3,4)
    -1
    >>> subtract=lambda x,y:x-y
    >>> subtract(3,4)
    -1
     
    >>> def divide(x,y):
        return x/y
     
    >>> divide(4,2)
    2.0
    >>> divide=lambda x,y:x/y
    >>> divide(4,2)
    2.0
     
    #上面的乘法函数,减法函数,除法函数都可以用lambda表达式来代替,更方便

    高阶函数

    高阶函数

    结论:

    一个函数可以作为参数传给另外一个函数,或者一个函数为另外一个函数的返回值(若返回值为该函数本身,则为递归),满足其一则为高阶函数。

    高阶函数英文叫Higher-order function。什么是高阶函数?我们以实际代码为例子,一步一步深入概念

    变量可以指向函数

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

    以Python内置的求绝对值的函数abs()为例,调用该函数用以下代码:

    >>> abs(-10)
    10

    但是,如果只写abs呢?

    >>> abs
    <built-in function abs>

    可见,abs(-10)是函数调用,而abs是函数本身。

    要获得函数调用结果,我们可以把结果赋值给变量:

    >>> x = abs(-10)
    >>> x
    10

    但是,如果把函数本身赋值给变量呢?

    >>> f = abs
    >>> f
    <built-in function abs>

    结论:函数本身也可以赋值给变量,即:变量可以指向函数。

    如果一个变量指向了一个函数,那么,可否通过该变量来调用这个函数?用代码验证一下:

    >>> f = abs
    >>> f(-10)
    10

    成功!说明变量f现在已经指向了abs函数本身。直接调用abs()函数和调用变量f()完全相同。

    函数名也是变量

    那么函数名是什么呢?函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!

    如果把abs指向其他对象,会有什么情况发生?

    >>> abs = 10
    >>> abs(-10)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'int' object is not callable

    abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10

    当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复abs函数,请重启Python交互环境。

    注:由于abs函数实际上是定义在import builtins模块中的,所以要让修改abs变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10

    传入函数

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

     一个最简单的高阶函数:

    def add(x, y, f):
        return f(x) + f(y)

    当我们调用add(-5, 6, abs)时,参数xyf分别接收-56abs,根据函数定义,我们可以推导计算过程为:

    x = -5
    y = 6
    f = abs
    f(x) + f(y) ==> abs(-5) + abs(6) ==> 11
    return 11

    用代码验证一下

    >>> add(-5, 6, abs)
    11

    编写高阶函数,就是让函数的参数能够接收别的函数。

    小结

    把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。

    函数作为返回值

    高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

    我们来实现一个可变参数的求和。通常情况下,求和的函数是这样定义的:

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

    但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数:

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

    当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:

    >>> f = lazy_sum(1, 3, 5, 7, 9)
    >>> f
    <function lazy_sum.<locals>.sum at 0x101c6ed90>

    调用函数f时,才真正计算求和的结果:

    >>> f()
    25

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

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

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

    f1()f2()的调用结果互不影响。

     内建高阶函数

    map()函数

    map()函数接收两个参数,一个是函数,一个是序列,map将传入的函数依次作用到序列的每个元素,并把结果作为新的list返回。

    返回值
    Python 2.x 返回列表。
    Python 3.x 返回迭代器。

     举例说明,比如我们有一个函数f(x)=x2,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9] 上,就可以用 map() 实现如下:

     现在,我们用Python代码实现:

    >>> 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]

    如果不是交互式的话,可以这样做:

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

    或者

    def f(x):
        return x * x
    
    print map(f,[i for i in range(1,10)])

    执行结果:

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

     map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。
    你可能会想,不需要 map() 函数,写一个循环,也可以计算出结果:

    def f(x):
        return x * x
    
    L = []
    for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
        L.append(f(n))
    print L

    执行结果:

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

    的确可以,但是,从上面的循环代码,能一眼看明白"f(x)作用在list的每一个元素并把结果生成一个新的list"吗?
    所以, map() 作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x ,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串:

    def f(x):
        return x * x
    
    print map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])

    执行结果:

    ['1', '2', '3', '4', '5', '6', '7', '8', '9']

     只需要一行代码。

    filter()函数

    map() 类似, filter() 也接收一个函数和一个序列。和 map() 不同的时, filter() 把传入的函数依次作用于每个元素,然后根据返回值是 True 还是 False 决定保留还是丢弃该元素。

    例如,在一个list中,删掉偶数,只保留奇数,可以这么写:

    def is_odd(n):
        return n % 2 == 1
    print filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])

    或者使用filter+lambda结合:

    print filter(lambda n:n %2, [1, 2, 4, 5, 6, 9, 10, 15])

    执行结果:

    [1, 5, 9, 15]

    reduce()函数

    同样的接收两个参数,一个是函数,一个是可迭代对象 Iterable Object(eg: list列表)。reduce中的函数必须也要接收2个参数,执行时把前一个结果继续和序列的下一个元素做累积计算,其效果就是:

    reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

    python2中,为内置函数

    python3中,导入reduce, from functools import reduce

    比方说对一个序列求和,就可以用reduce实现:

    def add(x,y):
        return x + y
    
    print reduce(add,[1,3,5,7,9])

    执行结果:

    25

    阶乘

    #!/usr/bin/env python
    #coding:utf8
    
    def mulit(x,y):
        return x * y
    
    print reduce(mulit,[1,3,5,7,9])

    执行结果:

    945

    sorted函数

    列表里面提供了sort方法,其它数据结构没有.sorted方法可以对任何可迭代对象排序
    sort方法支持原地排序(变量排序后,变量本身改变),sorted排序后返回一个新的列表,并不改变原有变量
    默认sort和sorted方法由小到大进行排序排序,reverse=True时,由小到大进行排序

    排序是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个dict呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。

    1.sorted() 函数就可以对list进行排序

    >> sorted([36, 5, 12, 9, 21])
    [5, 9, 12, 21, 36]

    2、sorted() 函数也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。比如,如果要倒序排序,我们就可以自定义一个 reversed_cmp 函数:

    def reversed_cmp(x, y):
      if x > y:
        return -1
      if x < y:
        return 1
      return 0

    传入自定义的比较函数 reversed_cmp ,就可以实现倒序排序:

    >>> sorted([36, 5, 12, 9, 21], reversed_cmp)
    [36, 21, 12, 9, 5]

    我们再看一个字符串排序的例子:

    >>> sorted(['bob', 'about', 'Zoo', 'Credit'])
    ['Credit', 'Zoo', 'about', 'bob']

    默认情况下,对字符串排序,是按照ASCII的大小比较的,由于 'Z' < 'a' ,结果,大写字母 Z 会排在小写字母 a的前面。
    现在,我们提出排序应该忽略大小写,按照字母序排序。要实现这个算法,不必对现有代码大加改动,只要我们能定义出忽略大小写的比较算法就可以:

    def cmp_ignore_case(s1, s2):
      u1 = s1.upper()
      u2 = s2.upper()
      if u1 < u2:
        return -1
      if u1 > u2:
        return 1
      return 0

    忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。
    这样,我们给 sorted 传入上述比较函数,即可实现忽略大小写的排序:

    >>> sorted(['bob', 'about', 'Zoo', 'Credit'], cmp_ignore_case)
    ['about', 'bob', 'Credit', 'Zoo']

    偏函数

     

    偏函数(Partial function)是通过将一个函数的部分参数预先绑定为某些值,从而得到一个新的具有较少可变参数的函数。

    在Python中,可以通过functools中的partial高阶函数来实现偏函数功能。

    首先介绍下偏函数partial。首先借助help来看下partial的定义

    首先来说下第一行解释的意思:

    partial 一共有三个部分:

    (1)第一部分也就是第一个参数,是一个函数,这个函数可以是你定义的,也可以是Python内置函数

    (2)第二部分是一个可变参数,*args,比如内置函数max的参数就是一个可变参数,max(1,2,3,4,5)=5

    (3)第三部分是一个关键字参数,比如内置函数int的第二个参数就是命名关键字参数,默认base=10,表示int转换时默认是10进制的:

    partial函数的作用就是:将所作用的函数作为partial()函数的第一个参数,原函数的各个参数依次作为partial()函数的后续参数,原函数有关键字参数的一定要带上关键字,没有的话,按原有参数顺序进行补充。

    >>> from operator import add
    >>> from functools import partial
    >>> add10 = partial(add, 10)
    >>> print add10(25)
    35

    myadd.py内容:

    #!/usr/bin/env python
    #coding:utf8
    
    from functools import partial
    
    def add(x,y):
        return x + y 
    
    if __name__ == "__main__":
        print add(10, 8)
        print add(10,30)
        print add(10,5)
        add10 = partial(add,10)
        print add10(50) 

    执行结果

    18
    40
    15
    60

    编写一个窗口程序,要求如下
    1. 窗口程序提供三个按钮

    #!/usr/bin/env python
    #coding:utf8
    
    import Tkinter
    from functools import partial
    
    root = Tkinter.Tk()
    MyButton = partial(Tkinter.Button,root,fg='white',bg='blue')
    b1 = MyButton(text="Button1")
    b2 = MyButton(text="Button")
    qb = MyButton(text='quit',command=root.quit)
    b1.pack()
    b2.pack()
    qb.pack()
    Tkinter.mainloop()

     编写一个窗口程序,要求如下
    1. 窗口程序提供三个按钮
    2. 其中两个按钮的前景色均为白色,背景色为蓝色
    3. 第三个按钮前景色为红色,背景色为红色
    4. 按下第三个按钮后,程序退出

     方案

    python 可以使用 tkinter 模块实现 GUI 编程,系统默认没有安装相关模块,需要将对
    应的 RPM 安装好。
    由于创建按钮的实例需要很多参数,同时这些参数使用相同的值,那么可以使用偏函数
    的方式将这些值固定下来以简化程序的编写。

    实现此案例需要按照如下步骤进行。
    步骤一:安装相关的程序包
    [root@py01 bin]# yum install -y tkinter

    步骤二:编写脚本

    #!/usr/bin/env python
    #coding:utf-8
    import Tkinter #导入模块,用于编写 GUI 界面
    from functools import partial #仅导入偏函数相关的功能函数
    root = Tkinter.Tk() #创建背景窗口
    #使用偏函数定义按钮的默认参数 MyButton = partial(Tkinter.Button, root, fg = 'white', bg = 'blue') b1 = MyButton(text = 'Button 1') #创建按钮,指定按钮上的文字 b2 = MyButton(text = 'Button 2') qb = MyButton(text = 'QUIT', bg = 'red', command = root.quit)
    b1.pack() #将安装到背景窗口上 b2.pack() qb.pack(fill = Tkinter.X, expand = True) root.title('PFAs!') #设置背景窗口标题 root.mainloop() #启动窗口程序

    执行结果:

    二、偏函数的使用

    A、偏函数的第二个部分(可变参数),按原有函数的参数顺序进行补充,参数将作用在原函数上,最后偏函数返回一个新函数(类似于,装饰器decorator,对于函数进行二次包装,产生特殊效果;但又不同于装饰器,偏函数产生了一个新函数,而装饰器,可改变被装饰函数的函数入口地址也可以不影响原函数)

    注意:如果不理解装饰器,可以先去看一下装饰器的内容

    案例:我们定义一个sum函数,参数为*args可变,计算这些可变参数的和。

    扩展:我们想要对sum函数求和后的结果,再加上10加上20甚至加更多,得到一个新的结果

    实现:我们分别用decorator和partial来实现,对比一下二者的区别

    (一)装饰器 decorator 实现

    test.py

    # /usr/bin/env Python3
    # -*- encoding:UTF-8 -*-
    
    from functools import wraps
    
    def sum_add(*args1): #我们要给我们的装饰器decorator,带上参数
        def decorator(func):
            @wraps(func) #加上这句,原函数func被decorator作用后,函数性质不变
            def my_sum(*args2): #注意,参数要和原函数保持一致,真正实行扩展功能的是外层的装饰器
                my_s = 0
                for n in args1:
                    my_s = my_s +n #这个是我们新加的求和结果
                return func(*args2) + my_s #这个,我们在原求和函数的结果上再加上s,并返回这个值
            return my_sum #返回my_sum函数,该函数扩展原函数的功能
        return decorator  #返回我们的装饰器
    
    @sum_add(10,20) #启用装饰器 对sum函数进行功能扩展 
    def sum(*args):
        s = 0
        for n in args:
            s = s+n
        return s
    print(sum(1,2,3,4,5))
    print(sum.__name__)

    sum(1,2,3,4,5)返回的结果绝不是15,这样就失去了装饰器存在的意义,当然,这里,我们知道,sum最后返回的值应该是10+20+15 = 45,这样一来,我们的decorator就实现了我们想要的扩展功能,最后,发现,原函数sum的name属性,仍然是sum,说明,这种装饰扩展功能,不影响我们的原函数:

    (二)偏函数 partial function 实现

    这才是我们本篇的重点,准备好了,我们就开始:

    我们先来看下普通函数,我们是怎么来实现

    A:普通函数可变参数顺序执行

    #!/usr/bin/env python
    #coding:utf8
    
    
    def sum(*args):
        s = 0
        for n in args:
            s = s + n
        return s
    
    
    print(sum(10, 20) + sum(1, 2, 3, 4, 5))

    我们如果想实现+10+20的效果,必须写两遍sum,这样写,显然是最易懂的,但是,却显得很邋遢

    B:普通函数可变参数加关键字参数组合

    针对上面的A过程,我们改下代码,使我们的代码看起来稍显复杂,但是略显专业:

    #!/usr/bin/env python
    #coding:utf8
    
    
    
    
    def sum(*args, **others):
        s = 0
        for n in args:
            s = s + n
        s1 = 0
        for k in others:
            s1 = s1 + others[k]  # 我们还要算一下,关键字参数里蕴藏的求和结果,k是dict中的关键字key
        return s + s1  # 最终,我们实现扩展功能,顺序参数和关键字参数结果相加
    
    
    D = {'value1': 10, 'value2': 20}
    print(sum(1, 2, 3, 4, 5, **D))

    代码看起来,是显得专业了,但是感觉冗余,没必要

    C:偏函数可变参数顺序填充一步到位

    上面A和B我们都说过了,这两种方式都不好,显然,这么简单的事情,我们不必麻烦decorator了,那我们还有办法没?有,Python,给我们提供了偏函数,来吧,主角登场:

    提示:两种使用partial功能方式

    1)import functools                        -->functools.partial(func,*args)
    (2from   functools import partial -->partial(func,*args)

    我们这里选第二种,我们看下demo:

    #!/usr/bin/env python
    #coding:utf8
    
    
    from functools import partial
    
    
    def sum(*args):
        s = 0
        for n in args:
            s = s + n
        return s
    
    
    sum_add_10 = partial(sum, 10)  # 10 作用在sum第一个参数的位置
    sum_add_10_20 = partial(sum, 10, 20)  # 10 20 分别作用在sum第一个和第二个参数的位置
    print('A____________我们看下原函数sum的函数地址入口:')
    print(sum)
    print('B______我们看下partial函数返回函数的地址入口:')
    print(partial(sum, 10))
    print(sum_add_10(1, 2, 3, 4, 5))  # --> 10 + 1 + 2 + 3 + 4 + 5 = 25
    print(sum_add_10_20(1, 2, 3, 4, 5))  # --> 10 + 20 + 1 + 2 + 3 + 4 + 5 = 45

    可以看出,我们针对sum函数的求和结果,再加上10,或者加10加20,甚至加更多,都是可以通过偏函数来实现的,注意偏函数的第二部分,参数是可变的,是按顺序走的,因此,偏函数产生的新函数,sum_add_10 实际上等同于sum(10,*args):

    通过几个例子,我们最终发现,还是偏函数比较方便,一行代码就搞定了,而且新定义的函数,可以根据函数名很容易知道,这个函数扩展的原函数是哪个,实现的效果是什么:

    B、偏函数的第三个部分(关键字参数),按原有函数的关键字参数进行填补,参数将作用在原函数上,最后偏函数返回一个新函数

    案例:我们定义一个mod求余函数,两个参数,一个是被除数,一个是除数,除数我们这里用命名关键字参数表示,默认值2

    扩展:我们的除数不固定,可以是对2就行求余,也可以对3,对4,总之我们需要指定除数的值

    返回结果: True 或 False

    实现:原函数实现和partial函数实现

    demo如下:

    #!/usr/bin/env python
    #coding:utf8
    
    
    import  functools
    def mod(m,key=2):
        return m % key == 0
    mod_to_2 = functools.partial(mod,key=2)
    print('A__3___使用原函数的默认关键字参数对2进行求余:')
    print(mod(3))                           #对2进行求余-- 原函数 使用默认参数
    print('B__3___使用偏函数对2进行求余:')
    print(mod_to_2(3))                      #对2进行求余-- 新函数 --偏函数产生
    mod_to_5 = functools.partial(mod,key=5)
    print('C__25___使用原函数的关键字参数对5进行求余:')
    print(mod(25,key=5))                    #对5进行求余 -- 原函数
    print('D__25___使用偏函数对5进行求余:')
    print(mod_to_5(25))                     #对5进行求余 -- 新函数--偏函数产生  + 3 + 4 + 5 = 45

    我们发现,实际上,偏函数的作用,其实和原函数差不多,只不过,我们要多次调用原函数的时候,有些参数,我们需要多次手动的去提供值,比如上述的对5进行求余,如果我们想知道,15,45,30这些数是否能够被5整除,那么,我们用原函数的话,就需要写三次,key=5,然而,我们用偏函数的话,只需要重复调用新产生的函数mod_to_5(15 or 45 or 30)即可,至于除数5,偏函数已经为我们设定了,因此:

    当函数的参数个数太多,需要简化时,使用 functools.partial 可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。当然,decorator也可以实现,如果,我们不嫌麻烦的话。

    偏函数参考:https://www.cnblogs.com/huyangblog/p/8999866.html

  • 相关阅读:
    Picasa生成图片幻灯片页面图文教程
    Ubuntu下缓冲器溢出攻击实验(可以看看问题分析)
    redis源码笔记 aof
    redis源码笔记 bio
    redis源码笔记 slowlog
    记录一个字符数组和字符指针的不同
    redis源码笔记 rediscli.c
    redis源码笔记 redis对过期值的处理(in redis.c)
    redis源码笔记 有关LRU cache相关的代码
    redis源码笔记 initServer
  • 原文地址:https://www.cnblogs.com/zhongguiyao/p/11048418.html
Copyright © 2011-2022 走看看