学习笔记: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个参数。

猜你喜欢

转载自www.cnblogs.com/lowkeyao/p/11300441.html