最近QQ挂着Python Logo在某QQ群吹水,有人说Python语法反直觉,又有人说要举个实例而那个说Python语法混乱的人八成也说不出Python语法哪里混乱。而这个时候借着我在某培训班重新看了一遍Python的字符串和列表语法随口说了一句,Python的语法有很多混乱的地方,比如说字符串/列表的方法有的只有副作用而没有返回值,有的只有返回值而没有副作用,以及lambda表达式的值和def写的函数不一样。这时候这个大佬说Talk is cheap,我又试了一下,发现真相是这样的:
Python的列表的方法大多数(除了pop,push这种)是没返回值而只有副作用的,而字符串在Python中间压根是不可变的,更别说什么副作用了。
我感到十分尴尬,毕竟我挂着Python Logo的头像,而这个群的人基本都是会Prolog, CommonLisp, Haskell这样的语言的,而这其实说明了,Python是我最了解的编程语言,但是了解程度还比不上别人的第二甚至第三语言。
曾经我以为Python在副作用上是混乱的,因为Python不在意这一点,不过看来Python并没有这么混乱,它的一致性还是很好的。
以下是在这个群吹水发现的其他东西。
列表推导,For … in循环和函数式编程
如果你读过一些关于lisp语言的宣传,你一定会听说很多语言的特性其实只是重现了lisp在很多年前就做到的事。在这个群有人跟我说Python的列表推导只是filter和map的语法糖。如果稍微考虑一下会发现的确是这样。一般列表推导是这么做的
[func(i) for i in source_list]
这里面的函数实际上也可是有任何值的表达式,实际上我认为它是表达式还是函数也不是什么重要的问题,实际上一看就非常接近
map(func, source_list)
不太了解函数式编程的读者可能不知道map是什么东西,其实map就是它字面的意思,映射,对source_list里面每一个元素得出func的返回值然后组成一个新列表。Python的map和filter得出的其实是一个可迭代对象。这个并不难理解,毕竟如果一开始生成的是可迭代对象,就可以考虑通过流(stream)的方式来处理这个序列,这样合适的时候可以大大提高执行效率和降低内存占用。
其实Python的列表推导不止这些东西,还可以再加个if,虽然这个貌似比较难用到,比如
[func(i) for i in source_list if cond(i)]
这里的if实际就是一个filter
filter其实就是一个过滤器。返回所有对于func(i)为真的对象。
map(func, filter(cond, source_list))
如果你会写scheme之类的括号语言,其实这种结构用括号表达非常漂亮,比如
(map func source_list)
(map func (filter cond source_list))
至于for … in循环,那比起这个就更加显而易见了,本质就是一个filter/map。不再赘述。
既然没加任何额外宏的scheme处理起列表来都这么接近Python的列表推导了,更别说在加几个宏之后了。很容易发现:原来Python里面用起来这么强大和清晰的东西,其实它的原型在lisp里面早就有了。曾经在我刚开始接触scheme的时候会故意避开使用列表推导和for in循环,其实在某种程度上,这是错误的。for in在java/c这样的语言里面,要构造一个列表通常要初始化一个空列表,然后一个一个地改变列表的元素。for in和列表推导里面,其实你可以选择不改变任何东西,也就是不造成任何副作用。一开始我认为这里面有一个i表明i被潜在地改变了,不过现在看来,这并不是多重要。
Python,函数式编程,或者面向对象
有人说Python是一个很简单的语言,认为写Python很简单,其实这并不完全正确。Python只是上手很简单,但是要想写得漂亮是不那么简单的。
最简单的,可能有人认为Python有class这种东西就把Python当作语法改良版的Java,或者像我一样因为Python是函数第一的就把Python当作另一种scheme。但如果你只知道java式的那套“面向对象”或者scheme式的函数式编程都不能说完全知道了Python就是这么写的。从这一点上说,Python比java要难得多。实际上我发现Java本身其实没什么好发掘的,一单你了解了那些东西,java里面也没有更多的思想了。或许你会发现java里面还有一些复杂的东西,但是你迟早会发现java里面难以理解的东西基本都是些自找麻烦,比如设计模式,再比如用各种动物,植物,飞机,汽车之类的东西解释接口/抽象类/内部类,再比如java的访问控制有那么多层。
Python从语法上看完全不是一个完美的或者纯粹的语言。但它有一个良好的学习曲线,上手很简单,熟悉之后还有很多可以进一步发掘的。
有人说Python之禅里面是一些正确而无用的废话,这其实是不了解Python的表现。举个例子,zen of Python说“命名空间是个好东西”,相较java(当然这不是java语言本身的问题,但你会发现写java的人都是这么干的)里面那么玄乎的对于面向对象的解释,Python的理解非常简单粗暴:类和包都是个命名空间。事实上简单来说,类也就是这么个东西。
类似的还有平直胜于嵌套。相应的你其实可以发现装饰器这个东西,装饰器可以明显减少嵌套,被装饰的函数和装饰器是同一级的。漂亮的Python代码显然应该是整齐的,而不是需要游标卡尺的。而据我观察,其实很多人的Python都是参差不齐的。其实这个装饰器也可以对应于写java的人说的“装饰器模式”,只不过写一个装饰器demo就要费掉好几十行代码,而Python只需要一个@
符号。从这里也能发现,被搞得非常玄乎的“设计模式”是一个多扯淡的东西。
据slashdot对Guido的采访,他自己也说自己不怎么会haskell,Python追求的目标是实用。
谈到函数式编程时他说他尝试过消灭filter,map,reduce这种东西,但是最终保留了filter和map,因为列表推导已经可以部分代替map和filter,reduce则移到了标准库里面。其实标准库里面还有其他的函数式工具。
列表推导和filter,map的一个不同是,列表推导看起来非常接近自然语言。我相信任何懂点英语的人都能看出那是什么意思。而filter,map这种东西,如果你不了解什么是函数式编程,那你几乎看不出这是什么意思。这也再次印证了Python之禅的内容:可读性很重要。
顺带说一句,只用Python其实是体验不到纯粹的函数式编程的,因为 Python没有高阶函数,也没有尾递归,而且Python之父明确表示Python不会支持尾递归,另外Python的lambda表达式只能写一行,而lambda表达式对于函数式编程是很重要的,毕竟第一个函数式语言的数学基础就叫做lambda演算。这个时候你或许可以考虑使用hylang。Hylang只有受限的尾递归,和jvm平台上的clojure一个毛病,但是有不受限的lambda和宏。这样其实SICP或者是The little schemer上的很多代码,Hy或者是clojure是不好写得一样漂亮的。据说最纯粹的函数式语言是Haskell,它里面想要产生副作用只有用monand算子,而这个概念在数学上是非常新的范畴论的内容。
参考: [slashdot对Guido的采访:link]