Python是一门挺好的语言,但用着久了,也还是想吐槽一下:)

不够方便的文档

一直都觉得Python的文档不够方便。自己在使用上遇到的一个主要问题是对象类型查看不便。举个简单的例子,sys模块中定义的_current_frames方法,在文档中描述了其功能,

sys._current_frames()

Return a dictionary mapping each thread’s identifier to the topmost stack frame currently active in that thread at the time the function is called. Note that functions in the traceback module can build the call stack given such a frame.

...

然而这里返回的frame对象的定义又在哪呢?没有办法直接从这里点击跳转。那frame的定义在何处呢?在inspect里面可以找到,但即便在这个页面内也没有页面内的跳转。不知道其他人是怎么解决这个问题的,自己很多时候都是去shell下help一下看看,有够原始的。

不一致的命名

Python标准库中的模块存在好几种不同命名方式,比如,

StringIO
cStringIO
copy_reg
linecache

第一次见到的时候对这个事情是非常困惑的,因为这种不一致的命名方式增加了记忆的复杂度。在标准库都使用了各种不一致命名的情况下,各种第三方库则是选择了各自偏好的命名方式,于是实际项目中这种不一致的命名就会泛滥开来。

除了命名方式之外,还有一个略困惑的事情就是,urllib、urllib2。不知道当时的设计者是怎么考虑的,竟然在标准库里面出现了这种模块,于是也难怪出现了第三方的urllib3。一言不合就实现新版本,然后名称+1..

不符合预期的函数默认参数

Python函数默认参数中如果使用了mutable对象,那么这个参数是共享的,

def foobar(nums=[]):
  nums.append(1)
  print sum(nums)

>>> foobar()
1
>>> foobar()
2
>>> foobar()
3

这是一个容易产生bug的语言特性,曾经也踩过这个坑。StackOverflow也有关于此的讨论。不少Python Best Practice会提醒我们避开这种使用方式,但如果一开始就符合预期,应该会更好。

不符合直觉的循环中的lambda定义

同样也是一个容易产生bug的特性,

funcs = []
for x in xrange(5):
  funcs.append(lambda: x + 1)
for func in funcs: print func()

上述代码的输出全都是5,lambda中的x被绑定至了最后一次出现的值。因为这种不当使用导致的bug,估计不少人都遇到过。

记不清的__getattr__, __getattribute__

这两个函数其中一个是每次属性访问的时候都会被调用、另一个是当对象上没有该属性时被调用。功能很容易理解,但记忆上却很容易混淆,每次记清楚之后一段时间未用,就又搞不清了,只能运行遍代码试试。

原本attr就是attribute的缩写,于是记忆混淆就难免了。Python邮件组里曾经也有过关于这个的讨论。

容易被忽略的','

不止一次遇到过因为赋值语句末尾的逗号导致的类型异常,这确实是很蠢的一个错误,但偏偏不太容易注意到,

foo = 'foo',

上述语句末尾的','会导致foo变成一个tuple,而不是str。有时候真的是不小心或是无意识按到了',',然后运行时就悲剧了。另一方面Python的tuple定义也是有点小混淆的,

t = tuple()  # create tuple
t = ()       # create tuple
t = (1,)     # create tuple
t = 1,       # create tuple
t = (())     # create tuple
t = (1)      # just integer
t = (x for x in xrange(5))  # create generator

()这个操作符在不同使用方式中语义是不同的,是不是有点烦人呢?

可以动态添加属性的函数与不能动态添加属性的容器类(list, dict, set...)

在命令行下执行下列代码,

# no error
def foobar(): pass
foobar.name = 'foobar'

# exception rased, AttributeError: 'dict' object has no attribute 'name'
foobar = {}
foobar.name = 'foobar'

很奇怪为什么Python中的函数可以直接进行类似的动态设置属性操作,而常见的容器类们都不可以。函数属性这一条还是PEP232中通过的。StackOverflow也有不错的讨论。

这个特性的功能不难理解,但为啥别的一些类型不可以。只是单纯的对一致性产生了怀疑。

可以被修改的True、False

试试下面这小段代码,

True, False = False, True
if 1 > 0 is True:
  print 'correct'
else:
  print 'wrong'

这个问题在Python 3中得到了解决,True/False/None已经变成了保留字,但在2.x中还是依然如此,希望没人在正式代码中来这么一下。

不被推荐使用的for..else, while..else

为什么Python中加入了for..else、while..else,StackOverflow上这个问题有不少有价值的讨论。曾经尝试过在代码中使用,但一段时间之后自己就忘记了这个else到底是什么时候会进入触发。

这两个else所期望实现的功能是很明确的,但这个功能却用了else来进行标识,这与if..else中的else起始含义是不一样的,同样的名字不一样的含义,自然就是不好记忆的。

''、""、''''''、""""""

Python字符串定义可以使用这几种符号,

foobar = 'foobar'
foobar = "foobar"
foobar = '''foobar'''
foobar = """foobar"""
foobar = 'foo"bar'
foobar = "foo'bar"
foobar = """foo'''bar"""
foobar = '''foo"""bar'''

对单引号、双引号都支持,个人理解是为了方便。这两组对应的使用方式是等价的,但PEP257中竟然说为了保持一致docstring推荐使用"""""",这又是为什么?真有保持一致的需要,就不应该对单引号、双引号都支持。否则真是平添了StackOverflow上的这些讨论。

空格与Tab

Python的缩进像大多数语言那样支持空格与Tab,但Python与常用语言的差异也在于Python中的缩进是有意义的,不匹配的缩进会导致语法错误。在代码中混用空格、Tab是一件危险的行为。于是不同的项目可能约定不同的缩进方式,于是也有了网上很多无谓的讨论

如果Python的缩进不会导致语法错误,那么不对此进行约定是很正常的。可偏偏缩进就有可能导致错误,这种时候语言应该对此进行强制限定才是。

其它

一些众所周知的问题这里就没有必要再记录了。Python最大的问题可能还是Python2、Python3的分裂。可惜没有机会在正式项目中使用Python3,不然倒也想实际对比下两者的差异与改进。

最后还是来看下The Zen of Python,

import python
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

与其说是The Zen of Python不如说是The Zen of Programming,可惜其中一些条目是很难一如既往的保持的。

比如,

Special cases aren't special enough to break the rules.

不少语言特性的引入就是为了解决一些并不常见的情况。但解决这些问题真的有必要直接增加语言特性吗?

再比如,

In the face of ambiguity, refuse the temptation to guess.

混淆的特性、混淆的缩写命名..

再再比如,

There should be one-- and preferably only one --obvious way to do it.

这一条感觉太太难以实现了。看看网上的各种讨论就知道,这一条大部分时候也只是停留在纸面上。

总结

写了那么多,主要还是希望不论Python还是自己实现的代码尽量符合这里定义的“Zen”。一些问题估计也是历史遗留问题,为了保持兼容,不得不保持现状。

另外也可以发现即便Python有一些缺点,但它无疑是一门成功的语言,而且依然还处在发展期。这足以说明在现实中,是否能够获得的成功,不在于缺点如何,而在于优点如何。让优点足够优秀,那么就有可能从竞争中胜出。