Python基础十:函数式编程

Python基础十:函数式编程


Python基础系列内容为学习廖雪峰老师Python3教程的记录,廖雪峰老师官网地址:廖雪峰Python3教程


Author:yooongchun

Email:yooongchun@foxmail.com


  • 高阶函数:高阶函数指的是函数内部接收另一个函数作为参数。

    我们来看这样一个例子:求绝对值之和

    def abs(x):
      if x>=0:
          return x
      else:
          return -x
    
    def sum_abs(x,y,f):
      return f(x)+f(y)

    我们来进行测试:

    >>> def sum_abs(x,y,f):
    ...     return f(x)+f(y)
    ...
    >>> sum_abs(-5,5,abs)
    10

    高阶函数的作用主要就是为了将函数作为参数使用。实际上,函数的名称本身也是一个变量,一个指向函数体的变量,如:

    >>> f=abs
    >>> f(-10)
    10
  • map/reduce

    Python内建了map()reduce()函数。关于这两个函数的详细情况,请参考Google的map educe

    • map的使用示例:假设现在你要对List[1,2,3,4,5,6,7,8,9]进行)=x2操作,一般的,你可以通过循环实现它
    >>> L=[x for x in range(1,11)]
    >>> L
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    >>> LL=[]
    >>> for x in L:
    ...     LL.append(x*x)
    ...
    >>> LL
    [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

    但是,更为简便的是推荐map()方法:

    >>> def f(x):
    ...     return x*x
    ...
    >>> LL=map(f,L)
    >>> list(LL)
    [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

    可见使用map可以将f()函数作用于全体List对象。map返回的是一个Itertor,因而我们需要使用list将其全部序列计算出来并返回一个list。

    • reduce()把一个函数作用在一个序列[x1,x2,x3,...,xn]上,这个函数接收两个参数,reduce把结果继续和序列的下一个元素做累计计算,结果如下:
    reduce(f,[x1,x2,x3,x4])=f(f(f(x1,x2),x3),x4)

    示例1:序列求和

    >>> from functools import reduce
    >>> def add(x,y):
    ...     return x+y
    ...
    >>> reduce(add,[1,2,3,4,5,6,7,8,9,10])
    55

    示例2:把序列[1,3,5,7,9]变换为整数13579

    >>> def trans(x,y):
    ...     return x*10+y
    ...
    >>> reduce(trans,[1,3,5,7,9])
    13579

    综合应用1:写一个函数实现str转换为int

    >>> def list2int(x,y):
    ...     return x*10+y
    ...
    >>> def char2int(s):
    ...     return{'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}[s]
    ...
    >>> def str2int(s):
    ...     return  reduce(list2int,list(map(char2int,s)))
    ...
    >>> str2int('1234567890')
    1234567890

    综合应用2:写一个函数把用户输入不规范的英文名改为首字母大写其余小写的规范写法

    >>> def normalize(s):
    ...     return s[0].upper()+s[1:].lower()
    ...
    >>> list(map(normalize,['adam','LISA','barT']))
    ['Adam', 'Lisa', 'Bart']
  • filter:Python 内建的fileter()函数用于过滤序列。filter函数需要传入两个参数:一个函数和一个序列,其把函数作用于序列中的每个数,然后根据返回值保留返回True的数而删除返回False的数。

    示例1:删除一个序列中的偶数

    >>> def isodd(x):
    ...     return x%2==1
    ...
    >>> list(filter(isodd,[1,2,3,4,5,6,7,8,9]))
    [1, 3, 5, 7, 9]

    示例2:删除一个序列中的空字符串

    >>> def not_empty(s):
    ...     return s and s.strip()
    ...
    >>> list(filter(not_empty,['a',' ','AS',None,'   ','/']))
    ['a', 'AS', '/']

    综合应用:用埃式筛法求素数

    首先,构造一个从3开始的奇数序列

    >>> def _odd_iter():
    ...     n=1
    ...     while True:
    ...             n=n+2
    ...             yield n
    ...

    其次定义一个筛选函数

    >>> def _not_divisiable(n):
    ...     return lambda x:x%n>0
    ...

    定义一个生成器,用以不断产生素数

    >>> def primes():
    ...     yield 2
    ...     it=_odd_iter()
    ...     while True:
    ...             n=next(it)
    ...             yield n
    ...             it=filter(_not_divisiable(n),it)
    ...

    最后,我们来测试

    >>> L=[]
    >>> for n in primes():
    ...     if n<1000:
    ...             L.append(n)
    ...     else:
    ...             break
    ...
    >>> L
    [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]
  • sorted :排序是程序中经常使用的算法,Python内置了sorted函数以实现排序算法。

    list进行排序

    >>> sorted([12,-12,32,45,67,-1212])
    [-1212, -12, 12, 32, 45, 67]

    sorted函数也是一个高阶函数,他可以额外接受一个key函数来实现自定义排序,如按照绝对值排序

    >>> sorted([12,-12,32,45,67,-1212],key=abs)
    [12, -12, 32, 45, 67, -1212]

    对字符串排序时按照首字母的ASCII码值进行排序

    >>> sorted(['bob','about','Zoo','Credit'])
    ['Credit', 'Zoo', 'about', 'bob']

    如果要实现忽略大小写的排序,则可以这样写

    >>> sorted(['bob','about','Zoo','Credit'],key=str.lower)
    ['about', 'bob', 'Credit', 'Zoo']

    当要进行反向排序时,可以传入第三个参数reverse=True

    >>>sorted(['bob','about','Zoo','Credit'],key=str.lower,reverse=True)
    ['Zoo', 'Credit', 'bob', 'about']

    实例:对以下list实现按照名字排序

    L=[('Bob',75),('Adam',99),('Bart',87),('Lisa',89),('Lucy',67),('Nancy',100)]

    >>> def getName(name):
    ...     return [s[0] for s in name]
    ...
    >>> sorted(getName(L),key=str.lower)
    ['Adam', 'Bart', 'Bob', 'Lisa', 'Lucy', 'Nancy']
  • 返回函数:高阶函数除了可以接收函数作为参数外,还可以把函数作为结果值返回。

    示例:来实现这样一个操作:写一个可变参数求和函数

    >>> def calc_sum(*args):
    ...     ax=0
    ...     for n in args:
    ...             ax=ax+n
    ...     return ax
    ...

    但是在某些情况下,函数并不需要立刻进行求和,而是在后面的使用中再进行计算,这时,就可以不返回计算的结果,而是返回求和的函数

    >>> def lazy_sum(*args):
    ...     def sum():
    ...             ax=0
    ...             for n in args:
    ...                     ax=ax+n
    ...             return ax
    ...     return sum
    ...

    调用上面的lazy_sum()时,返回sum()函数

    >>> f=lazy_sum(1,3,5,6,7,9)
    >>> f
    <function lazy_sum.<locals>.sum at 0x00000295A6FC6048>

    调用这个返回的函数,才进行计算

    >>> f()
    31

    上面的程序中,sum()函数定义在lazy_sum()的内部,但是其却可以引用外部函数lazy_sum()的参数和局部变量,当lazy_sum()返回函数sum()时,相关参数和变量都保存在返回的函数中,这种程序结构称为:闭包(Closure)

    闭包: 注意,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。返回的函数没有被立即执行,直到调用返回的函数时才被执行,来看一个例子

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

    按理,不是应该为1 4 9吗,为何都为9呢?结合上面的说明,我们来进行分析,在执行for循环时,流程如下:

    i=1:

    def f():
      return i*i

    关键就在这,此时f()的返回值是多少呢?你可能觉得应该是:1*1,但请注意:返回的是函数,也就是i*i本身,此时结果并未被计算出来,并且,此时的i还会被后面的函数引用。

    i=2

    def f():
      return i*i

    同理:i=3

    def f():
      return i*i

    注意,上面的三个函数并非同一个,而是相互独立的,但是,内部的变量却是同一个,都是母函数count()的变量,那么,当调用这三个函数时,发生了什么?

    >>> f1,f2,f3=count()

    此时上面的三个函数会被依次执行,但是,执行时已经有i=3,所以结果都为:9

    请记住:返回闭包时,不要让返回函数引用任何循环变量,或者是后续会发生变化的变量。

  • 匿名函数:在传入函数时,若不需要显式的定义函数,则可以考虑使用匿名函数。

    示例:对list进行x*x操作

    >>> list(map(lambda x:x*x,[1,2,3,4,5,6,7,8,9]))
    [1, 4, 9, 16, 25, 36, 49, 64, 81]

    此时,lambda x:x*x其实就是替代了f()函数

    def f(x):
      return x*x

    lambda x:x*x属于匿名函数,关键字lambda表示匿名函数,冒号前面的x表示函数的参数而冒号后面的表达式就是匿名函数的返回结果。匿名函数只能有一个表达式。

  • 匿名函数可以进行赋值,比如

    >>> f=lambda x:x*x
    >>> f
    <function <lambda> at 0x00000295A6FC6378>
    >>> f(5)
    25

    匿名函数也可以作为函数进行返回

    >>> def build(x,y):
    ...     return lambda:x*x+y*y
    ...
    >>> f=build(3,4)
    >>> f()
    25
  • 装饰器:在某些特殊的情况下,希望在代码运行期间动态的为函数增加功能但又不修改原函数,这种方式即为:装饰器(Decorator)

    示例:现在,我们需要在调用一个函数时让函数自动打印调用信息

    先来定义一个打印调用函数的装饰器

    >>> def log(func):
    ...     def wrapper(*args,**kw):
    ...             print('calls %s():' % func.__name__)
    ...             return func(*args,**kw)
    ...     return wrapper
    ...

    上面的log()函数接收一个函数参数,然后返回添加了打印功能的函数。

    调用Decrator函数使用@语法,将其置于函数定义处,如下

    @log
    def now():
       print('2017-05-11')

    调用结果:

    >>> now()
    calls now():
    2017-05-11

    其实,将@log放在now()函数定义处就相当于执行了语句

    now=log(now)
  • 偏函数:Python的functools模块提供了偏函数的支持,注意,这里说的偏函数不是数学意义上的偏函数。

    示例:使用int()函数实现由字符串对任意数制的转换

    int()函数提供了这个功能,其接受一个额外的参数base

    >>> int('12345',base=10)
    12345
    >>> int('12345',base=8)
    5349

    假如我们需要大量的把代码转换为2进制,这时我们希望有一个默认函数

    def int2(s):
      return int(s,base=2)
    >>> int2('1000001')
    65

    上面对int2()的定义,其实可以用python语法实现,也即:偏函数

    >>> import functools
    >>> int2=functools.partial(int,base=2)
    >>> int2('1000001')
    65

    这样就为我们省去了定义的麻烦。总结来说就是:当函数的参数太多,需要简化时,使用fooltools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。