Loading

Python学习笔记(六)之函数式编程

0. Python函数式编程思维导图

0.1 导图链接

Python函数式编程

0.2 导图截图

  • 可能不够清晰看链接就好啦

TIM截图20200120001944.png

1.函数式编程(Functional Programming)

  • 在编程语言中(抽象程度是相对于计算机来说的):
    • 越低级的语言,越贴近计算机,抽象程度越低,执行效率越高,如C语言;
    • 越高级的语言,越贴近数学计算,抽象程度高,执行效率越低,如Lisp。
  • 函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量。
  • 函数式编程特点
    • 允许将一个函数作为参数传入到另一个函数中。
    • 允许返回一个函数。
  • Python对函数式编程提供部分支持,由于Python允许使用变量,所以它不是存函数式编程。

2. 高阶函数

2.1 高阶函数是什么?

  • 首先了解两个概念

    1. 变量可以指向函数

      • 例2.1.1:

        >>> abs
        <built-in function abs>
        >>> a = abs
        >>> a(-1)
        1
        
      • 分析:函数本身可以赋值给变量。上述例子中被赋值的变量a可以当做abs函数本身来使用。

    2. 函数名也是变量

      • 例2.1.2:

        >>> abs = 10
        >>> abs(-1)
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        TypeError: 'int' object is not callable
        
      • 分析:这里的函数名abs指向了数字10后,我们再以函数的形式调用它,就已经不起作用了,因为abs已经不指向求绝对值的函数,而是指向了10这个整数!说明函数名本身也是变量

  • 搞懂了上面两个概念后,我们再回过头来看高阶函数。

    • 一个函数可以接受另一个函数作为参数,这种函数就是高阶函数

    • 例2.1.3:

      #!/user/bin/python
      #coding=utf-8
      
      _author_ = "zjw"
      
      #一个计算两个列表总长度的高阶函数函数
      def allLen(x, y, fun):
          return fun(x) + fun(y)
      
      if __name__ == "__main__":
          li1 = [1, 2, 3]
          li2 = [4, 5]
          print allLen(li1, li2, len)
      
    • 输出

      5

    • 分析:上述高阶函数allLen是一个计算两个列表长度总和的函数。该函数将len函数作为参数传入到allLen函数中,所以是一个高阶函数。

2.2 map

  • 基本形式:map(function, Iterable);

    • function指某个特定的函数。
    • Iterable,即凡是可以使用for循环的对象都是Iterable类型的。如list, tuple, dict, set, str等。
  • map函数返回一个Iterator(即凡是可以使用next()函数的对象都是Iterator类型的)。

    • 注:因为Iterator是一个惰性序列,所以在返回值时可以使用list()函数把整个序列编程一个list型。
  • 函数说明:map将传入的函数依次作用到序列的每一个元素,并把结果作为新的Iterator返回。

  • 实例

    • 例2.2.1:

      #!/user/bin/python
      #coding=utf-8
      
      _author_ = "zjw"
      
      if __name__ == "__main__":
          li = [-1, -3, 2, -3]
          newList = list(map(abs, li))
          print newList
      
    • 输出:

      [1, 3, 2, 3]

    • 分析:上面例子中,map函数的作用是将abs函数作用于li列表的每一个元素,返回一个全部都是绝对值的新列表newList;这里在map函数外还嵌套了一个list函数的作用是将惰性序列转化为列表。

2.3 reduce

  • 基本形式:reduce(function, Iterable);

    • function指某个特定函数。
    • Iterable的说明如上。
  • reduce函数返回的是一个值。

  • 函数说明:。reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,效果如下:

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

    • 例2.3.1:

      #!/user/bin/python
      #coding=utf-8
      
      _author_ = "zjw"
      
      def add(x, y):
          return x + y
      
      if __name__ == "__main__":
          li = range(11)
          sum = reduce(add, li)
          print sum
      
    • 输出

      55

    • 分析:这里的reduce函数是将add函数作为功能函数,一开始传入两个li的元素,得到结果与li的下一个元素再传入add函数中,以此类推,得到最后的总和的结果。

2.4 filter

  • 基本形式:filter(function, Iterable);

  • 函数返回一个Iterator

  • 函数说明:filter()把传入的函数依次作用到每一个元素上,然后根据函数的返回值是True还是False决定保留还是丢弃该元素。即就像一个过滤器一样,过滤掉我们不想要的元素。

  • 实例:

    • 例2.4.1:

      #!/user/bin/python
      #coding=utf-8
      
      _author_ = "zjw"
      
      #判断是否为偶数,偶数返回True,反之返回False
      def isEven(x):
          return x % 2 == 0
      
      if __name__ == "__main__":
          li = [1, 2, 3, 4]
          newList = list(filter(isEven, li))
          print(newList)
      
    • 输出:

      [2, 4]

    • 分析:这里的isEven函数是一个功能函数,用来判断是否为偶数,再通过filter函数进行筛选,将li列表中不是偶数的元素筛选掉,左右输出的是只有偶数的序列。

