Functional Programming in Python

2018-05-23  本文已影响35人  zhizhuwang

高阶函数(High-order Function)是函数式编程中非常重要的概念,它是提升代码抽象层次的重要方法和手段。越来越多的语言开始支持函数式编程的范式,比如Java、C++。
虽然Python不是像Haskell这样纯粹的函数式编程语言,但是它也具有函数式编程的一些特性;而且Python现在应用非常广泛,了解一些这方面的特性,可以帮助我们写出更加简洁的代码。
在介绍高阶函数之前,我们需要先弄清楚First ClassCurryClosure这几个概念。

Function Object

在Python中,有三种实现Function Object的形式:

def add(a, b):
    return a + b
>>> add(2,3)
5
f = lambda a,b: a + b
>>> f(2,3)
5
class MyClass:
    def __call__(self, a, b):
        return a + b
>>> cls = MyClass()

>>> cls
<__main__.MyClass at 0x5e9d358>

>>> cls(2,3)
5

上述三种方式都可以实现一个add的函数,并且三者在功能上是等价的。

由于函数是对象,所以,它可以被赋值给变量,可以被存储在数据结构中,可以作为函数的参数,还可以作为函数的返回值。
函数作为一等公民(First Class)存在,是实现高阶函数(High-order function)的基础。

Closure

在说闭包之前先来认识自由变量。在某个作用域中,如果使用了未在本作用域中声明的变量,对于此作用域来说,该变量就是一个自由变量。闭包(Closure),就是指引用了自由变量的函数。
以Python为例,可以这样来认识Closure

def inc(x):
    def incx(y):
        return x + y
    return incx
>>> inc2 = inc(2)
>>> inc10 = inc(10)

>>> inc2(2)
4
>>> inc10(2)
12 

在这个例子中,incx函数绑定了一个自由变量x

Curry

在Haskell中,所有的函数都只有一个参数,所有的多参数的函数都是柯里函数。柯里函数不会一次性取完所有参数,而是在每次调用是只取用一个参数,并返回一个一元函数,来取下一个参数,以此类推。

以Haskell中的max函数为例:

ghci> max 4 5
5
ghci> (max 4) 5
5

首先,max会被应用到4上,返回一个一元函数,随后以5为参数调用返回的一元函数,并在这一步得到最终结果。因此,上述两个调用是等价的。

这样的好处是什么呢?简言之,我们只要以部分的参数来调用某函数,就可以得到一个部分应用(partial application)函数,部分应用函数所接受的参数的数量,和之前少传入的参数的数量一致。比如max 4,可以得到一个一元函数。部分应用使得构造新函数变得便捷简便,随时都可以为传递为其它函数而构造出新函数。

在上面闭包的例子中,函数add(x,y)有两个入参,我们把它改写成了两个具有一个入参的函数。下面两种使用方式是等效的:

>>> inc(2)(3)
5

>>> inc2(3)
5

高阶函数

什么是高阶函数:

map,reduce,filter

典型的高阶函数:mapreducefilter,也是几乎所有的函数式编程语言都会提供的函数,也是一种很重要的抽象手段。著名的MapReduce模型就来自于这个思想。

>>> sq = map( lambda x: x ** 2, range(0,10) )

>>> print([ i for i in sq ])
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
from functools import *
>>> sq = map( lambda x: x ** 2, range(0,10) )

>>> reduce(lambda x,y: x+y, sq)
285
>>> f = filter ( lambda x: x % 2 == 0, [ i for i in range(10) ])

>>> f
<filter at 0x50c56d8>

>>> print([ i for i in f ])
[0, 2, 4, 6, 8]

在Python的3.x版本中,mapreducefilter函数会构造出一个Iterator,而不是list。

Decorator

Decorator(装饰器)是高阶函数的语法糖,一般用@符号标识。

def makeitalic(fn):
     def wrapped(*args, **kwargs):
         return "<i>" + fn(*args, **kwargs) + "</i>"
     return wrapped

def makebold(fn):
     def wrapped(*args, **kwargs):
         return "<b>" + fn(*args, **kwargs) + "</b>"
     return wrapped

@makeitalic
@makebold  # hello = makeitalic(makebold(hello))
def hello(*args, **kwargs):
     return "Hello. args: {}, kwargs: {}".format(args, kwargs)
>>> print( hello( 'hello', 'world', demo='generator'))
<i><b>Hello. args: ('hello', 'world'), kwargs: {'demo': 'generator'}</b></i>

在上面的这个示例中,分别定义了makeitalicmakebold两个decorator,对hello函数进行相应的装饰。比较特别的是,decorator之间也可以进行组合,在上面这个示例中,它们组合起来完成了italicbold的功能。从本质上可以理解为,这是一个多层嵌套的高阶函数。

此外,Decorator还可以带参数,可以是实现了__call__的可调用对象,可以是带有不同参数的多个可调用对象的实例。

class BoldMaker:
    def __init__(self, fn):
        self.fn = fn

    def __call__(self, *args, **kwargs):
        return "<b>" + self.fn(args, kwargs) + "</b>"

@BoldMaker
def hello(*args, **kwargs):
    return "Hello. args: {}, kwargs: {}".format(args, kwargs)
>>> print(hello("This is decorator of callable class"))
<b>Hello. args: (('This is decorator of callable class',), {}), kwargs: {}</b>

Decorator也可以带一个或者多个参数,例如:

def  enclose_in_tags(opening_tag, closing_tag):
    def make_with_tags(fn):
        def wrapped():
            return opening_tag + fn() + closing_tag
        return wrapped
    return make_with_tags

@enclose_in_tags(opening_tag='<h1>', closing_tag='</h1>')
def hello():
    return "hello world"
>>> hello()
'<h1>hello world</h1>'

参考资料

谈谈闭包——以Swift为例
Using functional programming in Python like a boss: Generators, Iterators and Decorators
理解 Python 装饰器就看这一篇
Higher Order Functions: Using Filter, Map and Reduce for More Maintainable Code

上一篇下一篇

猜你喜欢

热点阅读