为什么在Python中进行功能编程?

时间:2009-12-12 04:46:23

标签: python functional-programming

在工作中,我们习惯以非常标准的OO方式编写Python。最近,有几个人加入了这个功能性的潮流。他们的代码现在包含更多的lambda,map和reduce。我知道函数式语言对并发性有好处,但Python函数编程真的有助于并发吗?我只是想了解如果我开始使用更多Python的功能,我会得到什么。

8 个答案:

答案 0 :(得分:69)

答案 1 :(得分:24)

FP不仅对并发很重要;事实上,在规范的Python实现中几乎没有并发性(可能3.x会改变吗?)。在任何情况下,FP都很适合并发,因为它导致程序没有或没有(显式)状态。由于一些原因,国家很麻烦。一个是他们分配计算硬(呃)(这是并发论证),另一个,在大多数情况下更重要的是,是一种造成错误的倾向。当代软件中最大的漏洞来源是变量(变量与状态之间存在密切关系)。 FP可能会减少程序中变量的数量:压缩错误!

通过在这些版本中混合使用变量来查看可以引入多少错误:

def imperative(seq):
    p = 1
    for x in seq:
        p *= x
    return p

与(警告,my.reduce的参数列表不同于python的reduce;后面给出的理由

import operator as ops

def functional(seq):
    return my.reduce(ops.mul, 1, seq)

正如你所看到的那样,事实上FP可以减少与变量相关的错误射击自己的机会。

另外,可读性:可能需要一些培训,但functionalimperative更容易阅读:你看reduce(“好吧,它正在减少一个序列到一个单值“),mul(”乘以“)。 wherease imperative具有for周期的通用形式,其中包含变量和赋值。这些for个周期看起来都是一样的,所以为了了解imperative中发生了什么,你需要阅读几乎全部内容。

然后就是那种顽固和灵活。你给我imperative并且我告诉你我喜欢它,但是想要一些东西来总结序列。没问题,你说,然后你走了,复制粘贴:

def imperative(seq):
    p = 1
    for x in seq:
        p *= x
    return p

def imperative2(seq):
    p = 0
    for x in seq:
        p += x
    return p

你可以做些什么来减少重复?好吧,如果运算符是值,你可以做类似的事情

def reduce(op, seq, init):
    rv = init
    for x in seq:
        rv = op(rv, x)
    return rv

def imperative(seq):
    return reduce(*, 1, seq)

def imperative2(seq):
    return reduce(+, 0, seq)
哦等等! operators为运营商提供 值!但是......亚历克斯·马尔泰利已经谴责reduce ......看起来如果你想留在他建议的范围内,你注定要复制粘贴的管道代码。

FP版本更好吗?当然你也需要复制粘贴?

import operator as ops

def functional(seq):
    return my.reduce(ops.mul, 1, seq)

def functional2(seq):
    return my.reduce(ops.add, 0, seq)
嗯,这只是半成品方法的神器!放弃命令def,你可以将两个版本都收缩到

import functools as func, operator as ops

functional  = func.partial(my.reduce, ops.mul, 1)
functional2 = func.partial(my.reduce, ops.add, 0)

甚至

import functools as func, operator as ops

reducer = func.partial(func.partial, my.reduce)
functional  = reducer(ops.mul, 1)
functional2 = reducer(ops.add, 0)

func.partialmy.reduce

的原因

运行时速度怎么样?是的,在像Python这样的语言中使用FP会产生一些开销。在这里,我只想说一些教授对此有何评论:

  • 过早优化是万恶之源。
  • 大多数程序花费80%的运行时间占其代码的20%。
  • 个人资料,不要推测!

我不太善于解释事情。不要让我把水弄得太多,看看speech约翰巴克斯在1977年获得图灵奖时给出的上半部分。引用:

  

5.1内在产品的von Neumann程序

c := 0
for i := I step 1 until n do
   c := c + a[i] * b[i]
     

该计划的几个属性是   值得注意的是:

     
      
  1. 根据复杂的说法,它的陈述在一个看不见的“状态”上运作   规则。
  2.   
  3. 不是等级制的。除了作业的右侧   声明,它不构造   简单的复杂实体。   (但是,较大的程序经常这样做。)
  4.   
  5. 这是动态和重复的。一个人必须在精神上执行它   理解它。
  6.   
  7. 它通过重复(赋值)和by来计算一次一个字   修改(变量i)。
  8.   
  9. 部分数据n在程序中;因此缺乏一般性和   仅适用于长度为n的矢量。
  10.   
  11. 命名其论点;它只能用于向量ab。   为了变得普遍,它需要一个   程序声明。这涉及到   复杂问题(例如,按名称呼叫)   与按值调用相比)。
  12.   
  13. 其“管家”操作由符号表示   分散的地方(在for语句中   以及作业中的下标)。   这使得它变得不可能   巩固家政服务,   最常见的,单身,   强大,广泛有用的运营商。   因此在编程那些操作   必须始终在广场重新开始   一,写“for i := ...”和   “for j := ...”后跟   赋值语句随附   ij的。{/ li>   

答案 2 :(得分:19)

我每天都在使用Python进行编程,而且我不得不说对OO或功能性过多的“转移”会导致缺少优雅的解决方案。我相信这两种范式对某些问题都有其优势 - 我认为当你知道使用什么方法时。在为您提供干净,可读且高效的解决方案时,请使用功能性方法。 OO也是如此。

这就是我喜欢Python的原因之一 - 事实上它是多范式的,让开发人员选择如何解决他/她的问题。

答案 3 :(得分:15)

答案 4 :(得分:9)

这个问题似乎在这里被忽略了:

  

编程Python功能真的有助于并发吗?

没有。 FP带来并发的价值在于消除计算中的状态,这最终导致并发计算中难以理解的意外错误。但这取决于并发编程习语本身不是有状态的,不适用于Twisted。如果有利用无状态编程的Python的并发习惯用法,我不知道它们。

答案 5 :(得分:7)

以下是关于/为何在功能上进行编程的正面答案的简短摘要。

  • 列表推导是从FP语言Haskell导入的。他们是Pythonic。我更愿意写
y = [i*2 for i in k if i % 3 == 0]

而不是使用命令式构造(循环)。

  • 在向lambda提供复杂密钥时,我会使用sort,例如list.sort(key=lambda x: x.value.estimate())

  • 使用高阶函数比使用OOP的设计模式(如visitor或抽象工厂

  • 编写代码更清晰
  • 人们说你应该用Python编写Python,用C ++编写C ++等等。这是真的,但你当然应该能够以不同的方式思考同一件事。如果在编写循环时你知道你正在减少(折叠),那么你将能够在更高层次上思考。这可以清理你的思想,并有助于组织。当然,低级思维也很重要。

你不应该过度使用这些功能 - 有许多陷阱,请参阅Alex Martelli的帖子。我主观地说,最严重的危险是过度使用这些功能会破坏代码的可读性,这是Python的核心属性。

答案 6 :(得分:2)

标准函数filter(),map()和reduce()用于列表上的各种操作,并且所有这三个函数都需要两个参数:函数和列表

我们可以定义一个单独的函数并将其用作filter()等的参数,如果该函数被多次使用,或者如果函数太复杂而无法写入一行,则可能是个好主意。但是,如果它只需要一次并且它非常简单,那么使用lambda构造生成(临时)匿名函数并将其传递给filter()会更方便。

这有助于readability and compact code.

使用这些函数也会变成efficient,因为列表元素的循环是在C中完成的,这比在python中循环要快一点。

除了抽象,分组等之外,在维护状态时强制需要面向对象的方式。如果要求非常简单,我会坚持使用功能而不是面向对象编程。

答案 7 :(得分:1)

Map和Filter在OO编程中占有一席之地。紧接着列表推导和生成器函数。

减少更少。减少算法可以快速吸收比它应得的更多的时间;通过一点思考,手动编写的reduce循环将比reduce更有效,reduce将一个经过深思熟虑的循环函数应用于序列。

Lambda永远不会。 Lambda没用。人们可以说它实际上做了某事,所以它不是完全无用的。第一:Lambda不是句法“糖”;它使事情变得更大,更丑陋。第二:10,000行代码中有一次认为你需要一个“匿名”函数,在20,000行代码中变成了两次,这消除了匿名的价值,使其成为维护责任。

然而

无对象状态变化编程的功能风格本质上仍然是OO。您只需创建更多对象并减少对象更新。一旦开始使用生成器函数,许多OO编程就会向功能方向漂移。

每个状态更改似乎都转换为生成器函数,该函数从旧对象构建新状态的新对象。这是一个有趣的世界观,因为对算法的推理要简单得多。

但是没有使用reduce或lambda的调用。