2.5 sorted

  • sorted (iterable, *, key=None, reverse=False)

    • iterable 即一个可以使用for循环的序列。
    • *是一个特殊分隔符,后面的两个参数时命名关键字参数。
      • key中可以传入一个函数,用来指定排序规则,即映射函数,是排序的灵魂所在。
      • reverse则是决定最后输出序列是递增还是递减序列,默认是递增,即reverse = False。想要改成递减即reverse = True即可。
  • 函数返回一个递增或递减序列。

  • 函数说明:排序。

  • 实例:

    • 例2.5.1:

      >>> li = ['Z', 'a', 'z', 'c', 'A']
      >>> sorted(li)
      ['A', 'Z', 'a', 'c', 'z']
      
      • 分析:默认情况下对字符串的排序是按照ASCII的大小进行排序的,其中大写字母'Z'<'a',所以大写Z排在小写a之前。且默认情况下排序结果是递增序列。
    • 改进2.0,例2.5.2:

      >>> li = ['Z', 'a', 'z', 'c', 'A']
      >>> sorted(li)
      ['A', 'Z', 'a', 'c', 'z']
      >>> sorted(li, key = str.lower)
      ['a', 'A', 'c', 'Z', 'z']
      
      • 分析:通过key关键字参数规定了排序规则为,将所有字母都变成小写字母后再进行排序,且最后得到的排序序列依旧是原来的字母。
    • 改进3.0,例2.5.3:

      >>> li = ['Z', 'a', 'z', 'c', 'A']
      >>> sorted(li)
      ['A', 'Z', 'a', 'c', 'z']
      >>> sorted(li, key = str.lower)
      ['a', 'A', 'c', 'Z', 'z']
      >>> sorted(li, key = str.lower, reverse = True)
      ['Z', 'z', 'c', 'a', 'A']
      
      • 分析:这次将reverse改成True,则函数根据key规则调整后,又将序列通过逆序输出了出来。

3. 返回函数

3.1 返回函数是什么?

  • 顾名思义:函数作为返回值的函数。相当于函数中嵌套一个函数,然后实现该函数后,并返回该函数。

  • 将函数作为结果返回也是高阶函数的另一个特性。

3.2 返回函数实例分析

  • 例3.2.1:

    #!/user/bin/python
    #coding=utf-8
    
    _author_ = "zjw"
    
    
    def lazy_sum(*args):
        def sum():
            ax = 0
            for n in args:
                ax = ax + n
            return ax
        return sum
    
    if __name__ == "__main__":
        f1 = lazy_sum(1, 2, 3, 4)
        f2 = lazy_sum(5, 6, 7, 8)
        print f1, f2
        print f1(), f2()
    
  • 输出:

    <function sum at 0x0000000002F785F8> <function sum at 0x0000000002F78668>
    10 26

  • 分析:

    • 可以看到,当我们调用f1和f2的时候,得到的是一个函数本身,即lazy_sum内部函数sum。
    • 当我们想要得到内部函数sum所求的值时,我们需要调用f1()f2()来得到。
    • 值得注意的是,这里在函数lazy_sum中又定义了函数sum,且内部函数sum可以引用外部函数 lazy_sum的参数和局部变量。当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”。

4. 匿名函数(lambda)

4.1 匿名函数是什么?

  • 所谓匿名,匿名,就是将名字隐藏,或则直接说它是没有名字的函数也不为过。哈哈,Python的匿名函数可以说所有的匿名函数的名字统一都是lambda
  • 匿名函数的存在,将已经非常简约的函数又一次变得更简约,且在某些函数使用次数不多的情况下,可以随心所欲的创建和使用。
  • 相对于java来说,Python只对匿名函数提供了有限的支持。因为Python中的匿名函数只能够有一个表达式。

4.2 匿名函数实例分析

  • 例4.2.1:

    >>> list(map(lambda x : 2 * x, [1, 2, 3, 4, 5]))
    [2, 4, 6, 8, 10]
    
    • 分析:其中map函数和list的转化已经比较熟悉了。那么来看一下这里的lambda函数lambda x : 2 * x,它就相当于一下的函数:

      def f(x):
          return 2 * x
      
    • 其中lambda函数中的关键字lambda就是用来昭示匿名函数,冒号前面的x表示要传入函数的参数,冒号后的2 * x表示的就是return的内容。

  • 例4.2.2

    >>> f = lambda x, y : x + y
    >>> f(1, 2)
    3
    >>> f('a', 'b')
    'ab'
    
    • 分析:如该lambda函数表达式所示,函数传入两个数据,中间用,号隔开,并返回两个数据的和,当然和也可以是字符串的和。且在该例子中,我们将该匿名函数作为一个对象赋值给了变量f,调用变量f即可调用我们创建的匿名函数。该函数的原型应该如下:

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

4.3 匿名函数注意点

  • 好处:
    • 因为匿名函数没有名字,所以不必担心函数名冲突的问题。
    • 匿名函数也是一个函数对象,可以赋值给一个变量,再利用变量来调用函数。
  • 需要注意的是,匿名函数有一个限制,就是只能有一个表达式,该表达式的计算结果就是该匿名函数的返回结果。所以只有一些简单的情况下可以使用匿名函数。

