Pau*_*ndt 35 python functional-programming
在工作中,我们习惯以非常标准的OO方式编写Python.最近,有几个人加入了这个功能性的潮流.他们的代码现在包含更多的lambda,map和reduce.我知道函数式语言对并发性有好处,但Python函数编程真的有助于并发吗?我只是想了解如果我开始使用更多Python的功能,我会得到什么.
Ale*_*lli 69
编辑:我已被带到评论的任务(部分似乎,由Python的狂热分子,但不是唯一的)没有提供更多的解释/示例,因此,扩大答案提供一些.
lambda
,更是这样map
(和filter
),而最特别的是reduce
,在几乎没有在Python中,这是一个强烈的多范式语言工作的工具.
lambda
主要优点(?)与普通def
语句相比,它是一个匿名函数,同时def
给函数一个名字 - 而且为了这个非常可疑的优势,你付出了巨大的代价(函数的主体仅限于一个表达式,结果函数)对象不是pickleable,缺乏名称有时会使得理解堆栈跟踪或以其他方式调试问题变得更加困难 - 我需要继续吗?! - ).
考虑一下你有时会在"Python"中看到的最愚蠢的习惯用法(带有"恐慌引用"的Python),因为它显然不是惯用的Python - 它是来自惯用方案等的错误音译,就像更频繁的过度使用Python中的OOP是来自Java之类的错误音译):
inc = lambda x: x + 1
Run Code Online (Sandbox Code Playgroud)
通过将lambda指定为名称,这种方法立即抛弃了上述"优势" - 并且不会失去任何缺点!例如,inc
不知道它的名字 - inc.__name__
是无用的字符串'<lambda>'
- 祝你好运了解堆栈跟踪中的一些;-).在这个简单的例子中,正确的Python方法是实现所需的语义,当然:
def inc(x): return x + 1
Run Code Online (Sandbox Code Playgroud)
现在 inc.__name__
是字符串'inc'
,因为它显然应该是,并且对象是可拾取的 - 语义在其他方面是相同的(在这个简单的情况下,所需的功能在一个简单的表达式中舒适地适合 - def
如果你需要,它也很容易重构)临时或永久插入诸如print
或raise
当然的陈述.
lambda
是(一部分)表达式,def
而是(一部分)一个语句 - 这是人们lambda
有时使用的一点语法糖.许多FP爱好者(就像许多OOP和程序粉丝一样)不喜欢Python在表达式和语句之间的相当强烈的区别(对命令查询分离的一般立场的一部分).我,我认为当你使用一种语言时,你最好"使用谷物" - 它的设计方式- 而不是反对它; 所以我用Pythonic方式编程Python,以Schematic(;-)方式构建Scheme,以Fortesque(?)方式编译Fortran,依此类推:-).
继续reduce
- 一条评论声称这reduce
是计算列表产品的最佳方式.真的吗?让我们来看看...:
$ python -mtimeit -s'L=range(12,52)' 'reduce(lambda x,y: x*y, L, 1)'
100000 loops, best of 3: 18.3 usec per loop
$ python -mtimeit -s'L=range(12,52)' 'p=1' 'for x in L: p*=x'
100000 loops, best of 3: 10.5 usec per loop
Run Code Online (Sandbox Code Playgroud)
所以简单,基本,平凡的循环比执行任务的"最佳方式"快两倍(以及更简洁)? - 我认为速度和简洁的优点必须使得琐碎的循环成为"最好的" "方式,对吧? - )
通过进一步牺牲紧凑性和可读性......:
$ python -mtimeit -s'import operator; L=range(12,52)' 'reduce(operator.mul, L, 1)'
100000 loops, best of 3: 10.7 usec per loop
Run Code Online (Sandbox Code Playgroud)
...我们几乎可以回到最容易获得的最简单,最简单,最简单,最易读的方法(简单,基本,简单的循环).这指出了另一个问题lambda
,实际上:性能!对于足够简单的操作,例如乘法,与正在执行的实际操作相比,函数调用的开销非常大 - reduce
(map
和filter
)经常强制您在简单循环,列表推导和生成器表达式中插入这样的函数调用,允许在线操作的可读性,紧凑性和速度.
也许甚至比上面指出的"将一个lambda指定给一个名字"更糟糕的反成语实际上是以下的反成语,例如按照它们的长度对字符串列表进行排序:
thelist.sort(key=lambda s: len(s))
Run Code Online (Sandbox Code Playgroud)
而不是明显的,可读的,紧凑的,更快速的
thelist.sort(key=len)
Run Code Online (Sandbox Code Playgroud)
在这里,使用lambda
除了插入间接级别之外什么都不做 - 没有任何好的效果,还有很多不好的方法.
使用的动机lambda
通常是允许使用map
和filter
代替非常优选的循环或列表理解,这样可以让你在线进行简单,正常的计算; 当然,你仍然支付"间接水平".这不是Pythonic不得不怀疑"我应该在这里使用listcomp还是map":只要总是使用listcomps,当两者都适用时你不知道选择哪一个,基于"应该有一个,并且最好只做一件明显的做事方式".你经常会编写无法合理地转换为映射的listcomp(嵌套循环,if
子句等),而没有调用map
它不能被合理地重写为listcomp.
Python中完全正确的函数方法通常包括列表推导,生成器表达式,itertools
高阶函数,各种伪装的一阶函数,闭包,生成器(偶尔还有其他类型的迭代器).
itertools
作为评论者指出的那样,不包括imap
和ifilter
:所不同的是,像所有itertools,这些是基于流的(像map
和filter
在Python 3建宏,但是从在Python 2那些建宏不同). itertools
提供了一组构建模块,彼此构成良好,并且表现出色:特别是如果你发现自己可能处理很长(甚至无限制的! - )序列,你应该熟悉itertools - 他们的整体文档中的章节有助于阅读,特别是食谱非常有启发性.
编写自己的高阶函数通常很有用,特别是当它们适合用作装饰器时(两个函数装饰器,如文档部分所述,以及Python 2.6中引入的类装饰器).请记住在函数装饰器上使用functools.wraps(以保持函数的元数据被包装)!
所以,总结...:任何你可以使用代码lambda
,map
以及filter
,你可以(比不了最好更多)的代码def
(称为函数)和listcomps -通常一个缺口向上移动到发电机,发电机表达式,或者itertools
是甚至更好. reduce
符合"有吸引力的烦扰"的法律定义......:它几乎不是适合这项工作的工具(这就是为什么它不再是Python 3中的内置功能,最后! - ).
jus*_*ody 24
FP不仅对并发很重要; 事实上,在规范的Python实现中几乎没有并发性(可能3.x会改变吗?).在任何情况下,FP都很适合并发,因为它导致程序没有或没有(显式)状态.由于一些原因,国家很麻烦.一个是他们分配计算硬(呃)(这是并发论证),另一个,在大多数情况下更重要的是,是一种造成错误的倾向.当代软件中最大的漏洞来源是变量(变量与状态之间存在密切关系).FP可能会减少程序中变量的数量:被压扁的错误!
通过在这些版本中混合变量,您可以看到有多少错误:
def imperative(seq):
p = 1
for x in seq:
p *= x
return p
Run Code Online (Sandbox Code Playgroud)
与(警告,my.reduce
参数列表不同于python的参数列表reduce
;后面给出的理由)
import operator as ops
def functional(seq):
return my.reduce(ops.mul, 1, seq)
Run Code Online (Sandbox Code Playgroud)
正如你所看到的那样,事实上FP可以让你更少有机会用变量相关的bug来射击自己.
另外,可读性:它可能需要一些训练,但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
Run Code Online (Sandbox Code Playgroud)
你能做些什么来减少重复?好吧,如果运营商是价值观,你可以做类似的事情
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)
Run Code Online (Sandbox Code Playgroud)
等一下!operators
提供了运营商都值!但是...... Alex Martelli 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)
Run Code Online (Sandbox Code Playgroud)
好吧,这只是半成品方法的神器!放弃命令def
,你可以将两个版本合同
import functools as func, operator as ops
functional = func.partial(my.reduce, ops.mul, 1)
functional2 = func.partial(my.reduce, ops.add, 0)
Run Code Online (Sandbox Code Playgroud)
甚至
import functools as func, operator as ops
reducer = func.partial(func.partial, my.reduce)
functional = reducer(ops.mul, 1)
functional2 = reducer(ops.add, 0)
Run Code Online (Sandbox Code Playgroud)
(func.partial
是的原因my.reduce
)
运行速度怎么样?是的,在像Python这样的语言中使用FP会产生一些开销.在这里,我只是鹦鹉几位教授对此有何评论:
我不太擅长解释事情.不要让我把水弄得太多,请阅读John Backus在1977年获得图灵奖时发表的演讲的前半部分.引用:
5.1内在产品的von Neumann程序
Run Code Online (Sandbox Code Playgroud)c := 0 for i := I step 1 until n do c := c + a[i] * b[i]
该程序的几个属性值得注意:
- 它的陈述根据复杂的规则在一个看不见的"状态"上运作.
- 它不是等级的.除了赋值语句的右侧,它不会从更简单的实体构造复杂的实体.(但是,较大的程序经常这样做.)
- 它是动态和重复的.一个人必须在心理上执行它才能理解它.
- 它通过重复(赋值)和修改(变量i)来计算一次一个字.
- 部分数据,
n
在程序中; 因此它缺乏一般性,只适用于长度的矢量n
.- 它命名其论点; 它只能用于矢量
a
和b
.为了变得普遍,它需要一个程序声明.这些涉及复杂问题(例如,逐个呼叫与按值呼叫).- 它的"管家"操作由分散的位置(在for语句和赋值中的下标)中的符号表示.这使得无法将最常见的内务管理操作整合为单一,强大,广泛有用的运营商.因此,在编程这些操作时,必须始终在第一个方向开始,写"
for i := ...
"和"for j := ...
",然后是分配语句,其中包含i
's和j
's.
Ben*_*den 19
我每天都在用Python编程,我不得不说对OO或功能过多的"徘徊"会导致缺少优雅的解决方案.我相信这两种范式对某些问题都有其优势 - 我认为当你知道使用什么方法时.在为您提供干净,可读且高效的解决方案时,请使用功能性方法.OO也是如此.
这就是我喜欢Python的原因之一 - 事实上它是多范式的,让开发人员选择如何解决他/她的问题.
Omn*_*ous 15
这个答案完全重复了.它结合了其他答案的许多观察结果.
正如您所看到的,在Python中使用函数式编程结构存在许多强烈的感受.这里有三大类想法.
首先,几乎每个人,除了那些最坚持最纯粹的功能范式表达的人都同意列表和生成器理解比使用map
或更好更清晰filter
.你的同事,应避免使用map
和filter
如果你的目标一个Python版本足够的新支持列表内涵.你应该避免itertools.imap
,itertools.ifilter
如果你的Python版本足够新的生成器理解.
其次,整个社会都存在许多矛盾心理lambda
.除了def
声明函数之外,很多人都对语法感到恼火,特别是涉及像lambda
这样的关键字有一个相当奇怪的名字.人们也很恼火,这些小的匿名函数缺少描述任何其他类型函数的任何好的元数据.它使调试更难.最后,声明的小函数lambda
通常不是非常有效,因为它们每次调用时都需要Python函数调用的开销,这通常在内部循环中.
最后,大多数人(意思是> 50%,但很可能不是90%)人们认为这reduce
有点奇怪和模糊.我自己承认print reduce.__doc__
每当我想要使用它时,我都不会经常这样做.虽然当我看到它被使用时,参数的性质(即函数,列表或迭代器,标量)不言而喻.
至于我自己,我陷入了那些认为功能风格通常非常有用的人的阵营.但平衡这种想法是因为Python本身并不是一种功能性语言.过度使用功能结构可能会使程序看起来奇怪扭曲,人们难以理解.
要了解函数样式何时何地非常有用并提高可读性,请在C++中考虑此函数:
unsigned int factorial(unsigned int x)
{
int fact = 1;
for (int i = 2; i <= n; ++i) {
fact *= i;
}
return fact
}
Run Code Online (Sandbox Code Playgroud)
这个循环看起来非常简单易懂.在这种情况下它是.但它看似简单是一个疏忽的陷阱.考虑这种编写循环的替代方法:
unsigned int factorial(unsigned int n)
{
int fact = 1;
for (int i = 2; i <= n; i += 2) {
fact *= i--;
}
return fact;
}
Run Code Online (Sandbox Code Playgroud)
突然,循环控制变量不再以明显的方式变化.您可以减少查看代码并仔细推理循环控制变量的情况.现在这个例子有点病态,但有现实世界的例子没有.问题在于这个想法是重复分配给现有变量的事实.你不能相信变量的值在整个循环体中是相同的.
这是一个长期被认可的问题,在Python中编写这样的循环是相当不自然的.你必须使用while循环,它看起来不对.相反,在Python中你会写这样的东西:
def factorial(n):
fact = 1
for i in xrange(2, n):
fact = fact * i;
return fact
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,您在Python中讨论循环控制变量的方式不适合在循环中欺骗它.这消除了其他命令式语言中"聪明"循环的许多问题.不幸的是,这是一个半功能语言的想法.
即使这样也适合奇怪的摆弄.例如,这个循环:
c = 1
for i in xrange(0, min(len(a), len(b))):
c = c * (a[i] + b[i])
if i < len(a):
a[i + 1] = a[a + 1] + 1
Run Code Online (Sandbox Code Playgroud)
哎呀,我们又有一个难以理解的循环.它表面上看起来像一个非常简单明显的循环,你必须仔细阅读它才能意识到循环计算中使用的变量之一正在以一种影响循环未来运行的方式被搞乱.
再次,一个更实用的救援方法:
from itertools import izip
c = 1
for ai, bi in izip(a, b):
c = c * (ai + bi)
Run Code Online (Sandbox Code Playgroud)
现在通过查看代码,我们有一些强烈的指示(部分是由于这个人正在使用这种功能样式),在执行循环期间不会修改列表a和b.少想一想.
最后要担心的是c以奇怪的方式进行修改.也许它是一个全局变量,正在通过一些环形函数调用进行修改.为了拯救我们免受这种心理上的担忧,这里有一个纯粹的功能方法:
from itertools import izip
c = reduce(lambda x, ab: x * (ab[0] + ab[1]), izip(a, b), 1)
Run Code Online (Sandbox Code Playgroud)
非常简洁,结构告诉我们x纯粹是一个累加器.它出现在任何地方都是一个局部变量.最终结果明确地分配给c.现在不用担心了.代码的结构删除了几类可能的错误.
这就是人们可能选择功能风格的原因.它简洁明了,至少如果您了解什么reduce
和lambda
做什么.有很多类问题可能会影响以更迫切的风格编写的程序,您知道这些程序不会影响您的功能样式程序.
在阶乘的情况下,有一种非常简单明了的方法可以在函数风格中用Python编写这个函数:
import operator
def factorial(n):
return reduce(operator.mul, xrange(2, n+1), 1)
Run Code Online (Sandbox Code Playgroud)
这个问题似乎在这里被忽略了:
编程Python功能真的有助于并发吗?
不会.FP对并发性的价值在于消除计算中的状态,这最终导致并发计算中意外错误的难以掌握的肮脏.但这取决于并发编程习语本身不是有状态的,不适用于Twisted.如果有利用无状态编程的Python的并发习惯用法,我不知道它们.
以下是关于何时/为何进行功能编程的正面答案的简短摘要.
Run Code Online (Sandbox Code Playgroud)y = [i*2 for i in k if i % 3 == 0]
而不是使用命令式构造(循环).
我会lambda
在给出一个复杂的钥匙时使用sort
,比如list.sort(key=lambda x: x.value.estimate())
使用高阶函数比使用OOP的设计模式(如访问者或抽象工厂)编写代码更清晰
人们说你应该用Python编写Python,用C++编写C++等.这是真的,但你当然应该能够以不同的方式思考同一件事.如果在编写循环时你知道你正在减少(折叠),那么你将能够在更高层次上思考.这可以清理你的思想,并有助于组织.当然,低级思维也很重要.
你不应该过度使用这些功能 - 有许多陷阱,请参阅Alex Martelli的帖子.我主观上说最严重的危险是过度使用这些功能会破坏代码的可读性,这是Python的核心属性.
归档时间: |
|
查看次数: |
3605 次 |
最近记录: |