len(x) 击败 x.len(),从内置函数看 Python 的设计思想(内有公号宣传,不喜勿进)

2019-04-21 20:44:05 +08:00
 chinesehuazhou

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

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

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

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

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

同样是求字符串长度,Python 的写法:

saying = "Hello world!"
print(len(saying))

# 结果:12

而在 Java 中,写法可能如下(简化起见):

String saying = "Hello world!";
System.out.println(saying.length());

// 结果:12

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 中列表的两个方法为例:

mylist = [2, 1, 3, 5, 4]

mylist.sort()
print(mylist)   # [1, 2, 3, 4, 5]

mylist.reverse()
print(mylist)   # [5, 4, 3, 2, 1]

它们都是通过列表对象来调用,并不是凭空从内置命名空间中拿来的。语义表达得也很清楚,就是对 mylist 做排序和逆转。

恰恰那么巧,它们还有两个同父异母的兄弟 sorted() 与 reversed(),这俩是前缀表达型。

mylist = [2, 1, 3, 5, 4]

sort_list = sorted(mylist)
print(sort_list)   # [1, 2, 3, 4, 5]

reverse_list = reversed(mylist)
print(list(reverse_list))   # [4, 5, 3, 1, 2]

不同的写法,都在做同一件事(不考虑它们的副作用)。因此,后缀语法并非不可行,之所以不用,那肯定是刻意的设计。

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

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

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

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

优雅易懂是 Python 奉行的设计哲学 ,len() 函数的前缀表达方式是最好的体现。我想起在《超强汇总:学习 Python 列表,只需这篇文章就够了》这篇文章中,曾引述过 Guido 对“为什么索引从 0 开始 ”的解释。其最重要的原因,也正是 0-based 索引最优雅易懂。

让我们来先看看切片的用法。可能最常见的用法,就是“取前 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。

关联阅读:

Guido 解释 len 的由来: https://mail.python.org/pipermail/python-3000/2006-November/004643.html

Guido 解释 0 索引的由来: https://python-history.blogspot.com/2013/10/why-python-uses-0-based-indexing.html

本文原创并首发于公众号 [Python 猫] ,未经授权,请勿转载。

原文地址: https://mp.weixin.qq.com/s/pKQT5wvyaSNFvnJexiCC8w

公众号 [Python 猫] , 本号连载优质的系列文章,有喵星哲学猫系列、Python 进阶系列、好书推荐系列、技术写作、优质英文推荐与翻译等等,欢迎关注哦。后台回复“爱学习”,免费获得一份学习大礼包。

9967 次点击
所在节点    Python
103 条回复
ryd994
2019-04-21 21:38:57 +08:00
作为 C 程序员,我只能说,你们这些有对象的,屁事真多
怎么不说明白 len 只不过是个语法糖,实际上调用的是__len__
chinesehuazhou
2019-04-21 22:20:05 +08:00
@ryd994 实际上是调的__len__,但在用 len() 的时候不需要知道,更不需要写成调用的方式。同样比如说 Java,他也可以把类似调用__len__的方法藏起来,直接用 len(),但是没有。相比较而言,Python 的这种设计就是更优雅。(糖真甜)
cnt2ex
2019-04-21 23:30:23 +08:00
Guido 的两个理由根本不足以解释为什么用 len(x)而不是 x.len()
>对于某些操作,前缀符比后缀更好读
这一点完全和 len(x)和 x.len()之间选择无关。你只是说明了在表达*分配律*的时候用中缀比较好,与 len(x)和 x.len()之间选择有什么关系?我难道可以说,因为在表达*分配律*的时候,中缀优于前缀。所以在*写 n 个数求和*时 1+2+3+...+n 优于 sigma 求和符号?
同样地,第二点也没法说明选择 len(x)而不是 x.len()。

而且考虑到一致性上,获取一个对象的长度,显然 x.len()这种面向对象的风格更好。
yujincheng08
2019-04-21 23:36:06 +08:00
len(x)的时候并不需要事先知道 x 是容器。所以最后程序跑着跑着突然说这句话出问题了,不能求 x 的 len。。。