5. 装饰器(Decorator)

5.1 装饰器是什么?

  • 首先来写一个简单的函数:

    def now():
        print("这里是now函数,时间是2020年1月19日21:57:19。")
    
  • 现在我们有了一个简单的now()函数,当我们想在这个函数的前后加一些装饰,即给他加一些说明时,我们往往会在now函数里面动手脚,但是当我们想要在不动now函数本体的情况下进行一些修改时,我们就要用到装饰器了。

  • 这种在代码运行期间动态增加功能的方式,称之为装饰器

5.2 装饰器实例分析

  • 实例1:两层嵌套decorator用法

    • 例5.2.1:

      #!/user/bin/python
      #coding=utf-8
      
      _author_ = "zjw"
      
      #用作now()函数的装饰函数
      def log(func):
          def wrapper(*args, **kw):
              print('call %s():' % func.__name__)
              return func(*args, **kw)
          print("现在执行的是wrapper函数")
          return wrapper
      
      @log
      def now():
          print("2020 1 19")
      
      if __name__ == "__main__":
          now()
      
    • 输出:

      现在执行的是wrapper函数
      call now():
      2020 1 19

    • 分析:在调用now函数的时候,实际上我们先调用了@log中的函数内容。Python就是通过@语法,把decorator置于函数的定义处的,并在调用该函数的时候,实际上是执行了now = log(now)
      之所以我们看到先执行了wrapper函数,是因为我们进入到log函数后,先跳过了wrapper函数,执行了print("现在执行的是wrapper函数"),在返回函数wrapper的时候调用了wrapper函数,并输出了下面的内容。我们看到wrapper函数的参数是(*args, **kw),所以该函数可以接受任意参数的调用。

  • 实例2:三层嵌套decorator用法

    • 实例5.2.2:

      #!/user/bin/python
      #coding=utf-8
      
      _author_ = "zjw"
      
      #用作now()函数的装饰函数
      def log(text):
          def decorator(func):
              def wrapper(*args, **kw):
                  print('%s %s():' % (text, func.__name__))
                  return func(*args, **kw)
              print('我现在执行的是decorator函数')
              return wrapper
          print("我现在执行的还是log函数")
          return decorator
      
      @log('execute')
      def now():
          print("2020 1 19")
      
      if __name__ == "__main__":
          now()
      
    • 输出:

      我现在执行的还是log函数
      我现在执行的是decorator函数
      execute now():
      2020 1 19

    • 分析:3层decorator调用实际上效果是:now = log('execute')(now)。首先我们看到执行的是log函数,因为log函数要传参进去,我们传了一个'execute'信息进去,首先我们跳过里面的两层嵌套循环来到了print("我现在执行的还是log函数")语句,在要返回decorator函数时我们进入到decorator函数,其中的func参数就是now函数了,这时我们又先跳过了wrapper函数执行了print('我现在执行的是decorator函数')语句,在返回wrapper函数的时候进入到wrapper函数,执行了wrapper函数的print('%s %s():' % (text, func.__name__))语句,并进入func函数,即执行了now函数中的内容,最后层层返回,先返回到decorator函数,再最后退出了log装饰器。

6. 偏函数(Partial function)

6.1 老规矩,偏函数是什么?

  • 就是当函数的参数太多的时候,或则我们需要在某些时候经常改变某些参数的时候,我们需要简化,这时候可以使用(functools.partial)创建一个新的函数,这个函数可以固定住原来的参数的部分参数,从而使我们调用的时候更加的简单。

6.2 话不多说,直接上实例

  • 例6.2.1:这里以int()函数为例,当我们需要转换大量的二进制字符串时,每次传入int(x, base = 2)非常麻烦,这时可以用下面方法解决。

    #!/user/bin/python
    #coding=utf-8
    
    _author_ = "zjw"
    
    import functools
    
    if __name__ == "__main__":
        int2 = functools.partial(int, base = 2)
        print(int2('101'))
    
  • 输出:

    5

  • 分析:其中int2 = functools.partial(int, base = 2)语句其实可以用下面的函数代替:

    def int2(x, base = 2):
    	return int(x, base)
    

    两个的实现方式不同,但是能用一行代码解决的我们坚决不用两行。但是呢这只是暂时固定住函数的base参数,如果我们更改base值,调用时也是会发生变化的,如下:

    #!/user/bin/python
    #coding=utf-8
    
    _author_ = "zjw"
    
    import functools
    
    if __name__ == "__main__":
        int2 = functools.partial(int, base = 2)
        print(int2('101', base = 8))
    

    此时输出的是65了,即八进制转为十进制。

6.3 偏函数小总结

  • 当函数参数个数太多,我们需要简化是,我们不妨用上偏函数,使用functools.partial来创建一个新的函数,来固定住原函数中的某些参数,实现新的通用的功能。且用该创建方法更加的简洁,实则就是’节约代码行数‘啦。
posted @ 2020-01-19 23:21  August_丶  阅读(175)  评论(0编辑  收藏  举报