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

lest 发布于 2019-11-10 python 最后更新 2019-11-10 12:10 83 浏览

在工作中,我们用一种非常标准的面向对象的方式来编写我们的Python。最近,几个人开始了功能性的潮流。他们的代码现在包含更多的lambda表达式,映射和缩减。我知道函数式语言对并发有好处,但是编程Python在功能上确实有助于并发?我只是想了解如果我开始使用更多Python的功能特性,我会得到什么。

已邀请:

isunt

赞同来自:

Map和Filter在OO编程中占有一席之地。列表推导和生成器函数旁边。 少减少。减少算法可以快速吸收比它应得的更多的时间;通过一点思考,手动编写的reduce循环将比reduce更有效,reduce将一个经过深思熟虑的循环函数应用于序列。 Lambda永远不会。 Lambda没用。人们可以说它实际上做了某些事情,所以它并非完全没用。第一:Lambda不是句法“糖”;它使事情变得更大,更丑陋。第二:10,000行代码中的一次认为你需要一个“匿名”函数,在20,000行代码中变成了两次,这消除了匿名的价值,使其成为维护责任。 然而。 无对象状态变化编程的功能风格本质上仍然是OO。您只需创建更多对象并减少对象更新。一旦开始使用生成器功能,许多OO编程就会向功能方向漂移。 每个状态更改似乎都转换为生成器函数,该函数从旧对象构建新状态的新对象。这是一个有趣的世界观,因为对算法的推理要简单得多。 但是没有使用reduce或lambda的调用。

eipsum

赞同来自:

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

does programming Python functionally really help with concurrency?
否.FP值带来的并发性在于消除计算中的状态,这最终导致并发计算中意外错误的难以掌握的肮脏。但这取决于并发编程习语本身不是有状态的,不适用于Twisted。如果有利用无状态编程的Python的并发习惯用法,我不知道它们。

get

赞同来自:

内容太长未翻译

wanimi

赞同来自:

内容太长未翻译

gsint

赞同来自:

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

  • 列表推导是从FP语言Haskell导入的。他们是Pythonic。我更愿意写
y = [i*2 for i in k if i % 3 == 0]
而不是使用命令式构造(循环)。
  • 在为sort提供复杂的密钥时,我会使用lambda,例如list.sort(key=lambda x: x.value.estimate())
  • 使用高阶函数比使用OOP的设计模式(如visitor或抽象工厂)编写代码更清晰
  • 人们说你应该用Python编写Python,用C++编写C++等等。这是真的,但你当然应该能够以不同的方式思考同一件事。如果在编写循环时你知道你真的正在减少(折叠),那么你将能够在更高的层次上思考。这可以清理你的思想,并有助于组织。当然,低级思维也很重要。
你不应该过度使用这些功能 - 有许多陷阱,请参阅Alex Martelli的帖子。我主观上说最严重的危险是过度使用这些功能会破坏代码的可读性,这是Python的核心属性。

bquos

赞同来自:

我每天都在使用Python编程,我不得不说,对于OO或功能性过多的“徘徊”可能导致缺少优雅的解决方案。我相信这两种范式对某些问题都有其优势 - 我认为当你知道使用什么方法时。在为您提供干净,可读且高效的解决方案时,请使用功能性方法。 OO也是如此。 这就是我喜欢Python的原因之一 - 事实上它是多范式的,让开发人员选择如何解决他/她的问题。

vanimi

赞同来自:

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(“通过乘法”)。其中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提供值为的运算符!但是...... 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)
好吧,这只是半成品方法的神器!放弃命令式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的上半部分John Backus在1977年获得图灵奖时给出的.Quote:
5.1 A von Neumann Program for Inner Product
c := 0
for i := I step 1 until n do
   c := c + a[i] * b[i]
Several properties of this program are worth noting:
  1. Its statements operate on an invisible "state" according to complex rules.
  2. It is not hierarchical. Except for the right side of the assignment statement, it does not construct complex entities from simpler ones. (Larger programs, however, often do.)
  3. It is dynamic and repetitive. One must mentally execute it to understand it.
  4. It computes word-at-a-time by repetition (of the assignment) and by modification (of variable i).
  5. Part of the data, n, is in the program; thus it lacks generality and works only for vectors of length n.
  6. It names its arguments; it can only be used for vectors a and b. To become general, it requires a procedure declaration. These involve complex issues (e.g., call-by-name versus call-by-value).
  7. Its "housekeeping" operations are represented by symbols in scattered places (in the for statement and the subscripts in the assignment). This makes it impossible to consolidate housekeeping operations, the most common of all, into single, powerful, widely useful operators. Thus in programming those operations one must always start again at square one, writing "for i := ..." and "for j := ..." followed by assignment statements sprinkled with i's and j's.

nodit

赞同来自:

标准函数filter(),map()和reduce()用于列表上的各种操作,并且所有这三个函数都需要两个参数:函数和列表 我们可以定义一个单独的函数并将其用作filter()等的参数,如果该函数被多次使用,或者如果函数太复杂而无法写入一行,则可能是个好主意。但是,如果它只需要一次并且它非常简单,那么使用lambda构造生成(临时)匿名函数并将其传递给filter()会更方便。 这有助于readability and compact code. 使用这些函数,也会变成efficient,因为列表元素的循环是在C中完成的,这比在python中循环要快一点。 除了抽象,分组等之外,在维护状态时强制需要面向对象的方式。如果要求非常简单,我会坚持使用功能而不是面向对象编程。