你主要是因为什么特性使用Python:函数式还是面向对象?
我注意到在StackOverflow上,很多Python开发者都支持使用一些简洁的功能性工具,比如lambda、map、filter等等。而另一些人则认为不使用这些工具能让代码更清晰、更容易维护。你更喜欢哪种方式呢?
另外,如果你是个死忠的功能编程爱好者或者特别喜欢面向对象编程的人,你还有哪些特定的编程习惯是你觉得最适合你的风格的呢?
提前感谢你的看法!
6 个回答
我既是个狂热的面向对象编程(OOP)爱好者,也喜欢函数式编程(FP),这两种风格其实可以很好地结合在一起,因为它们是完全独立的。很多编程语言都支持面向对象和函数式编程,Python就是其中之一。
简单来说,把一个应用拆分成多个类在设计系统时非常有帮助。而在实际编写代码时,函数式编程能帮助你写出更正确的代码。
我觉得你说函数式编程就是“到处使用折叠(fold)”这点非常不妥。这可能是对函数式编程最大的误解。关于这个话题已经有很多讨论,我只想说,函数式编程的一个好处就是可以把简单的(正确且可重用的)函数组合成新的、更复杂的函数。这样的话,写出“几乎正确”的代码就很难了——要么整个代码完全按照你的想法运行,要么就完全不行。
在Python中,函数式编程主要是围绕着编写生成器和相关的东西(比如列表推导式)以及itertools
模块中的内容。显式地使用map/filter/reduce这些调用其实是没必要的。
我会使用语言中的那些功能,尽量用最简洁、最干净的代码来完成任务。如果这意味着我需要把两种方法混合在一起,那我也会这么做,因为这样能解决问题。
我主要使用Python进行面向对象和过程式编程。其实,Python并不是特别适合函数式编程。
很多人认为通过使用大量的lambda
、map
、filter
和reduce
在写函数式Python代码,但这其实有点过于简单化了。函数式编程的一个重要特点是没有状态或副作用。函数式编程的关键要素包括纯函数、递归算法和一等函数。
以下是我对函数式编程和Python的一些看法:
纯函数非常好。 我尽量让我的模块级函数保持纯粹。
- 纯函数可以被测试。因为它们不依赖外部状态,所以测试起来要简单得多。
- 纯函数可以支持其他优化,比如记忆化和简单的并行处理。
基于类的编程也可以是纯粹的。 如果你想用Python类实现类似纯函数的效果(有时候这样做是有意义的,但并不总是),
- 让你的实例不可变。特别是,这主要意味着让你的方法总是返回新实例,而不是修改当前实例。
- 使用依赖注入,而不是从全局范围获取东西(比如导入的模块)。
- 这可能并不总是正是你想要的。
不要试图完全避免状态。 在Python中,这并不是一个合理的策略。例如,使用
some_list.append(foo)
而不是new_list = some_list + [foo]
,前者更符合习惯,也更高效。(实际上,我看到的很多“函数式”解决方案在算法上比那些同样简单或更简单的非函数式解决方案要差。)从函数式编程中学习最好的教训,比如可变状态是危险的。 问问自己,我真的想改变这个X,还是想要一个新的X?
一个常见的情况是在处理列表时。我会使用
foo = [bar(item.baz()) for item in foo]
而不是
for index, _ in enumerate(foo): foo[index] = bar(foo[index].baz())
以及类似的东西。这可以避免一些混淆的错误,比如同一个列表对象在其他地方被存储而不应该被改变。(如果它应该被改变,那么很可能你有设计错误。改变一个在多个地方引用的列表并不是共享状态的好方法。)
不要随便使用
map
和类似的东西。 这样做并没有更函数式。map
/filter
并不比列表推导式更函数式。列表推导式是从Haskell(一个纯函数式语言)借来的。map
,尤其是filter
,可能比列表推导式更难理解。我不会在使用lambda
时使用map
或filter
,但如果我有一个已经存在的函数,我会使用map
。- 同样的道理适用于
itertools.imap
/ifilter
与生成器表达式之间。(这些东西有点懒惰,这是我们可以从函数式世界借来的一个好东西。) - 不要为了副作用使用
map
和filter
。我经常看到map
被这样使用,这样会导致难以理解的代码和不必要的列表,而且显然不是函数式的(尽管人们认为它一定是因为有map
)。直接使用for循环就好。 reduce
除了非常简单的情况外都很混乱。Python有for循环,使用它们没有坏处。
不要使用递归算法。 这是函数式编程中Python不太支持的一部分。CPython(我想所有其他Python也是)不支持尾调用优化。使用迭代代替。
只有在你需要动态定义函数时才使用
lambda
。 匿名函数并不比命名函数更好,后者通常更健壮、可维护且有文档。