美文网首页
Python 对函数式编程的支持

Python 对函数式编程的支持

作者: ButteredCat | 来源:发表于2016-12-29 17:20 被阅读0次

    Python does not promote functional programming even though it works fairly well.

    Anonymous function

    匿名函数是一种语法糖(syntactic sugar):所有使用匿名函数的地方,都可以用普通函数代替,少了它程序一样写,匿名函数只是让这一过程更加简便可读。我国程序员们为了给小函数起一个能准确描述功能的英文名称,做归纳、查字典,花的时间比实现函数本身还要长。匿名函数一出,问题迎刃而解。不过从简便可读的角度来讲,Python 的匿名函数,即 lambda 函数,真是有点名不副实。lambda 关键字包含6个字母,外加一个空格,本身就很长,写出来的匿名函数更长,严重影响阅读。比如下面这个:

    map(lambda x: x * x, lst)
    

    同样的功能,Scala 写出来就清晰了不少:

    lst map (x => x * x)
    

    另外,lambda 函数体只支持一条语句,这也限制了它的功能。不过从另一方面来说,需要两条以上语句实现的函数就不该再作为匿名函数了。

    Currying & partial application

    Python 并不原生支持柯里化(currying),如果你一定要,那也可以有。

    举例来说,你有一个函数 f:

    def f(a, b, c):
        print(a, b, c)
    

    如果要将其柯里化,你可以改写成这样:

    def f(a):
        def g(b):
            def h(c):
                print(a, b, c)
            return h
        return g
    
    In [2]: f(1)(2)(3)
    1 2 3
    
    In [3]: g = f(4)
    
    In [4]: g(5)(6)
    4 5 6
    

    得益于函数在 Python 中一等公民的地位,即所谓 first-class function,你可以在函数中返回另一个函数,柯里化也由此实现。《Currying in Python》一文提供了一种将普通函数柯里化的方法。

    Partial application(偏函数)与柯里化是相关但不同的概念。你可以用 functools.partial 得到一个函数的偏函数:

    In [1]: def f(a, b, c):
       ...:     print(a, b, c)
       ...:
    
    In [2]: from functools import partial
    
    In [3]: g = partial(partial(f, 1), 2)
    
    In [4]: g(3)
    1 2 3
    

    Recursion & tail recursion

    Python 不支持对尾递归函数的优化(tail recursion elimination, TRE),也根本不建议程序员在生产代码中使用递归。

    摘录一段 Python 领导核心 Guido van Rossum 关于尾递归的讲话,供大家学习领会:

    So let me defend my position (which is that I don't want TRE in the language). If you want a short answer, it's simply unpythonic.

    Lazy evaluation

    生成器(generator)就是 Python 中惰性求值(lazy evaluation)的一个例子:一个值只有当请求到来时,才会被计算。生成器的使用,可以看我的另一篇文章《用 Python 写一个函数式编程风格的斐波那契序列生成器》

    但是 Python 没有一些函数式编程语言中那种精确到每个值的惰性计算。比如在 Scala 中,你可以用关键字 lazy 定义一个值:

    scala> lazy val a = 2 * 2
    a: Int = <lazy>
    
    scala> a
    res0: Int = 4
    

    直到第一次调用,值 a 才会被计算出来。

    Python 可以曲折地实现类似的惰性计算特性。Python Cookbook 第三版 8.10 节展示了如何利用描述符(descriptor)实现类属性的惰性计算:

    class lazyproperty(object):
        def __init__(self, func):
            self.func = func
    
        def __get__(self, instance, cls):
            if instance is None:
                return self
            else:
                value = self.func(instance) 
                setattr(instance, self.func.__name__, value) 
                return value
    

    定义好的 lazyproperty 作为装饰器(decorator),被装饰的属性就只有在被调用时才会求值,求得的值会被缓存供以后使用:

    class Circle(object):
        def __init__(self, radius):
            self.radius = radius
    
        @lazyproperty
        def area(self):
            print('Computing area')
            return math.pi * self.radius ** 2
    
        @lazyproperty
        def perimeter(self):
            print('Computing perimeter')
            return 2 * math.pi * self.radius
    
    In [16]: import math
    
    In [17]: c = Circle(4.0)
    
    In [18]: c.area
    Computing area
    Out[18]: 50.26548245743669
    
    In [19]: c.area
    Out[19]: 50.26548245743669
    

    map, reduce & filter

    好消息是,Python 支持这些基本的操作;而坏消息是,Python 不建议你使用它们。

    在 Python 2 中,mapreducefilter 都是 build-in functions(内置函数),返回的都是列表或计算结果,使用起来很方便;但到了 Python 3,reduce 不再是内置函数,而被放入 functoolsmapfilter 返回的不再是列表,而是可迭代的对象。假如你需要的恰恰是列表,那么使用起来略显繁琐。

    In [1]: from platform import python_version
    
    In [2]: print('Python', python_version())
    Python 3.5.2
    
    In [3]: n1 = [1, 2, 3]
    
    In [4]: n2 = [4, 5, 6]
    
    In [5]: import operator
    
    In [6]: map(operator.add, n1, n2)
    Out[6]: <map at 0x104749748>
    
    In [7]: list(map(operator.add, n1, n2))
    Out[7]: [5, 7, 9]
    
    In [8]: import functools
    
    In [9]: functools.reduce(operator.add, n1)
    Out[9]: 6
    

    如果不拘泥于 mapreduce 的形式,实际上,list comprehension (列表推导)和 generator expression(生成器表达式)可以优雅地代替 mapfilter

    In [10]: [i + j for i, j in zip(n1, n2)] # list(map(operator.add, n1, n2))
    Out[10]: [5, 7, 9]
    
    In [11]: [i for i in n1 if i > 1] # list(filter(lambda x: x > 1, n1))
    Out[11]: [2, 3]
    

    一个 for 循环也总是可以代替 reduce,当然,你得多写几行代码,看上去也不那么 functional 了。

    此外,itertools 中还提供了 takewhiledropwhilefilterfalsegroupbypermutationscombinations 等诸多函数式编程中常用的函数。

    其他

    另外,Python 不支持 pattern matching,return 语句怎么看怎么像 imperative programming (命令式编程)余孽,这些都限制了 Python 成为一种更好的函数式编程语言。


    综上所述,相信你对于如何使用 Python 编写函数式代码已经入门,考虑到 Python 语言设计上对函数式编程的诸多限制,下一步就该考虑放弃了……

    相关文章

      网友评论

          本文标题:Python 对函数式编程的支持

          本文链接:https://www.haomeiwen.com/subject/tcfevttx.html