• 中文
    • English
  • 注册
  • 查看作者
    • 第十二章:函数式编程

      前言

      函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数

      一. 高阶函数

      函数名其实就是指向函数的变量,所以函数本身也可以赋值给变量,即变量可以指向函数

      print(abs(-10))  # 10
      x = abs
      print(x(-10)) # 10

      如果一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数

      def add(a,b,c):  
          return c(a) + c(b)
      print(add(1,2,abs)) # 输出3 ,变量c接受abs作为参数

      二. map/reduce

      map()接收两个参数,一map将传入的第一个参数的函数依次作用到第二个参数序列的每个元素中,并把结果作为新的Iterator返回,比如把一个list中的数字全部变成字符串,其中map()传入的第一个参数是str,即函数对象本身。由于结果x是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list:

      from collections.abc import Iterator
      L = [1,2,3,4,5,6,7,8]
      x = map(str,L)
      print(x) # <map object at 0x0000026E83D4BDC0>
      print(type(x)) # <class 'map'>
      print(isinstance(x,Iterator)) # True
      print(list(x)) # ['1', '2', '3', '4', '5', '6', '7', '8']

      而reduce把一个函数作用在一个序列[x1, x2, x3, …]上,把结果继续和序列的下一个元素做累积计算,这个函数必须接收两个参数,其效果就是:reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

      from  functools import reduce
      
      def add(x,y):
          return  x + y
      
      sum = reduce(add, list(range(1,5)))  # list = [1,2,3,4]
      
      print(sum) # 10

      设计一个str转换为int的函数:

      from  functools import reduce
      
      DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
      def fn(x, y):
          return x * 10 + y
      
      def char2num(s):
          digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
          return digits[s]
      print(list(map(char2num, '13579'))) # [1, 3, 5, 7, 9]
      print(reduce(fn, map(char2num, '13579'))) # 13579

      整理成一个函数:

      from functools import reduce
      
      DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
      
      def str2int(s):
          def fn(x, y):
              return x * 10 + y
          def char2num(s):
              return DIGITS[s]
          return reduce(fn, map(char2num, s))

      用lambda函数进一步简化:

      from functools import reduce
      
      DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
      
      def char2num(s):
         return DIGITS[s]
      
      def str2int(s):
          return reduce(lambda x, y: x * 10 + y, map(char2num, s))

      三. filter

      Python内建的filter()函数用于过滤序列,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素,返回一个Iterator,把一个序列中的空字符串删掉:

      def not_empty(s):
          return s and s.strip()
      
      list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))

      四. sorted

      Python内置的sorted()函数可以对list进行排序,此外,sorted是一个高阶函数,可以接受多个参数对list进行处理,再针对处理结果进行排序,例如例如按绝对值大小排序:

      L = [-3, 2, 1, 4, -5, 6]
      print(sorted(L))  # [-5, -3, 1, 2, 4, 6]
      print(sorted(L, key=abs))  # [1, 2, -3, 4, -5, 6]
      print(sorted(L, key=abs, reverse=True))  # [6, -5, 4, -3, 2, 1]

      根据学生姓名和成绩排序:

      L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
      def by_name(t):
          return t[0]
      L2 = sorted(L,key=by_name) 
      print(L2)  #[('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]
      
      def by_socre(s):
          return -s[1]   # -s[1]相当于reverse=True
      L3 = sorted(L,key=by_socre) 
      print(L3)  # [('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]

      五. 返回函数

      高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回,在下面的例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种的程序结构称为“闭包(Closure)”。

      def calc_sum(*args):
         def sum():
             ax = 0
             for i in args:
                 ax += i
             return ax
         return sum
      
      print(calc_sum(1,2,3)) # <function calc_sum.<locals>.sum at 0x000001847BCBB790>  返回的并不是求和结果,而是求和函数
      f = calc_sum(1,2,3)
      f2 = calc_sum(1,2,3)
      print(f) #<function calc_sum.<locals>.sum at 0x000001847BCBB790>
      print(f2) #<function calc_sum.<locals>.sum at 0x000002E654CCB160> 每次调用都会返回一个新的函数,f和f2的调用结果互不影响
      print(f()) # 6  调用函数f时,才真正计算求和的结果

      上例中的返回的函数f并没有立刻执行,而是直到调用了f()才执行,所以返回函数不要引用任何循环变量,或者后续会发生变化的变量。看下例

      def count():
          fs = []
          for i in range(1, 4):
              def f():
                   return i*i
              fs.append(f)
          return fs
      
      f1,f2,f3 = count()  #每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。
      print(f1(),f2(),f3()) # 9 9 9 !原因就在于返回的函数引用了变量i,但它并非立刻执行。
      #等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。

      可以通过下面的一个程序,来看一下程序的执行顺序:

      def createCounter():
          L = [0]
          print("执行了createCounter")
          def counter():
              L[0] += 1
              print("执行了counter")
              return L[0]
          return counter
      
      print(createCounter())
      '''
      输出:
      执行了createCounter
      <function createCounter.<locals>.counter at 0x00000254F97DB790>
      '''
      #———————————————分割线———————————————————
      def createCounter():
          L = [0]
          print("执行了createCounter")
      
          def counter():
              L[0] += 1
              print("执行了counter")
              return L[0]
      
          return counter
      
      
      x = createCounter()
      print(createCounter())
      print(x)
      '''
      输出:
      执行了createCounter
      执行了createCounter
      <function createCounter.<locals>.counter at 0x00000167555CB160>
      <function createCounter.<locals>.counter at 0x00000167555CAA60>
      '''
      #———————————————分割线———————————————————
      def createCounter():
          L = [0]
          print("执行了createCounter")
      
          def counter():
              L[0] += 1
              print("执行了counter")
              return L[0]
      
          return counter
      
      
      x = createCounter()
      print(x())
      
      
      '''
      输出:
      执行了createCounter
      执行了counter
      1
      '''

      如果一定要引用循环变量,可以再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

      def count():
          def f(j):
              def g():
                  return j*j
              return g
          fs = []
          for i in range(1, 4):
              fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
          return fs
      
      f1,f2,f3 = count()
      print(f1(),f2(),f3()) # 1 4 9

      利用闭包返回一个计数器函数,每次调用它返回递增整数[2]:

      错误示例:不能这么写,因为内部函数不能改变外部函数中变量的指向

       def createCounter():
          a = 0
          def counter():
              a = a+1
              return a
          return counter

      法1:使用list可以回避这个问题,修改list中的值,并未改变list的指向。

       def createCounter():
          L = [0]
          def counter():
              L[0] = L[0]+1
              return L[0]
          return counter

      法2:还可以在内部函数中声明变量为nonlocal,让内部函数获得修改权

       def createCounter():
          a = 0
          def counter():
              nonlocal a
              a = a+1
              return a
          return counter

      六. 匿名函数

      关键字lambda表示匿名函数,冒号前面的x表示函数参数。匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

      L = list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
      print(L) # [1, 4, 9, 16, 25, 36, 49, 64, 81]
      
      #lambda x: x * x实际上就是
      def f(x):
          return x * x

      用匿名函数改造下面的代码:

      def is_odd(n):
          return n % 2 == 1
      
      
      L = list(filter(is_odd, range(1, 20)))
      print(L)  # [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
      
      # 改造
      L2 = list(filter(lambda n: n % 2 == 1,range(1,20)))
      print(L2)

      七. 装饰器

      在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator),Decorator的本质是一个返回函数的高阶函数。比如,我们要定义一个能打印日志的decorator:

      def log(func):
          def wrapper(*args,**kw):
              print("我执行了一个函数:",func.__name__) # __name__属性可以拿到函数的名字
              return func(*args,**kw)
          return wrapper
      @log  # 相当于now = log(now)
      def test():
          print("我执行了test")
      
      test()
      '''
      输出:
      我执行了一个函数: test
      我执行了test
      '''

      调用test()函数,不仅会运行test()函数本身,还会在运行test()函数前打印一行日志,如果decorator本身需要传入除函数以外的参数,则程序如下:

      def log(text):
          def decorator(func):
              def wrapper(*args,**kw):
                  print("我执行了一个函数:", func.__name__,"还说了:",text)  # __name__属性可以拿到函数的名字
                  return func(*args,**kw)
              return wrapper
          return decorator
      
      
      
      @log("哈哈哈") # 相当于now = log('哈哈哈')(now)
      def test():
          print("我执行了test")
      
      test()
      '''
      输出:
      我执行了一个函数: test 还说了: 哈哈哈
      我执行了test
      '''

      我们来剖析上面的语句,首先执行log('哈哈哈'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

      最后,我们把需要把原始函数的__name__等属性复制到wrapper()函数中:

      import functools
      
      def log(func):
          @functools.wraps(func)
          def wrapper(*args, **kw):
              print('call %s():' % func.__name__)
              return func(*args, **kw)
          return wrapper
          
          
          
      import functools
      
      def log(text):
          def decorator(func):
              @functools.wraps(func)
              def wrapper(*args, **kw):
                  print('%s %s():' % (text, func.__name__))
                  return func(*args, **kw)
              return wrapper
          return decorator

      请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:

      import time,functools
      def metric(fn):
          @functools.wraps(fn)
          def spendTime(*args,**kw):
              start = time.time()
              result = fn(*args,**kw)
              end = time.time()
              print(fn.__name__,"函数执行时间为",end-start,"ms")
              return  result
          return spendTime
      
      @metric
      def test(name):
          print("我的姓名 = ", name)
      
      
      test("张甲")

      八:偏函数

      functools.partial用于创建偏函数,可以把一个函数的某些参数给固定住(也就是设置默认值),并返回一个新的函数,调用这个新函数会更简单。

      import functools
      
      print(int('12345',base=8)) #5349
      
      def int2(x,base = 8):
          return int(x,base)
      # 上面的int2可以简写为下面的形式:
      int3 = functools.partial(int, base=8)
      print(int2('12345')) # 5349
      print(int3('12345')) # 5349

      参考资料

      [1] 廖雪峰-Python教程

      [2] 廖雪峰-INVINCIBLEwyq

      [3] Python 函数装饰器

    • 0
    • 0
    • 0
    • 784
    • 请登录之后再进行评论

      登录

      赞助本站

      • 支付宝
      • 微信
      • QQ

      感谢一直支持本站的所有人!

      单栏布局 侧栏位置: