zoukankan      html  css  js  c++  java
  • 学习笔记:Python3 函数式编程

    仅为个人查阅使用,如有错误还请指正。

    函数式编程是一种抽象计算的编程模式。

    函数式编程的特点

    ​ 1、把计算视为函数而非指令。

    ​ 2、纯粹的函数式编程语言编写的函数没有变量。

    ​ 3、支持高阶函数,代码简洁。

    Python支持的函数式编程支持以下特点

    ​ 1、不是纯函数式编程:允许有变量。

    ​ 2、支持高阶函数:函数也可以作为变量传入

    ​ 3、支持闭包:有了闭包就能返回函数。

    ​ 4、有限度的支持匿名函数

    • 高阶函数

      直接上定义:能接收函数做参数的函数。

      开始解释

      • 变量可以指向函数

        说白了就是函数本身可以赋值给变量

        a = abs
        
        print(abs)	# output:<built-in function abs>
        print(a)	# output:<built-in function abs>
        

        既然赋值成功,是不是还可以进行调用呢?

        a = abs
        
        print(a(-10))		# output:10
        

        objk,说明变量a现在已经指向了abs函数本身。调用a()函数和调用abs()完全相同

      • 函数名也是变量

        大致的意思就是,函数名就是指向函数的变量。abs这个变量指向了abs函数。

        也就是说,如果你把abs指向了其他函数,那abs()不在是绝对值函数了。

        实例

        abs = len				# 把abs这个变量指向len函数
        
        print(abs)				# 此时abs它就是一个len函数,<built-in function len>
        print(abs(10))			# 你想要去求绝对值,是会报错的。
        print(abs([1,2,3]))		# 而是需要你求长度。3前面两
        

      应该说的明明白白了。

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

      体会一下最简单的高阶函数

      import math
      
      def sqrt_add(x, y, f):
          return f(x) + f(y)
      
      print(sqrt_add(25, 9, math.sqrt))
      # output:8.0
      
    • Python 内置的四大高阶函数

      • map()

        该函数,它接收一个函数和一个可迭代对象map将传入的函数依次作用到序列的每个元素

        并把结果作为新的迭代器返回。没错,返回的map对象它是一个迭代器

        要获取具体数据,可以通过next()方法。也可以通过list()函数。

        实例

        def f(x):
            return x * x
        
        print(list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
        # output:[1, 4, 9, 16, 25, 36, 49, 64, 81]
        
        # 首字母大写,后续字母小写的规则
        def format_name(s):
            return s[0].upper() + s[1:].lower()
        
        print(list(map(format_name, ['adam', 'LISA', 'barT'])))
        # output:['Adam', 'Lisa', 'Bart']
        
      • reduce()

        该函数接收的参数和map()类似。

        但行为和map()不同,reduce()传入的函数 f 必须接收两个参数,reduce()list的每个元素反复调用函数f,并返回最终结果值。

        用求和,求积来简化对它的理解。当然Python 求和可以直接用sum(),这里是为了理解。

        from functools import reduce
        
        def f(x, y):
            return x + y
        
        print(reduce(f, [1, 3, 5, 7, 9]))
        # output:25
        # 还可以接收第三个参数,作为计算的初始值。
        print(reduce(faa, [1, 3, 5, 7, 9], 100))
        # output:125
        
        # 第二个例子,求积函数
        def prod(x, y):
            return x * y
        
        print(reduce(prod, [2, 4, 5, 7, 12]))
        # 3360
        
      • filter()

        该函数接收的参数和map()类似。这个函数 f 的作用是对每个元素进行判断,返回TrueFalse,如果是True就保留该元素。

        没错,返回的结果跟map()一样,都是属于迭代器。

        实例

        # 从一个list [1, 4, 6, 7, 9, 12, 17]中删除偶数,保留奇数
        
        def is_odd(x):
            return x % 2 == 1
        
        print(list(filter(is_odd, [1, 4, 6, 7, 9, 12, 17])))
        # output:[1, 7, 9, 17]
        
        # 删除None或者空字符串
        def is_not_empty(s):
            return s and len(s.strip()) > 0
        
        print(list(filter(is_not_empty, ['test', None, '', 'str', '  ', 'END'])))
        # output:['test', 'str', 'END']
        

        可见,filter()函数的作用是从一个序列中筛出符合条件的元素。

      • sorted()

        排序算法的核心就是两个数比较大小。如果是比较字符串或者字典呢?用数学上的大小是没有意义的,需要通过抽象函数来比较。

        • 对list进行排序

          print(sorted([23, -454, 11, -6, 5]))
          
          # output:[-454, -6, 5, 11, 23]
          
        • 可以接受key函数来实现自定义排序。

          print(sorted([23, -454, 11, -6, 5], key=abs))
          
          # output:[5, -6, 11, 23, -454]
          # key指定的函数将作用于list的每一个元素上,并把返回的结果进行排序。
          
        • 对字符串进行排序,是按照ASCII的大小比较的。

          现在提出忽略大小写,然后进行排序,我们不需要先把字符串全改小写。

          按照前面一个例子,只要通过key接收的这个函数进行处理即可。

          print(sorted(["harden", "Durant", "jordan", "curry", "O'Neal"], key=str.lower))
          
          # output:['curry', 'Durant', 'harden', 'jordan', "O'Neal"]
          

          综上所述:sorted()函数排序的精髓在于实现一个映射函数。

    • 返回函数

      • 函数作为返回值

        直接用实例来分析

        def f(x):
            print("run f_function ...")
            def g():
            	print("run g_function ...")
                return x
            return g
        
        print(f(5))
        # output:
        run f_function ...
        <function f.<locals>.g at 0x0000017364A0BF28>
        # 当调用f函数时,返回的并不是x的值,而是g函数。
        
        # 要去调用g函数,才会返回x的值
        print(f(5)())
        # output:
        run f_function ...
        run g_function ...
        5 is ok
        

        在这个例子中,函数f中又定义了函数g,并且,内部函数g可以引用外部函数f的参数和局部变量。当f返回函数g 时,相关参数和变量都保存在返回的函数中。这种称为”闭包“

      • 闭包

        闭包的特点是返回的函数引用了外层函数的局部变量参数

        看看简单,其实要用起来还是很难的。

        现在让你用闭包实现一个功能,分别计算1x1,2x2,3x3

        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(), f2(), f3())
        

        你希望的结果是1,4,9。我也希望是这样的。实际输出的是9,9,9

        原因就是当count()函数返回了3个函数时,这3个函数所引用的变量i的值已经变成3。当你去调用的是,f1() --> 返回的值就是9。

        因此,返回函数不要引用任何循环变量,或者后续会发生变化的变量

        改正上面的函数

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

        把原先的return i*i换成了return lambda : i*i。使用lambda是一个匿名函数。

        参数不变的原因是跟函数绑定在一起的值不变。

    • 匿名函数

      map()函数为例,再次来计算f(x) = x²,之前的方法是,定义一个函数。

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

      现在的方式是,直接写一个匿名函数

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

      很显然,lambda x: x * x 就是函数f()

      关键字lambda表示匿名函数,冒号前面的x表示函数参数

      匿名函数有个限制,就是只能有一个表达式,不写return,返回值就是表达式的结果。

      当然,匿名是一个函数对象,就可以赋值给一个变量,再利用变量来调用该函数。

    • 装饰器

      在不修改原函数的前提下,动态的给它新增功能。称之为“装饰器”。

      通过高阶函数返回新函数

      def f1(x):
          return x * 2
      def new_fn(f):
          def fn(x):
              print("call " +  f.__name__ + " ()")
              return f(x)
              
          return fn
      
      g1 = new_fn(f1)
      print(g1(5))
      
      # 这种调用方式的结果:f1的原始定义函数被彻底隐藏了。
      f1 = new_fn(f1)
      print(f1(5))
      

      Python内置的@语法就是为了简化装饰器的调用

      @new_fn == f1 = new_fn(f1)

      • 无参装饰器

        由前面的介绍,我们知道Python的 decorator 本质上就是一个高阶函数, 它接收一个函数作为参数,然后,返回一个新函数。

        引入函数中提到的终极螺旋组合。

        from functools import reduce
        
        def log(f):
            def fn(x):
                print 'call ' + f.__name__ + '()...'
                return f(x)
            return fn
        
        # 首先是factorial函数要去调用是可型的。因为只要一个参数。
        @log
        def factorial(n):
            return reduce(lambda x,y: x*y, range(1, n+1))
        print(factorial(10))
        
        # 那如果add函数也要去调用这个装饰器就会报错,因为有两个参数,不匹配。
        @log
        def add(x, y):
            return x + y
        print(add(1, 2))
        
        

        要让@log自适应任何参数定义的函数,可以用*args**kw,保证任意个数的参数总是能正常调用。

        改正

        def log(f):
            def fn(*args, **kw):
                print('call ' + f.__name__ + '()...')
                return f(*args, **kw)
            return fn
        
      • 带参装饰器

        实例

        def log(prefix):
            def log_decorator(f):
                def wrapper(*args, **kw):
                    print('[%s] %s()...' % (prefix, f.__name__))
                    return f(*args, **kw)
                return wrapper
            return log_decorator
        
        @log('DEBUG')
        def test():
            pass
        print(test())
        

        这是3层嵌套。你只需理解带参装饰器一般都是需要3层循环,可以按照这个模板去写。

        这里简单的讲一下:

        @log('DEBUG')
        def test():
            pass
            
        # 这个定义相当于是    
        test = log('DEBUG')(test)
        # 再进行转换
        log_decorator = log("DEBUG")
        test = log_decorator(test)
        # 再进行转换就是
        log_decorator = log("DEBUG")
        @log_decorator
        def test():
            pass
            
        # 所以,带参数的log函数首先返回一个decorator函数,再让这个decorator函数接收test并返回新函数。
        

        不管有没有理解,现在要去解析带参装饰器实例代码的执行流程。

        首先执行log("DEBUG"),返回的是log_decorator函数,再调用返回的函数,参数是test函数,返回值最终是wrapper函数。

      • 完善装饰器

        以上两张方法其实基本上就完事了,为什么还要完善,是因为对原函数加了装饰器,所以原函

        数的函数名就变成了新函数的函数名。以上一个为例子,test的函数名变成了wrapper

        可以通过__name__.py去获取函数名。这就意味着依赖函数名的代码就会失效,比如

        test.__doc__等其他属性。所以需要把原始函数的__name__等属性复制到wrapper()函数中。

        Python为了方便起见,不需要编写wrapper.__name__ = f.__name

        wrapper.__doc__ = f.__doc__这样的操作。直接导入模块就好了。

        通过functools这个工具来完成这个复制过程。

        import functools
        
        def log(f):
            @functools.wraps(f)
            def fn(*args, **kw):
                print('call ' + f.__name__ + '()...')
                return f(*args, **kw)
            return fn
        
        def log(prefix):
            def log_decorator(f):
                @functools.wraps(f)
                def wrapper(*args, **kw):
                    print('[%s] %s()...' % (prefix, f.__name__))
                    return f(*args, **kw)
                return wrapper
            return log_decorator
        

        以上是修改了装饰器部分。可以看出再指向函数的前面写上方法。

    • 偏函数

      前面提到了functools模块,该模块功能很多,其中一个就是偏函数。

      偏函数的作用就是降低函数调用的难度。

      我在函数那个章节提到过,是通过设置参数默认值来降低函数调用的难度。

      在函数里面提到的int()函数,可以进行二进制转换。但是默认是十进制。

      为了避免每次调用都是int(x, base=2),我们就写一个函数。来定制。

      functools.partial可以把一个参数多的函数变成一个参数少的新函数,少的参数需要在创建时指定默认值,这样,新函数调用的难度就降低了。

      import functools
      
      int2 = functools.partial(int, base=2)
      
      print(int2('1000000'))
      # output:64
      print(int2('1010101'))
      # output:85
      

      最后再补充一点:创建偏函数时,实际上可以接收函数对象、*args**kw这3个参数。

  • 相关阅读:
    欧拉法求乘率
    利用连分数求乘率
    反乘率
    乘率
    别害怕暂时的迷茫
    别害怕心中的理想
    HDU6072 Logical Chain
    P3345 [ZJOI2015]幻想乡战略游戏
    P4449 于神之怒加强版
    [笔记] 拉格朗日插值法
  • 原文地址:https://www.cnblogs.com/lowkeyao/p/11300441.html
Copyright © 2011-2022 走看看