廖雪峰Python教程阅读笔记——4. 函数式编程

4函数式编程

函数是面向过程的程序设计的基本基元。而函数式编程-Function Programming,虽然其接近于面向过程的编程,但是更多的是偏于数学计算。

函数式编程是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。

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

Python对函数式编程提供部分支持,由于Python允许使用变量,因此,Python不是纯粹的函数式编程。

4.1高阶函数

高阶函数-Higher-order function。

  • 变量可以指向函数**

    以Python的绝对值函数abs()为例:

    
    >>> abs(-10)
    
    10
    

    但是如果写成:

    
    >>> abs
    
    <built-in function abs>
    

    可见abs(-10)是函数调用,而abs是函数本身。

    要获得函数的调用,我们可以把结果赋值给变量:

    
    >>> x = abs(-10)
    
    >>> x
    
    10
    

    但是如果将函数赋值给一个变量:

    
    >>> f=abs
    
    >>> f(-10)
    
    10
    

    结论:函数本身可以赋值给变量,即:变量可以指向函数。

    • 函数名也是变量

    函数名其实就是名副其实的指向函数的变量。当把函数名abs指向其他对象:

    
    >>> abs = 10
    
    >>> abs(-10)
    
    Traceback (most recent call last):
    
    File "<stdin>", line 1, in <module>
    
    TypeError: 'int' object is not callable
    

    可见,把abs指向10后,就无法通过abs(-10)调用该函数了。

    注:由于abs函数实际上是定义在import builtins模块中的,所以要让修改abs变量的指向在其他模块生效,要用import builtins;builtins.abs = 10

    • 传入函数

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


def add(x,y,f):

   return f(x) + f(y)

例如,调用add(-10,3,abs)


>>>add(-10,3,abs)

13

编写高阶函数就是让函数能接收函数作为参数。

小结:把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。

4.1.1 map/reduce

Python内建了map()reduce()函数。

  • map()函数

    map()函数接收两个参数,一个参数是函数,一个参数是Iterable,map()将传入的函数依次作用于Iterable对象的每个元素,结果返回一个Iterator。例如:


>>> def f(x):

  ... return x*x

>>> r = map(fun,[1,2,3,4])

>>> r

<map object at 0x00000222A759E780>  ##注意,r是一个惰性的序列,无法一次性输出,必须转换成list类型

>>> next(r)

1

>>> next(r)

4

>>> next(r)

9

>>> list(r) ##r的指向改变了,当使用多次next()函数,r指向的不再是原来的Iterator对象。而是类似于栈帧发生位移

[16]

>>> list(r)

[]

>>> list(r)

[]

>>> r = map(fun,[1,2,3,4])

>>> list(r)

[1, 4, 9, 16]

>>>

map()函数作为高阶函数,可以计算任意复杂的函数,比如把list转换为一个字符串:


>>> r1 = map(str,[1,2,3,4,5])

>>> list(r1)

['1', '2', '3', '4', '5']
  • reduce()函数

reduce()接收两个参数,一个是*函数*f,一个是Iterable对象。函数必须接受两个参数,reduce函数每次会把上次函数允许的结果和Iterable对象的下一个元素传入到函数f中。直到Iterable中所有的元素都参与过运算。


>>>from functools import reduce

>>>def testadd(x,y):

    return x+y

>>> reduce(testadd,[1,3,5])

9

再来看一个复杂的例子:把字符串”13579”转换成整数:


def str2num(s):

    def fn(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,'0':0}[s]

    return reduce(fn,map(char2int,s))

运行结果:


>>> str2num('222')

222

可以使用lambda函数进一步简化:


def str2num2(s):

    def char2int(s):

        return {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9,'0':0}[s]

    return reduce(lambda x,y:x*10+y,map(char2ints,s))

4.1.2filter

Python内建了filter函数用于过滤序列。

map类似,filter函数也可以接收一个函数和一个序列。和map()不同的是filter()函数把函数参数作用于序列的每一个元素,判断该元素是否满足函数参数,根据函数参数返回的TrueFalse来决定该元素的去和留

例如:过滤掉序列中的空字符串


>>>def Not_empty(s):

...  return s and s.strip()

>>>list(filter(Not_empty,['A','B','SSSS','    ','AAAAA',None]))

['A','B','AAAAA']

filter()的作用是从一个序列中筛出符合条件元素。使用了惰性计算,所以只有在取filter()结果的时候,才会真正的筛选并每次返回下一个筛除的元素。

4.1.3 sorted

  • 排序算法

    Python内置的函数sorted()就可以对list进行排序。

    
    >>> sorted([10,-9,3,93])
    
    [-9, 3, 10, 93]
    

sorted()函数也是一个高阶函数,他可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:


>>> sorted([10,-9,3,93],key=abs)

[3, -9, 10, 93]

key函数作用于序列的每一个元素,并将结果排序。

对于字符串排序,是按照ASCII的大小进行排序。由于’Z’<’a’,如果我们忽略大小写,对字符串进行排序。如果使用key函数,把字符串映射为忽略大小写排序即可。

忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较:


>>> sorted(['Asss','ass','ZXZ','bs'])   ##字符串排序

['Asss', 'ZXZ', 'ass', 'bs']

>>> sorted(['Asss','ass','ZXZ','bs'],key=str.lower)   ##忽略大小写,排序

['ass', 'Asss', 'bs', 'ZXZ']

4.2返回函数

函数作为返回值

高阶函数除了可以接收函数作为参数外,还可以把函数作为结果返回值。

例如,实现一个可变参数的求和


def lazy_sum(*args):

    def sum():

        ax = 0

        for n in args:

            ax = ax + n

        return ax

    return sum

当我们调用lazy_sum()函数时,返回的并不是求和的结果,而是求和函数。调用返回值时才算是真正的求和。


>>> from test1 import lazy_sum

>>> f = lazy_sum(1,2,3,4)

>>> f

<function lazy_sum.<locals>.sum at 0x000001B6A3BD3E18>

>>> f()

10

在这个例子中,我们在函数lazy_sum()中定义了一个函数sum()内部函数可以引用外部函数lazy_sum()参数局部变量,当lazy_sum()函数返回函数sum时,相关参数变量都保存在返回的函数中,这种称为:闭包(Closure)的程序结构拥有极大的威力

请再注意一点:当我们每次调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:


>>> f2 = lazy_sum(1,2,3,4)

>>> f3 = lazy_sum(1,2,3,4)

>>> f2==f3

False
  • 闭包

    注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回一个函数后,其内部的局部变量还被函数引用。所以,闭包使用起来方便,实现起来可不容易。
    返回闭包时要牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

小结:
一个函数不仅可以返回一个计算结果,也可以返回一个函数。
返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会发生变化的变量。

4.3 匿名函数

当我们传入函数时,有些时候,不需要显示的定义函数,只需要传入匿名函数更方便。在Python中,对匿名函数提供了有限支持。
map()函数为例,计算f(x) = x^2。我们使用匿名函数:

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

关键字lambda表示匿名函数,冒号前面的x表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return语句,返回值就是该表达式的结果。
匿名函数也是一个函数对象,可以把匿名函数赋值给一个变量,再利用该变量来调用函数。

>>> f=lambda x:x*x
>>> f(3)
9

4.4 装饰器

函数对象有个_name_属性,可以拿到函数的名字。
假如有一个函数,打印当前时间:

import time  ##yin
def now():
  print(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())

现在假如我们要增强now()函数的功能,在函数调用前后都自动打印日志,但又不希望更改函数now()的定义,这种在代码运行时动态增加功能的方式,称之为“装饰器”(Decorator)

本质上,Decorator就是一个返回函数的高阶函数。我们如果要定义一个可以打印日志的“装饰器”:

def log(func):
    def wrapper(*args,**kw):
        print("call %s():" % func._name_)
        return func(*args,**kw)
    return wrapper

log()函数,是一个decorator,可以接收一个函数作为参数。我们可以借助Python的@语法,把decorator置于函数的定义处:

@log
def now():
  print(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())

调用now()函数,不仅会允许now()函数本身,还会在运行now()函数前打印一行日志。
由于log()函数,是一个decorator,返回一个函数,因此原来now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()函数将执行新的函数,即log()函数中返回wrapper(*args,**kw)函数。
wrapper(*args,**kw)函数的参数定义是(*args,**kw),因此,该函数可以接受任意参数的调用。在wrapper(*args,**kw)函数内,首先打印日志,再紧接着调用原始函数。
如果decorator本身需要传入参数,那么就需要编写一个返回decorator的高阶函数,写出来会更复杂:

def log(text):
  def decorator(func):
    def wrapper(*args,**kw):
        print("call %s():" % func._name_)
        return func(*args,**kw)
    return wrapper
  return decorator

这个三层嵌套的decorator用法如下:

@log('execute')
def now():
  print(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())

执行结果如下:

>>>now()
execute now():
2017-8-25

小结:
在面向对象(OOP)的设计模式中,decorator被称为装饰模式,OOP的装饰模式需要通过继承组合来实现,而Python除了支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数来实现,也可以用类来实现。
decorator可以增强函数的功能,定义起来稍微有些复杂,但是用的时候非常灵活方便。

4.5偏函数

Python中的functools模块,提供了许多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的不一样。
Python中的int()函数可以将字符串转换为整数。默认是十进制的整数,int()函数还提供额外的base参数,默认为10:

>>> int('123')                                                                                                                              
123                                                                                                                                         
>>> int('123',base=8)                                                                                                                       
83                                                                                                                                          
>>> int('123',base=2)                                                                                                                       
Traceback (most recent call last):                                                                                                          
  File "<stdin>", line 1, in <module>                                                                                                       
ValueError: invalid literal for int() with base 2: '123'                                                                                    
>>> int('100100',base=2)                                                                                                                    
36

假设要转换大量的二进制函数,每次都要传入int('123',base=2),非常麻烦。可以定义一个默认参数的函数:

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

如此,我们转换二进制参数就非常方便了,但是代码量还是非常不方便。我们使用functools.partial,用来创建一个偏函数:

>>> import functools                                                                                                                        
>>> int2 = functools.partial(int,base=2)                                                                                                    
>>> int2('10000100')                                                                                                                        
132

只需要一行代码,无需重新定义一个函数,上面的代码int2是偏函数名。functools.partial()的第一个参数是函数,第二个参数是函数的默认参数。返回值为一个新函数。
该函数仅仅将base=2作为默认函数,也可以使用其他值:

>>>int2('2221111',base=10)

偏函数可以接收函数对象、*args**kw这三个参数。
小结:
当函数参数过多,需要简化时,使用functools.partial()可以创建一个新函数,这个新函数就可以固定原函数的部分参数,从而在调用时更简单。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值