Python 2.x陷阱和地雷

我的问题的目的是用Python加强我的知识基础,并更好地了解它,其中包括了解它的缺点和意外。 为了保持特定的内容,我只对CPython解释器感兴趣。

我正在寻找类似于从我的PHP地雷问题中学到的东西,其中一些答案是我所熟知的,但是有一对是可怕的。

更新:显然,也许有两个人不高兴,我问了一个问题,这个问题在Stack Overflow之外已经被部分地回答了。 作为某种妥协的URL http://www.ferg.org/projects/python_gotchas.html

请注意,这里的一个或两个答案已经是从上面引用的网站上所写的内容中得到的。

默认参数中的expression式是在函数被定义时计算的, 而不是被调用的。

示例:考虑默认当前时间的一个参数:

>>>import time >>> def report(when=time.time()): ... print when ... >>> report() 1210294387.19 >>> time.sleep(5) >>> report() 1210294387.19 

when参数不会改变。 在您定义函数时进行评估。 在应用程序重新启动之前它不会改变。

策略:如果您将默认参数设置为“ None ,那么您不会绊倒,然后在看到它时执行一些有用的操作:

 >>> def report(when=None): ... if when is None: ... when = time.time() ... print when ... >>> report() 1210294762.29 >>> time.sleep(5) >>> report() 1210294772.23 

练习:确保你已经明白了:为什么会这样呢?

 >>> def spam(eggs=[]): ... eggs.append("spam") ... return eggs ... >>> spam() ['spam'] >>> spam() ['spam', 'spam'] >>> spam() ['spam', 'spam', 'spam'] >>> spam() ['spam', 'spam', 'spam', 'spam'] 

你应该知道如何在Python中处理类variables。 考虑下面的类层次结构:

 class AAA(object): x = 1 class BBB(AAA): pass class CCC(AAA): pass 

现在,检查下面的代码的输出:

 >>> print AAA.x, BBB.x, CCC.x 1 1 1 >>> BBB.x = 2 >>> print AAA.x, BBB.x, CCC.x 1 2 1 >>> AAA.x = 3 >>> print AAA.x, BBB.x, CCC.x 3 2 3 

惊讶吗? 如果您还记得类variables是作为类对象的字典在内部处理的,则不会出现这种情况。 如果在当前类的字典中找不到variables名,则会search父类。 所以,下面的代码再一次,但有解释:

 # AAA: {'x': 1}, BBB: {}, CCC: {} >>> print AAA.x, BBB.x, CCC.x 1 1 1 >>> BBB.x = 2 # AAA: {'x': 1}, BBB: {'x': 2}, CCC: {} >>> print AAA.x, BBB.x, CCC.x 1 2 1 >>> AAA.x = 3 # AAA: {'x': 3}, BBB: {'x': 2}, CCC: {} >>> print AAA.x, BBB.x, CCC.x 3 2 3 

在类实例中处理类variables也是一样的(把这个例子看作是上面的例子的延续):

 >>> a = AAA() # a: {}, AAA: {'x': 3} >>> print ax, AAA.x 3 3 >>> ax = 4 # a: {'x': 4}, AAA: {'x': 3} >>> print ax, AAA.x 4 3 

循环和lambdas(或任何闭包,真的):variables被名称绑定

 funcs = [] for x in range(5): funcs.append(lambda: x) [f() for f in funcs] # output: # 4 4 4 4 4 

解决方法是创build一个单独的函数或按名称传递参数:

 funcs = [] for x in range(5): funcs.append(lambda x=x: x) [f() for f in funcs] # output: # 0 1 2 3 4 

dynamic绑定使得你的variables名中的拼写错误很难find。 修复一个微不足道的错误很容易花半个小时。

编辑:一个例子…

 for item in some_list: ... # lots of code ... # more code for tiem in some_other_list: process(item) # oops! 

我曾经对Python有过的最大的惊喜之一就是这个:

 a = ([42],) a[0] += [43, 44] 

这可以像预期的那样工作,除了在更新元组的第一个元素之后引发TypeError! 所以在执行+=语句之后([42, 43, 44],) a会是([42, 43, 44],) ,但是反正会有一个exception。 如果你尝试这个另一方面

 a = ([42],) b = a[0] b += [43, 44] 

你不会得到一个错误。

 try: int("z") except IndexError, ValueError: pass 

原因这是行不通的,因为IndexError是你正在捕获的exception的types,而ValueError是你指定exception的variables的名称。

正确的代码来捕获多个exception是:

 try: int("z") except (IndexError, ValueError): pass 

有一段时间关于隐藏的语言function的讨论很多: Python的隐藏function 。 哪里提到了一些陷阱(还有一些好的东西呢)。

你也可能想看看Python疣 。

但对我来说,整数除法是一个难题:

 >>> 5/2 2 

你可能想要:

 >>> 5*1.0/2 2.5 

如果你真的想要这样(类似C)的行为,你应该写:

 >>> 5//2 2 

因为这也可以和浮动工作(当你最终转到Python 3时它也会工作):

 >>> 5*1.0//2 2.0 

GvR解释了整数部分如何在Python的历史上发挥作用。

列表切片导致了我很多的悲伤。 我实际上认为以下行为是一个错误。

定义一个列表x

 >>> x = [10, 20, 30, 40, 50] 

访问索引2:

 >>> x[2] 30 

