python: 这些地方请你小心
python是一门非常有趣的语言。它提供了许多非常方便的标准库和许多内置命令是我们轻松完成任务.但是好东西太多了就有选择恐惧症了,以至于我们不能很好第利用这个标准库和它的基本机构。下面列出了一些对python新手来说很简单有效的陷阱。
忽略了python的版本
这是一个在StackOverflow上不断被人提起的问题。当你完美的代码跑在别人的电脑上就报错是怎样一种体验,所以这个时候就需要检查你们的python版本是否一致。确保代码跑在自己知道的python版本上。你可以通过以下代码查看python版本:
$ python --version
Python 2.7.9
python版本管理
pyenv是一个不错的python版本管理工具。不幸的是它只运行在*nix系统上。在Mac OS上,你可以简单用brew install pyenv安装,在linux系统中,有一个自动安装器automatic installer
纠结于用一行代码解决所有问题
许多人夸口说我多牛用一行代码就解决了所有问题,即便他们的代码比正常写的更缺少效率,而且这些代码也会更难以阅读,甚至会出现歧义。比如说:
l = [m for a, b in zip(this, that) if b.method(a) != b for m in b if not m.method(a, b) and reduce(lambda x, y: a + y.method(), (m, a, b))]
老实说上面的代码是我自己为了说明这件事情写的。但是我可是真的见过有许多人这样干过。如果你只是简单通过把东西添加到一个list或者一个set中来显摆自己解决复杂问题的手段,那么你有可能会得不偿失。
一行代码控并不是什么巨大的成就,尽管有时候看起来特别聪明。优秀的代码是简洁但是更注重高效和易读。
错误地初始化set
这是一个更加微妙的问题,有时候会让你措手不及。set推导式起来有点像list推导式.
>>> { n for n in range(10) if n % 2 == 0 }
{0, 8, 2, 4, 6}
>>> type({ n for n in range(10) if n % 2 == 0 })
上面的例子说明了这点。set有点像放在容器中的list 它们的区别是set没有重复的值和无序的。人们通常会把{}认为是一个空的set,可它不是,它是一个空的dict.
>>> {}
{}
>>> type({})
所以如果我们想要初始化一个空的set,就直接使用set()
>>> set()
set()
>>> type(set())
注意一个空的set可以表示成set(),但是一个包含了元素的集合要被定义成set([1, 2])的样子。
误解了GIL
GIL(全局解释器锁)意味着只有一个线程在一个Python程序可以运行在任何时间。 这意味着当我们不能创建一个线程,并期望它并行运行。 Python解释器实际上做的是快速切换不同的运行线程。 但这是一个非常简单的版本。 在许多实例中程序并行运行,像使用C扩展的库时。 但Python代码运行时,大多数时候不会并行执行。换句话说,线程在Python中不像在Java或c++中一样。
许多人会尝试为Python辩解说,这些都是真正的线程。 3 这确实是真的,但并不能改变这样一个事实:Python处理线程的方式不同于你期望的那样。 Ruby也有类似的情况(还有一个解释器锁)。
规定的解决方案是使用multiprocessing模块。multiprocessing模块提供的过程类基本上可以很好地覆盖分歧。 然而,分歧比线程代价高得多。所以并行运行不总是好的。
然而,这个问题不是每个Python程序都会遇到。PyPy-stm就是Python的一个实现不受GIL影响的例子。 实现建立在其他平台上的像JVM(Jython) 或CLR(IronPython)没有GIL的问题。
总之,在使用时要小心线程类,你得到的可能不是你想要的。
使用过时的样式类
在Python 2有两种类型的类,“旧式”类,“新风格”类。 如果 你使用Python 3,那么你正在使用默认的“新风格”类。 为了确保你使用 “新风格”在Python 2类,您需要继承object或者任何你创建的不总是继承内建指令int或list的新类 。 换句话说,你的基类,应该总是继承object。
class MyNewObject(object): # stuff here
这些’新类’修复了一些非常基本的出现在老式类中问题,如果你感兴趣可以查看文档
错误的迭代
下面的这些错误对新手来说非常常见:
for name_index in range(len(names)):
print(names[name_index])
很明显没有必要使用len, 实际上遍历列表用非常简单的语句就可以实现:
for name in names:
print(name)
此外,还有一大堆其他的工具在你处理简化迭代。 例如,zip可以用来遍历两个列表:
for cat, dog in zip(cats, dogs):
print(cat, dog)
如果我们要考虑索引和值列表变量,我们可以使用enumerate
for index, cat in enumerate(cats):
print(cat, index)
在itertools中还有很多功能可以选择。如果itertools中有你想要的功能就很方便的拿来用。但是也不要过于为了用它而用它。
itertools滥用使得StackOverflow上的一个大神花了很多时间去解决它。
使用了可变的默认参数
我看过很多如下:
def foo(a, b, c=[]):
# append to c
# do some more stuff
不要使用可变的默认参数,而不是使用以下:
def foo(a, b, c=None):
if c is None:
c = []
# append to c
# do some more stuff
下面这个例子可以很直观地帮我们理解这个问题:
In[2]: def foo(a, b, c=[]):
... c.append(a)
... c.append(b)
... print(c)
...
In[3]: foo(1, 1)
[1, 1]
In[4]: foo(1, 1)
[1, 1, 1, 1]
In[5]: foo(1, 1)
[1, 1, 1, 1, 1, 1]
相同的 c 被引用一次又一次的每一次调用该函数。 这可能会产生一些非常 不必要的后果。