Python3简明教程之4函数式编程

Python3之函数式编程

高阶函数

高阶函数英文叫Higher-order function。

什么是高阶函数?

我们以实际代码为例子,一步一步深入概念。

变量可以指向函数

以Python内置的求绝对值的函数abs()为例,

调用该函数用以下代码:

>>> abs(-10)

10

但是,如果只写abs呢?

>>> abs

<built-in function abs>

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

 

 

如果把函数本身赋值给变量呢?

>>> f = abs

>>> f

<built-in function abs>

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

 

 

如果一个变量指向了一个函数,

那么,是否可以通过该变量来调用这个函数?

用代码验证一下:

>>> f = abs

>>> f(-10)

10

成功!说明变量f现在已经指向了abs函数本身。

直接调用abs()函数和调用变量f()完全相同。

 

函数名也是变量

那么函数名是什么呢?

函数名其实就是指向函数的变量!

对于abs()这个函数,完全可以把函数名abs看成变量,

它指向一个可以计算绝对值的函数!

 

如果把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这个变量已经不指向求绝对值函数而是指向一个整数10

 

这个例子说明函数名也是变量。

要恢复abs函数,请重启Python交互环境。

 

传入函数

既然变量可以指向函数,函数的参数能接收变量,

那么一个函数就可以接收另一个函数作为参数,

这种函数就称之为高阶函数

函数的参数也可以是一个函数

 

一个最简单的高阶函数:

def add(x, y, f):

    return f(x) + f(y)

当我们调用add(-1, 2, abs)时,参数xyf分别接收-12abs

根据函数定义,我们可以推导计算过程为:

x = -1

y = 2

f = abs

f(x) + f(y) ==> abs(-1) + abs(2) ==> 3

 

 

返回函数

函数返回值

python就可以返回int,str,list,dict等类型数据,

python还支持返回函数

 

高阶函数除了可以接受函数作为参数外,

还可以把函数作为结果值返回

 

python返回函数

基本语法

def f():

    print ('call f()...')

    # 定义函数h:

    def h():

        print( 'call h()...')

    # 返回函数h:

    return h

这里,最外层的函数f会返回一个函数h,也就是函数h本身;

 

x = f()  # 调用f()

print(x)  # 变量x就是上面一个最外层函数返回的函数(不是函数值)

x()  # x指向函数,因此可以调用,这里就是调用内层函数h()的过程

 

只返回函数的作用

返回函数可以把一些计算延迟执行

如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?

可以不返回求和的结果,而是返回求和的函数:

 

例如,如果定义一个普通的求绝对值函数:

def calc_abs(lst):

    return abs(lst)

>>>calc_abs(-10)

结果直接是:10

 

但是,可以通过返回函数思想写代码,就可以“延时计算”

def calc_abs(lst):

    def lazy_abs():

        return abs(lst)

    return lazy_abs

下面是调用:

注意,下面代码并没有对函数进行执行计算出结果,而是返回函数。

>>> f = calc_abs(-10)

>>> f

<function lazy_sum at 0x1037bfaa0>

1

2

3

对返回的函数进行调用时,才计算出结果

>>>f()

10

 

 

闭包

 

闭包的概念

闭包(Closure)是词法闭包(Lexical Closure)的简称,

是引用了自由变量的函数。

这个被引用的自由变量将和这个函数一同存在,

即使已经离开了创造它的环境也不例外。

所以,闭包是由函数和与其相关的引用环境组合而成的实体。

闭包预览

以下是一段简单的闭包代码示例:

def foo():

    m = 3

    n = 5

    def bar():

        a = 4

        return m+n+a

    return bar

 

>>>bar =foo()

>>>bar()

12

说明:

bar在foo函数的代码块中定义。

我们称bar是foo的内部函数

 

在bar的局部作用域中可以直接访问foo局部作用域中定义的m、n变量。

简单的说,

内部函数可以使用外部函数变量的行为,就叫闭包

 

 

怎么创建闭包

在Python中创建一个闭包可以归结为以下三点:

 

  1. 闭包函数必须有内嵌函数
  2. 内嵌函数需要引用该嵌套函数上一级函数中的变量
  3. 闭包函数必须返回内嵌函数

 

 

闭包案例一

注意到返回的函数在其定义内部引用了局部变量args,

所以,当一个函数返回了一个函数后,

其内部的局部变量还被新函数引用。

 

另一个需要注意的问题是,返回的函数并没有立刻执行,

而是直到调用了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个函数都返回了。

 

你可能认为调用f1(),f2()和f3()结果应该是1,4,9,

但实际结果是:

>>> f1()

9

>>> f2()

9

>>> f3()

9

全部都是9!

原因就在于返回的函数引用了变量i,但它并非立刻执行

等到3个函数都返回时,

它们所引用的变量i已经变成了3,因此最终结果为9。

 

返回闭包时牢记的一点就是:

返回函数不要引用任何循环变量,

或者后续会发生变化的变量。

闭包案例

如果一定要引用循环变量怎么办?

方法是再创建一个函数,

用该函数的参数绑定循环变量当前的值,

无论该循环变量后续如何更改,已绑定到函数参数的值不变。

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()

>>> f1()

1

>>> f2()

4

>>> f3()

9

 

闭包小结

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

 

 

 

 

 

 

闭包(closure)是函数式编程的重要的语法结构,Python也支持这一特性,下面就开始介绍Python中的闭包。

 

 

在开始介绍闭包之前先看看Python中的namespace。

 

Python中的namespace

 

Python中通过提供 namespace 来实现重名函数/方法、变量等信息的识别,其一共有三种 namespace,分别为:

 

local namespace: 作用范围为当前函数或者类方法

global namespace: 作用范围为当前模块

build-in namespace: 作用范围为所有模块

当函数/方法、变量等信息发生重名时,Python会按照 “local namespace -> global namespace -> build-in namespace”的顺序搜索用户所需元素,并且以第一个找到此元素的 namespace 为准。

 

同时,Python中的内建函数locals()和globals()可以用来查看不同namespace中定义的元素。

 

 

匿名函数

匿名函数的概念

当我们在传入函数时,

有些时候,不需要显式地定义函数,直接传入匿名函数更方便。

 

在Python中,对匿名函数提供了有限支持。

还是以sum(x,y)函数为例,计算sum(x,y)=x+y时,

除了定义一个sum(x,y)的函数外,还可以直接传入匿名函数:

>>> m=(lambda x,y: x + y)

>>> print(m(2,3))

 

通过对比可以看出,匿名函数lambda x,y: x + y实际上就是:

def sum(x,y):

    return x + y

关键字lambda表示匿名函数,冒号前面的x,y表示函数参数。

 

匿名函数的特点

匿名函数有个限制,就是只能有一个表达式

不用写return,返回值就是该表达式的结果。

 

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突

 

匿名函数赋值给变量

此外,匿名函数也是一个函数对象,

也可以把匿名函数赋值给一个变量

再利用变量来调用该函数:

>>> f = lambda x: x * x

>>> f

<function <lambda> at 0x101c6ef28>

>>> f(5)

25

 

匿名函数作为返回值

同样,也可以把匿名函数作为返回值返回,比如:

def build(x, y):

    return lambda: x * x + y * y

 

 

 

装饰器

装饰器的概念

装饰器本质上是一个Python函数,

它可以让其他函数在不需要做任何代码变动的前提下增加额外功能

装饰器的返回值也是一个函数对象。

 

它经常用于有切面需求的场景

比如:插入日志、性能测试、事务处理等场景。

装饰器 是解决这类问题的绝佳设计,有了装饰器,

就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

 

概括的讲,

装饰器的作用就是为已经存在的函数或对象添加额外的功能

怎么写一个装饰器

def debug(func):

    def wrapper():

        print("[DEBUG]: enter {}()".format(func.__name__))

        return func()

    return wrapper

 

@debug   #Python中支持@语法糖

def say_hello():

print ("hello!")

 

注意:函数对象有一个__name__属性,可以拿到函数的名字

传递一个参数

以上代码是最简单的装饰器,

但是有一个问题:

如果被装饰的函数需要传入参数,那么这个装饰器就坏了。

因为返回的函数并不能接受参数,

这时可以指定装饰器函数wrapper接受和原函数一样的参数,比如:

 

def debug(func):

    def wrapper(something):  # 指定一毛一样的参数

        print "[DEBUG]: enter {}()".format(func.__name__)

        return func(something)

    return wrapper  # 返回包装过函数

 

@debug

def say(something):

print "hello {}!".format(something)

 

传递任意参数

这样你就解决了一个问题,但又多了N个问题。

因为函数有千千万,你只管你自己的函数,

别人的函数参数是什么样子,无法知晓?

幸好,Python提供了可变参数*args关键字参数**kwargs

有了这两个参数,装饰器就可以用于任意目标函数了。

 

def debug(func):

    def wrapper(*args, **kwargs):  # 指定宇宙无敌参数

        print("[DEBUG]: enter {}()".format(func.__name__))

        print('Prepare and say...',)

        return func(*args, **kwargs)

    return wrapper  # 返回

 

@debug

def say(something):

    print("hello {}!".format(something))

 

 

装饰器小结

在面向对象(OOP)的设计模式中,decorator被称为装饰模式

OOP的装饰模式需要通过继承和组合来实现,

而Python除了能支持OOP的decorator外,

直接从语法层次支持decorator。

 

 

 

 

 

偏函数

Python的设计核心原则就是简洁

在这种原则的指导下,诞生了lambda表达式偏函数

二者都让函数调用变得简洁。

接下来介绍偏函数的应用。

 

偏函数的概念

如果我们定义了一个函数,

比如说将四个数相加add(one ,two,three,four),

上层有很多函数需要调用这个函数。

在这些调用中,80%的调用传递的参数都是one=10,two=20,

如果我们每都输入相同的参数,这样乏味而且浪费,

固然,我们可以通过默认参数来解决这个问题;

但是如果另外我们也需要参数是one=20,two=10的情况呢?

所以,我们需要一种函数,

能够将任意数量的参数的函数转化成带剩余参数的函数对象

 

简单的说,偏函数就是某一种函数带有固定参数的实现

所以,我们需要:

1)给偏函数命名

2)传递固定参数

 

偏函数的实现

functools.partial可以设置默认参数关键字参数默认值

 

Python的functools模块提供了很多有用的功能,

其中一个就是偏函数(Partial function)。

 

通过设定参数的默认值,可以降低函数调用的难度,

而偏函数也可以做到这一点。

举例如下:

int()函数可以把字符串转换为整数,

当仅传入字符串时,int()函数默认按十进制转换:

>>> int('12345')

12345

 

但int()函数还提供额外的base参数,默认值为10。

如果传入base参数,就可以做N进制的转换:

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

5349

>>> int('12345', 16)

74565

 

假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,

于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:

def int2(x, base=2):

    return int(x, base)

这样,我们转换二进制就非常方便了:

>>> int2('1000000')

64

 

functools.partial帮助我们创建了一个偏函数,

不需要我们自己定义int2(),

可以直接使用下面的代码创建一个新的函数int2:

>>> import functools

>>> int2 = functools.partial(int, base=2)

>>> int2('1000000')

64

>>> int2('1010101')

85

 

 

 

 

 

偏函数小结

 

简单总结functools.partial的作用就是,

把一个函数的某些参数(不管有没有默认值)给固定住(也就是设置默认值),

返回一个新的函数,调用这个新函数会更简单。

 

最后,创建偏函数时,要从右到左固定参数,

就是说,对于函数f(a1, a2, a3),

可以固定a3,也可以固定a3和a2,

也可以固定a3,a2和a1,

但不要跳着固定,比如只固定a1和a3,把a2漏下了。

 

当函数的参数个数太多,需要简化时,

使用functools.partial可以创建一个新的函数,

这个新函数可以固定住原函数的部分参数,

从而在调用时更简单。

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

福优学苑@音视频+流媒体

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值