深入详解python高级特性——函数柯里化(Currying)与反柯里化
前言:本章的内容本来很简单,但是涉及到的理论部分相对较多,想要彻底弄懂前因后果需要具备以下几个知识点,
(1)python的高阶函数
(2)python的装饰器本质
(3)Python的functools模块里面的偏函数的本质
这三块类容我在之前的文章中已经有详细说明了,不再赘述,可以参考下面的连接中的文章:
博客专栏分类,关于Python的各种高级特性都有说明
关于functools模块以及偏函数的详细理解
一、什么是函数柯里化(Currying)
函数柯里化是解释型语言常见的一种特性,常见的语言比如python、javascript都支持函数柯里化
有两种理解,当然这两种理解的本质实际上是表达同一层含义,如下:
定义一:
柯里化:一个函数中有个多个参数,想固定其中某个或者几个参数的值,而只接受另外几个还未固定的参数,这样函数演变成新的函数。
定义二:
函数柯里化(currying)又称部分求值。一个 currying 的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
定义三:
一些函数式语言的工作原理是将多参数函数语法转化为单参数函数集合,这一过程称为柯里化,它是以逻辑学家Haskell Curry的名字命名的。Haskell Curry从早期概念中发展出了该理论。其形式相当于将z = f(x, y)转换成z = f(x)(y)的形式,原函数由两个参数,现在变为两个接受单参数的函数,在函数式编程语言中,这种特性很常见。
柯里化是一种将多参数函数转化为单参数高阶函数的技术。例如函数 f(x,y) -> z
,对于给定的两个参数 x 和 y ,该函数会返回 z
值作为结果。可以将 f(x,y) 柯里化为两个函数:f(x)(y) -> z。
即
f(x1,x2,x3,x4) ——> f(x1)(x2)(x3)(x4)
1.1 从一个例子来说起——按照定义二来实现
现在有一个函数add1,作用是实现三个数字相加,现在我们想要固定其中的第二个参数不变,调用的时候只指定第一个、第三个参数,艰难单的实现如下:
def add1(a, b, c):
return a + b + c
def add2(a, b):
return add1(a, 666, b)
result = add2(12,13)
print(result)
不错,这就是函数柯里化的简单实现,因为在python中一切皆对象,函数也是一种对象,所以实现起来很简单灵活,
1.2 python函数柯里化的不同实现方法——基于定义一、定义二
(1)通过functools提供的偏函数来实现
from functools import partial
def add1(a, b, c):
return a + b + c
add2 = partial(add1, b=666)
# 使用 partial 来实现偏函数时,要指定参数名
result = add2(a = 12, c = 13)
print(result)
(2)定义两个函数,用函数2来包装函数1
如上面所示,用add2来包装add1,这里不再赘述
(3)使用lambda表达式来完成
既然可以用一个函数包装另一个函数,自然可以使用lambda表达式来实现,如下所示:
def add1(a, b, c):
return a + b + c
# 使用lambda表达式柯里化,固定第二个参数b=666
add2 = lambda x,y:add_number(x,666,y)
result = add2(12, 13)
print(result)
(4)使用python的装饰器来实现
这就是最开始为什么强调深入理解python高级特性“装饰器”是非常有帮助的,关于python装饰器的系列文章,可以参考我前面的博客。代码如下:
def add(a, b, c):
return a + b + c
def currying_add(func):
def wrapper(a, c, b=666):
return func(a, b, c)
return wrapper
result = currying_add(add)(1,2)
print(result) # 669
如果是使用装饰器符号@该怎么实现呢?如下:
def currying_add(func):
def wrapper(a, c, b=666):
return func(a, b, c)
return wrapper
# 使用装饰器符号来定义函数add,add = currying_add(add)
@currying_add
def add(a, b, c):
return a + b + c
result = add(1,2)
print(result) # 669
(5)通过pymond模块;来实现currying
from pymonad import curry
1.2 python函数柯里化的不同实现方法——基于定义三
这里是实现以下功能,一个函数有多个参数,将其拆分成几个函数,每一个函数只具有其中的部分参数,实现函数参数的拆分。
如下代码:
def add(x, y):
return x + y
# 柯里化
def currying_add(x):
def inc(y):
return x + y
return inc # 返回函数
result = currying_add(2)(3)
print(result) # 5
柯里化的通俗总结:
不管是基于第一种、第二种定义,他更加侧重于将一个函数的多个参数中的其中几个先固定,这样减少参数的数量
还是基于第三中定义,它更加侧重于将函数的多个参数拆分成少量几个参数的组成,其实它们的本质都是一样,它们的实现核心思想也是一样的,真正核心的地方就在于“高阶函数”。
注意:柯里化的第一种定义、第二种定义看似是和函数定义默认值相似,但是默认值只能定义单个固定值,而柯里化能泛化出多个不同固定值但是相同计算规则的函数。
二、什么是函数反柯里化?
前面说的函数柯里化是实现这样的功能,即:
f(x1,x2,x3,x4) ——> f(x1)(x2)(x3)(x4)
那么实际上,函数的反柯里化就是恰好反过来的过程,即实现
f(x1)(x2)(x3)(x4)——>f(x1,x2,x3,x4)
含义的确是比较好理解,但是Python中比较少的用到,因为我们更多的使用函数柯里化操作,我们很少见到python中调用函数通过下面这样的方式调用吧,即:
f(x1)(x2)(x3)(x4)
所以函数反柯里化实际上一般都是和函数柯里化来结合使用,我们参见下面的例子:
# 定义加法函数
def add(x, y):
return x + y
# 柯里化
def currying_add(x):
def inc(y):
return x + y
return inc # 返回函数
# 因为currying_add()是一个柯里化函数,现在我针对他定义一个反柯里化函数,如下:
def anti_currying_add(x,y):
return currying_add(x)(y)
result = anti_currying_add(2,3) # 调用反柯里化函数
print(result) # 5
就是这么简单,其实这一个特性最重要的理解就在于“高阶函数”。
● 完结撒花!2020最新版《神经网络和深度学习》中文版PDF开放下载
机器学习与python集中营
视觉算法、机器学习、深度学习、python开发。百篇原创文章,用最通俗的言语阐述晦涩的理论,让我们来一次美好的邂逅吧!