len(x)击败x.len(),从内置函数看Python的设计思想

内置函数是Python的一大特色,用极简的语法实现很多常用的操作。

它们预先定义在内置的命名空间(Built-ins)中,开箱即用,所见即所得。Python被公认为是一种新手友好型语言,这种说法能够成立,内置函数在其中起到了极其关键的作用。

举个例子:求字符串 x 的长度,Python的写法是 len(x) ,而且这种写法对列表、元组和字典等对象也同样适用,只需要传入对应的参数即可。len() 函数是共用的。

这是一种极简哲学的体现:Simple is better than complex.

但是,有些语言并不是这样,例如在Java中,字符串类有一个求长度的方法,其它类页游自己的求长度的方法,它们无法共用。每次使用时,通过类或实例来调用。

同样是求字符串的长度,Python的写法:
111
而在Java中,写法可能如下(简化起见):
222
Python采用的是一种前缀表达式,而Java采用的则是后缀表达式

除了求长度,Python的某些内置函数也能在Java中找到对应的表达。
例如:数值型字符串 s 转化为整型数字,Python可以用 int(s) 函数,而Java可以用 Integer.parseInt(s) ;整型数字转化为字符串,Python可以用 str(i) ,而Java也有 String.valueOf(i)

Python的内置函数不与特定的类绑定,它们是一级对象。而Java的‘函数’则无法脱离类而存在,它们只是类的附属品。

从直观角度来看,Python的表达似乎是更优的。但是,它们并不具有可比性,因为这是两套语言系统,各有独特的范畴背景,不能轻易的化约。

就好比是,不能因为拉丁字母笔画简单,就说它优于汉字,因为在表意时,字母(表音文字)是远逊于汉字(表意文字)的。同样的,日本借用了汉字的偏旁部首造出来的文字,虽然更省笔墨,但是也完全丧失了意蕴。

以此类比,Python的内置函数虽有简便之美,但却丢失了某些表意功能。有些人在质疑、抨击Python的时候,喜欢拿这点说事,认为这是Python的设计缺陷。

这就引出本文最想讨论的一个问题:为什么Python要设计成 len(x) 这种前缀表达,而不是 x.len() 这样的后缀表达呢?

事实上,后缀设计也是可行的,以Python中列表的两个方法为例:
333
它们都是通过列表对象来调用的,并不是凭空从内置明明空间中拿来的。语义表达的也很清楚,就是对 mylist 做排序和逆转。

恰恰那么巧,它们还有两个同父异母的兄弟 sorted()reversed() ,这俩是前缀表达型。
444
不同的写法,都在做同一件事(这里先不考虑它们的副作用)。因此,后缀语法并非不可行,之所以不用,那肯定是有意为之。

回到前面的问题:为什么是 len(x) ,而非 x.len(x) ,这源于Python的什么设计思想呢?

Python之父 Guido van Rossum 曾经解释过这个问题(链接见文末),有两个原因:

  • 对于某些操作,前缀符比后缀更好阅读----前缀(和中缀)表示法在数学中有着悠久的历史,其视觉效果有助于数学家思考问题。我们可以简单的把公式 x*(a + b) 重写成 x*a + x*b 但是同样的事,以原生的面向对象的方式实现,就比较笨拙。
  • 当读到 len(x) 时,我就知道这是在求取某对象的长度。它告诉了我两点信息:返回值是一个整数,参数是某种容器。但当读到 x.len() 时,我必须事先知道某种容器 x ,它实现了一个接口,或者继承了一个拥有标准 len() 方法的类。我们经常会目睹到这种混乱:一个类并没有实现映射(mapping)接口,却拥有 get()keys() 方法,后者某些非文件对象,却拥有一个 write() 方法。

解释完这两个原因之后,Guido还总结成一句话说:“I see ‘len’ as a built-in operation”。这已经不仅在说 len() 更可读易懂了,而完全是在拔高 len() 的地位。

这就好比说,分数 1 2 \frac{1}{2} 21中的横线是数学中的一个“内置”表达式,并不需要再实现什么接口之类的,它自身已经表明了“某数除以某数”的意思。不同类型的数(整数、浮点数、有理数、无理数…)共用同一个操作符,不必为每类数据实现一种求分数的操作。

优雅易懂是Python奉行的设计哲学len() 函数的前缀表达方式是最好的体现。Guido曾对“为什么在Python中索引是从0开始”作出过解释,其最重要的原因,也正是 0-based 索引最优雅易懂,而且使用起来更加的方便。

其实这种优雅体现在了Python的切片当中。  
我们在切片当中最常见的用法,  
就是“取前n个元素”或“从第i位索引开始,取n个元素”  
(前一种用法,实际上是i==起始位的特殊用法。)  
如果这两种用法实现时可以不在表达式中出现难看的+1或-1,那将会非常的优雅。  

使用0-based的索引方式、半闭半开区间切片和缺省匹配区间的话(Python最终采用这种方式),  
上面两种情形的切片语法就变得非常漂亮:a[:n]和a[i:i+n],前者是a[0:n]的缩略写法。  

所以,我们能说 len(x) 击败 x.len() ,支撑它的是一种化繁为简、纯粹却深邃的设计思想。

面向对象的编程语言自发明时起,就想模拟我们生活于其中的现实世界。可是什么类啊、接口啊、对象啊、以及它们的方法啊,这些玩意儿的毒,有时候蒙蔽了我们去看见世界本质的眼睛。

桌子类有桌子类的求长度方法,椅子类有椅子类的求长度方法,无穷无尽,可现实真是如此么?求长度的方法就不能是一种独立存在的对象么?它之所以存在,是因为有“对象”存在,而不是因为有某个类才存在啊。

所以,我想说, len(x) 击败 x.len() ,这还体现了Python对世界本质的洞察

求某个对象的长度,这种操作独立于对象之外而存在,并不是该对象内部所有的一种属性或功能。从这个角度去理解,我们能够明白,**为什么Python要设计出内置函数?**内置函数其实是对世界本质的一种捕捉。

这些见微知著的发现,足够使我们爱上这门语言了。人生苦短,我用Python

OK,一顿吹B之后,再给各位奉上满满的干货:
Guido解释 len 的由来:http://suo.im/4ImAEo
Guido解释以0为起始索引的由来:http://suo.im/5cr12S

感谢本文原创作者Python猫的分享!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值