如你所料。

从索引2切片到列表的末尾:

 >>> x[2:] [30, 40, 50] 

如你所料。

访问索引7:

 >>> x[7] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of range 

再次,如你所料。

但是 ,尝试从索引7切片列表直到列表的末尾:

 >>> x[7:] [] 

???

补救措施是在使用列表切片时进行大量testing。 我希望我只是得到一个错误。 更容易debugging。

在你的包中不包括__init__ .py。 那个人有时候还会得到我。

我唯一遇到的问题就是CPython的GIL。 如果因为某种原因,你希望CPython中的python线程能够同时运行…嗯,他们不是这样,Python的人群甚至Guido本人都很好地logging了这些。

对CPython线程和一些事情进行了深入的解释,为什么使用CPython进行真正的并发是不可能的。 http://jessenoller.com/2009/02/01/python-threads-and-the-global-interpreter-lock/

James Dumay雄辩地提醒我另一个Python陷阱:

并不是所有的Python“包含的电池”都很棒

James的具体例子是HTTP库: httpliburlliburllib2urlparsemimetoolsftplib 。 其中一些function是重复的,并且您期望的一些function完全不存在,例如redirect处理。 坦率地说,这太可怕了。

如果我现在不得不通过HTTP获取某些东西,我使用Yum项目中派生的urlgrabber模块。

默认情况下不会以全精度打印repr (不带重新生成):

 x = 1.0 / 3 y = 0.333333333333 print x #: 0.333333333333 print y #: 0.333333333333 print x == y #: False 

repr打印太多数字:

 print repr(x) #: 0.33333333333333331 print repr(y) #: 0.33333333333300003 print x == 0.3333333333333333 #: True 

无意中混合旧式和新式课程可能会导致看似神秘的错误。

假设你有一个由超类A和子类B组成的简单的类层次结构。当B被实例化时,必须先调用A的构造函数。 下面的代码正确的做到这一点:

 class A(object): def __init__(self): self.a = 1 class B(A): def __init__(self): super(B, self).__init__() self.b = 1 b = B() 

但是,如果你忘记把A作为一个新风格的类并且像这样定义它:

 class A: def __init__(self): self.a = 1 

你得到这个回溯:

 Traceback (most recent call last): File "AB.py", line 11, in <module> b = B() File "AB.py", line 7, in __init__ super(B, self).__init__() TypeError: super() argument 1 must be type, not classobj 

另外两个问题是489269和770134

 def f(): x += 1 x = 42 f() 

导致UnboundLocalError ,因为本地名称是静态检测的。 一个不同的例子是

 def f(): print x x = 43 x = 42 f() 

您不能使用locals()['x'] =任何更改本地variables值,如您所期望的。

 This works: >>> x = 1 >>> x 1 >>> locals()['x'] = 2 >>> x 2 BUT: >>> def test(): ... x = 1 ... print x ... locals()['x'] = 2 ... print x # *** prints 1, not 2 *** ... >>> test() 1 1 

这实际上使我在这里回答了这个问题,因为我在一个函数之外testing了它,并得到了我想要的改变。 之后,我发现它提到并与“深入Python”中的globals()的情况进行了对比。 见例8.12。 (虽然它没有注意到,通过本地()的变化将工作在顶层,如上所示。)

用嵌套列表列出重复

这让我今天感到困惑,浪费了一小时的时间来debugging:

 >>> x = [[]]*5 >>> x[0].append(0) # Expect x equals [[0], [], [], [], []] >>> x [[0], [0], [0], [0], [0]] # Oh dear 

说明: Python列表问题

当需要实例variables时使用类variables。 大多数情况下,这不会造成问题,但如果这是一个可变的值,则会导致意外。

 class Foo(object): x = {} 

但:

 >>> f1 = Foo() >>> f2 = Foo() >>> f1.x['a'] = 'b' >>> f2.x {'a': 'b'} 

你几乎总是需要实例variables,这要求你在__init__里面赋值:

 class Foo(object): def __init__(self): self.x = {} 

Python 2与比较有一些令人惊讶的行为:

 >>> print x 0 >>> print y 1 >>> x < y False 

这是怎么回事? repr()来拯救:

 >>> print "x: %r, y: %r" % (x, y) x: '0', y: 1 

x是一个列表时, x += [...]不同于x = x + [...]

 >>> x = y = [1,2,3] >>> x = x + [4] >>> x == y False >>> x = y = [1,2,3] >>> x += [4] >>> x == y True 

一个创build一个新的列表,而另一个修改

如果你赋值给一个函数内的variables,Python假定variables是在该函数内定义的:

 >>> x = 1 >>> def increase_x(): ... x += 1 ... >>> increase_x() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in increase_x UnboundLocalError: local variable 'x' referenced before assignment 

使用global x (或Python 3中的nonlocal x )来声明你想设置一个定义在你的函数之外的variables。

range(end_val)的值不仅严格小于end_val ,而且严格小于int(end_val) 。 对于rangefloat参数,这可能是一个意外的结果:

 from future.builtins import range list(range(2.89)) [0, 1] 

由于“真实”,这是有道理的:

 >>>bool(1) True 

但是你可能不会期望它走向另一个方向:

 >>>float(True) 1.0 

如果您将string转换为数字,并且数据具有True / False值,则这可能是个棘手的问题。