我的问题的目的是通过Python加强我的知识库并更好地了解它,包括了解它的错误和意外.为了保持特定的内容,我只对CPython解释器感兴趣.
我正在寻找类似于从我的PHP地雷 问题中学到的东西,其中一些答案对我来说是众所周知的但是一对夫妇的边界恐怖.
更新:显然有一两个人感到不安,我问了一个已经在Stack Overflow之外部分回答的问题.这里的某些妥协是URL http://www.ferg.org/projects/python_gotchas.html
请注意,此处的一个或两个答案已经是上面引用的网站上的原始答案.
默认参数中的表达式是在定义函数时计算的,而不是在调用函数时计算的.
示例:考虑将参数默认为当前时间:
>>>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中处理类变量.考虑以下类层次结构:
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
惊讶吗?如果你记得类变量在内部被处理为类对象的字典,你将不会成为.对于读取操作,如果在当前类的字典中找不到变量名,则会搜索父类.所以,再次提供以下代码,但有解释:
# 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
在类实例中处理类变量也是如此(将此示例视为上述示例的延续):
>>> a = AAA() # a: {}, AAA: {'x': 3} >>> print a.x, AAA.x 3 3 >>> a.x = 4 # a: {'x': 4}, AAA: {'x': 3} >>> print a.x, AAA.x 4 3
循环和lambdas(或任何闭包,真的):变量由名称绑定
funcs = [] for x in range(5): funcs.append(lambda: x) [f() for f in funcs] # output: # 4 4 4 4 4
解决方法是创建单独的函数或按名称传递args:
funcs = [] for x in range(5): funcs.append(lambda x=x: x) [f() for f in funcs] # output: # 0 1 2 3 4
动态绑定使变量名称中的拼写错误很难找到.修理一个微不足道的bug很容易花半个小时.
编辑:一个例子......
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之外,这可以正常工作!因此,a
将([42, 43, 44],)
在执行后+=
声明,但会有一个例外呢.如果你试试另一方面
a = ([42],) b = a[0] b += [43, 44]
你不会得到错误.
try: int("z") except IndexError, ValueError: pass
这不起作用的原因是因为IndexError是您正在捕获的异常类型,而ValueError是您要为其分配异常的变量的名称.
用于捕获多个异常的正确代码是:
try: int("z") except (IndexError, ValueError): pass
前面有很多关于隐藏语言功能的讨论:隐藏的python功能.提到了一些陷阱(以及一些好东西).
您也可以查看Python Warts.
但对我来说,整数除法是一个问题:
>>> 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 "", line 1, in IndexError: list index out of range
再次,如你所料.
但是,尝试将列表从索引7切片到列表末尾:
>>> x[7:] []
???
解决方法是在使用列表切片时进行大量测试.我希望我只是得到一个错误.更容易调试.
包中不包含__init__
.py.那个人有时还会得到我.
我所处理的唯一问题是CPython的GIL.如果出于某种原因你期望CPython中的python线程同时运行......那么它们就不是了,而且这些已经被Python人群甚至Guido自己记录得很好.
对CPython线程的一个长期但彻底的解释以及一些在幕后发生的事情以及为什么CPython的真正并发性是不可能的. http://jessenoller.com/2009/02/01/python-threads-and-the-global-interpreter-lock/
James Dumay雄辩地提醒我另一个Python问题:
并非所有Python的"包含电池"都很精彩.
詹姆斯的具体例子是HTTP库:httplib
,urllib
,urllib2
,urlparse
,mimetools
,和ftplib
.某些功能是重复的,您期望的某些功能完全不存在,例如重定向处理.坦率地说,这太可怕了.
如果我现在必须通过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
无意中混合oldstyle和newstyle类可能会导致看似神秘的错误.
假设您有一个由超类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设为newstyle类并将其定义为:
class A: def __init__(self): self.a = 1
你得到这个追溯:
Traceback (most recent call last): File "AB.py", line 11, inb = 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'] =无论如何更改本地变量值.
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
这实际上让我在SO的答案上烧了,因为我在一个函数之外测试了它,并得到了我想要的改变.之后,我发现它提到并与"Dive Into Python"中的globals()的情况形成对比.见例8.12.(虽然它没有注意到,如上所示,通过locals()的更改将在顶层工作.)