在工作中,我们习惯以非常标准的OO方式编写Python。最近,有几个人加入了这个功能性的潮流。他们的代码现在包含更多的lambda,map和reduce。我知道函数式语言对并发性有好处,但Python函数编程真的有助于并发吗?我只是想了解如果我开始使用更多Python的功能,我会得到什么。
答案 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可以减少与变量相关的错误射击自己的机会。
另外,可读性:可能需要一些培训,但functional
比imperative
更容易阅读:你看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.partial
是my.reduce
)
运行时速度怎么样?是的,在像Python这样的语言中使用FP会产生一些开销。在这里,我只想说一些教授对此有何评论:
我不太善于解释事情。不要让我把水弄得太多,看看speech约翰巴克斯在1977年获得图灵奖时给出的上半部分。引用:
5.1内在产品的von Neumann程序
c := 0 for i := I step 1 until n do c := c + a[i] * b[i]
该计划的几个属性是 值得注意的是:
- 根据复杂的说法,它的陈述在一个看不见的“状态”上运作 规则。
- 不是等级制的。除了作业的右侧 声明,它不构造 简单的复杂实体。 (但是,较大的程序经常这样做。)
- 这是动态和重复的。一个人必须在精神上执行它 理解它。
- 它通过重复(赋值)和by来计算一次一个字 修改(变量i)。
- 部分数据
n
在程序中;因此缺乏一般性和 仅适用于长度为n
的矢量。- 命名其论点;它只能用于向量
a
和b
。 为了变得普遍,它需要一个 程序声明。这涉及到 复杂问题(例如,按名称呼叫) 与按值调用相比)。- 其“管家”操作由符号表示 分散的地方(在for语句中 以及作业中的下标)。 这使得它变得不可能 巩固家政服务, 最常见的,单身, 强大,广泛有用的运营商。 因此在编程那些操作 必须始终在广场重新开始 一,写“
for i := ...
”和 “for j := ...
”后跟 赋值语句随附i
和j
的。{/ li> 醇>
答案 2 :(得分:19)
我每天都在使用Python进行编程,而且我不得不说对OO或功能性过多的“转移”会导致缺少优雅的解决方案。我相信这两种范式对某些问题都有其优势 - 我认为当你知道使用什么方法时。在为您提供干净,可读且高效的解决方案时,请使用功能性方法。 OO也是如此。
这就是我喜欢Python的原因之一 - 事实上它是多范式的,让开发人员选择如何解决他/她的问题。
答案 3 :(得分:15)
答案 4 :(得分:9)
这个问题似乎在这里被忽略了:
编程Python功能真的有助于并发吗?
没有。 FP带来并发的价值在于消除计算中的状态,这最终导致并发计算中难以理解的意外错误。但这取决于并发编程习语本身不是有状态的,不适用于Twisted。如果有利用无状态编程的Python的并发习惯用法,我不知道它们。
答案 5 :(得分:7)
以下是关于/为何在功能上进行编程的正面答案的简短摘要。
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的调用。