美文网首页
Python函数式编程

Python函数式编程

作者: 夏海峰 | 来源:发表于2020-04-10 22:33 被阅读0次

    计算机(Computer)计算(Compute)这两个概念区分:在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。

    函数式编程(Functional Programming):把大段代码拆解成函数,通过一层一层地调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数是面向过程的程序设计的基本单元。而函数式编程虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。

    函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用。函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

    1、高阶函数(函数作为参数)

    (1)变量可以指向函数

    • 把函数本身赋值给变量,即“变量可以指向函数”。
    abs(-10)   # 这是函数调用
    f1 = abs   # 把函数本身赋值给一个变量
    res1 = f1(-100)   # 100
    print('res1', res1)
    

    (2)函数名也是变量

    abs(-10)  # 10
    abs = print
    abs(-10)  # -10
    abs = 1
    abs(-10)  # TypeError: 'int' object is not callable
    
    • 可见函数名本身也是变量,我们可以改变它的指向。

    (3)高阶函数

    def add(x, y, fn):
        return fn(x) + fn(y)
    res2 = add(-5, 6, abs)
    print('res2', res2)
    
    • 既然变量可以指向函数,函数参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。函数式编程就是指这种高度抽象的编程范式。
    • 高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁。

    (4)高阶函数 map()

    • map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列中的每个元素上,并把结果作为新的Iterator返回。
    def square(x):
        return x*x
    res3 = map(square, [1,2,3,4,5,6,7,8,9,10])
    print('列表元素求平方:', list(res3))  
    # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
    
    def money(num):
        return '¥'+str(num)
    res4 = map(money, [1.9, 9.9, 19.9, 29.9, 39.9])
    print('列表元素字符串处理:', list(res4))
    # ['¥1.9', '¥9.9', '¥19.9', '¥29.9', '¥39.9']
    

    (5)高阶函数 reduce()

    • reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算。
    from functools import reduce
    
    def sum(x, y):
        return x + y
    res5 = reduce(sum, [1, 2, 3, 4, 5])
    print('[1, 2, 3, 4, 5]求和:', res5)  # 15
    
    def deci(x, y):
        return x*10 + y
    res6 = reduce(deci, [1, 2, 3, 4, 5, 0, 0])
    print('res6', res6)  # 1234500
    

    (6)高阶函数 filter()

    • filter()函数,也接收一个函数参数和一个序列参数,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
    def is_odd(n):
        return n % 2 == 1
    res7 = filter(is_odd, [1,2,4,5,6,9,10,15])
    print('去除列表中的偶数:', list(res7))  # [1, 5, 9, 15]
    
    def not_empty(s):
        return s and s.strip()
    res8 = filter(not_empty, ['A','','B','','','C'])
    print('去除空串元素:', list(res8)) # ['A', 'B', 'C']
    

    (7)高阶函数 sorted()

    • sorted()函数就可以对 list 进行排序。
    res9 = sorted([36, 5, -12, 9, -20])
    print('sorted排序:', res9) # [-20, -12, 5, 9, 36]
    
    • sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。
    res10 = sorted([36, 5, -12, 9, -20], key=abs)
    print('sorted排序:', res10) # [5, 9, -12, -20, 36]
    
    res11 = sorted(['bob', 'about', 'Zoo', 'Credit'])
    print('默认ASCII的大小比较排序:', res11)
    # ['Credit', 'Zoo', 'about', 'bob']
    
    • 根据ASCII码大小,'Z' < 'a',所以 Z 会排在 a 的前面。
    res12 = sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
    print('忽略大小写的排序:', res12)
    # ['about', 'bob', 'Credit', 'Zoo']
    
    res13 = sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
    print('忽略大小写、反向排序:', res13)
    # ['Zoo', 'Credit', 'bob', 'about']
    

    2、高阶函数(函数作为返回值)

    (1)函数作为返回值

    • 高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
    def lazy_sum(*args):
        def sum():
            ax = 0
            for n in args:
                ax += n
            return ax
        return sum
    
    f1 = lazy_sum(1,2,3,4,5)
    res1 = f1()
    print('1,2,3,4,5求和的结果是:', res1) # 15
    
    • 在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

    (2)闭包

    • 当return返回的函数引用了当前函数的局部变量时,就产生了闭包。返回闭包时,要牢记一点:在返回函数中不要引用任何循环变量、或者后续会发生变化的变量。看如下这个反例:
    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', f1())  # 9
    print('调用f2', f2())  # 9
    print('调用f3', f3())  # 9
    
    • 说明:上述三个函数都返回9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。因此,在返回函数中不要引用任何会发生变化的局部变量。

    3、匿名函数(lambda 关键字)

    • 把函数当作参数传入时,有些时候,不需要显式地定义这个函数参数,直接传入匿名函数更方便。
    res = map(lambda x: x*x, [1,2,3,4,5,6])
    print('res', list(res)) # [1, 4, 9, 16, 25, 36]
    
    • 关键字lambda表示匿名函数,冒号前面的x表示函数参数。匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。
    • 匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数
    f = lambda x: x*x
    print('5的平方:', f(5))  # 25
    
    • 还可以把匿名函数作为返回值返回。
    def build(x,y):
        return lambda : x*x + y*y
    print('3和4的平方和:', build(3,4)())  # 25
    
    • lambda 关键字表示匿名函数,冒号前的是参数,冒号后的是返回值。

    4、装饰器(函数修饰)

    (1)函数对象的 __name__ 属性

    • 函数是一个对象,函数对象可以被赋值给变量,所以通过变量也能调用该函数。
    def now():
        print('执行了')
    f = now
    now()  # 执行了
    f()  # 执行了
    print('__name__ ', now.__name__)  # 'now'
    print('__name__ ', f.__name__)  # 'now'
    

    (2)装饰器

    • 在代码运行期间动态地增强函数功能,比如在函数调用前后自动打印日志等,这被称之为“装饰器”。本质上,decorator装饰器就是一个返回函数的高阶函数。
    • 定义一个能够打印日志的装饰器
    import functools
    def log(fn):
        @functools.wraps(fn)  # 解决被装饰器修饰后的函数__name__发生变化的问题。
        def wrapper(*args, **kw):
            print('日志打印:call %s()' % fn.__name__)
            return fn(*args, **kw)
        return wrapper
    
    • @ 语法使用装饰器,@ 语法相当于 now = log(now)
    @log
    def now():
        print('函数打印:2020-04-10')
    
    now()
    # 日志打印:call now()
    # 函数打印:2020-04-10
    print('now的__name__', now.__name__) # 'now'
    

    (3)给装饰器传入参数

    • 如果装饰器本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,下面这个日志装饰器,且日志内容是动态的:
    import functools
    def log2(text):
        def decorator(fn):
            @functools.wraps(fn)  # 解决被装饰器修饰后的函数__name__发生变化的问题。
            def wrapper(*args, **kw):
                print('日志打印:%s %s()' %(text, fn.__name__))
                return fn(*args, **kw)
            return wrapper
        return decorator
    
    • @ 语法使用装饰器,相当于 now2 = log2('自定义日志内容')(now)
    @log2('自定义日志内容')
    def now2():
        print('函数打印:2020-04-10')
    
    now2()
    # 日志打印:自定义日志内容 now2()
    # 函数打印:2020-04-10
    print('now2的__name__', now2.__name__) # 'now2'
    
    • @functools.wraps(fn) 用于解决被装饰器修饰后的函数name发生变化的问题。
    • decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。

    5、偏函数(functools.partial)

    • 在介绍函数参数的时候,我们知道通过设定参数的默认值,可以降低函数调用的难度。而偏函数(Partial function)也可以做到这一点。
    • functools.partial 可以帮助我们创建一个偏函数,看下面的示例:
    import functools
    int2 = functools.partial(int, base=2)
    print('转成十进制', int2('1000000'))  # 64
    print('转成十进制', int2('1010101'))  # 85
    
    print('转成十进制', int2('1010101', base=10))  # 1010101
    
    • 简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。因此上述使用偏函数来给定默认参数,等价于下面的代码:
    def int3(x, base=2):
        # int() 可以给第二个参数base,用于指定按N进制进行转化
        return int(x, base)
    print('转成十进制', int3('1000000'))  # 64
    print('转成十进制', int3('1010101'))  # 85
    
    print('转成十进制', int3('1010101', base=10))  # 1010101
    
    • 偏函数的作用总结:当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

    参考资源:
    1、廖雪峰Python教程
    2、Python官方文档


    END!!!

    相关文章

      网友评论

          本文标题:Python函数式编程

          本文链接:https://www.haomeiwen.com/subject/vhtcmhtx.html