我觉得把这种意外放到运行时就很蠢。。
9hills
2019-04-21 23:48:33 +08:00
@yujincheng08 在 Python 里 x.len()如果没有这个方法也是运行时才能发现的,效果一样。

动态语言就这样,没办法
MonoLogueChi
2019-04-22 00:09:13 +08:00
桌子类有桌子类求长度的方法,椅子类有椅子类求长度的方法,那桌子椅子类都是继承物体类,物体类有求长度的方法不就可以了嘛,我个人的理解是这样的,也可能是 C#写多了,有些写法看着不习惯了
wwqgtxx
2019-04-22 00:12:13 +08:00
@yujincheng08 像 python 这种随时可以在运行中给对象添加一个 function 的,x.len()和 len(x)都不需要也没办法在编译时知道 x 是不是容器
wwqgtxx
2019-04-22 00:20:02 +08:00
@MonoLogueChi python 有种习惯叫"鸭子类型"即一个类实现了某种方法,它不需要继承某个接口也可以称为支持某操作的类型,最常见的就是 file-likes object 和 path-likes object 了,这两个在 python 官方文档中大量出现的 duck type
这个个人感觉和 golang 的 interface 有点像,都不需要显示继承
而 python 的 len(x)就是 x.__len__()的语法糖,只要 x 存在__len__这个函数就可以求长度,不需要继承别的语言的类似 XXXable 这种接口
darkbread
2019-04-22 00:29:40 +08:00
兄 dei 建议你了解下 C#的 Extention
qiyuey
2019-04-22 00:38:08 +08:00
有一个儿子,想找到他的爹,也有两种方法:找爹(一个儿子);一个儿子.爹()
jinliming2
2019-04-22 01:49:27 +08:00
感觉语义上的不同,len() 更像是进行测量、获取长度这个动作,而 .len 更像是读取一个已经存在自身上的一个属性,.len() 也是进行测量、获取长度这个动作。
len(x) 和 x.len() 的区别是,一个是别人来测量自己,一个是自己测量自己。
len(x) 没有办法预先确定要测谁的长度,所以只能每次都重新数一下。而 x.len() 就可以直接把自己的长度存起来,变化的时候改一下就行,每次返回保存的值就行。这么看来,x.len() 的设计更好。
PY 里面的 len() 比较聪明,不会自己去数,而是调用 __len__ 直接抄答案……
msg7086
2019-04-22 04:43:34 +08:00
看了半天感觉就是把一个很无所谓的设计强行解释出了高大上的感觉。
原本只要对一个对象求长度,像 obj.len()这样简单的操作,特地去增加一个不伦不类的 built-in operation,让原本简单、统一的 容器.操作() 操作变成了突兀、怪异的 操作(容器) 方法,然后再强行解释说把简单的事情复杂化是为了「化繁为简」?

那为什么要还用 list.append(item) 呢,不如花点时间扯一片文章,把 append(item, list) 当成对世界本质的洞察,然后再强迫别人去记忆这种奇怪的语法,来体现人生苦短呢。毕竟据说当你读到这样的语法的时候,「就 知道 这是」把项目放进对象里了呢。
lilpig
2019-04-22 05:07:41 +08:00
体会不出什么别的,反正个人很不爱写 len(x)。。。😂
fakeshadow
2019-04-22 05:44:35 +08:00
对世界本质的洞察。。。
ColinZeb
2019-04-22 07:02:30 +08:00
理解不了,只感觉是尬吹。面向对象的哲学性更好,像这样的文章可以写十本书
Cbdy
2019-04-22 07:05:58 +08:00
恕我直言,这是个不好的设计。
LokiSharp
2019-04-22 07:26:38 +08:00
x.len()不是挺好的么
love
2019-04-22 07:55:40 +08:00
函数式也有好处的,比如得到一堆长度
array.map(len)

不过 py 函数式并不方便,还不如 js。
zjsxwc
2019-04-22 07:58:30 +08:00
强行解释,掩盖设计问题
ChefIsAwesome
2019-04-22 08:10:08 +08:00
大部分语言:1 + 1
closure: + 1 1

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/557286

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX