Python 2.x陷阱和地雷

时间:2009-02-09 23:25:00

标签: python

我的问题的目的是通过Python加强我的知识基础并更好地了解它,包括了解它的错误和惊喜。为了保持特定的内容,我只对CPython解释器感兴趣。

我正在寻找类似于从PHP landmines学到的东西 问我哪些答案对我来说是众所周知的,但是一对夫妇的边界恐怖。

更新:    显然有一两个人感到不安,我问了一个已经在Stack Overflow之外部分回答的问题。这里有一些妥协,就是URL http://www.ferg.org/projects/python_gotchas.html

请注意,此处的一个或两个答案已经是上面引用的网站上的原始答案。

23 个答案:

答案 0 :(得分:83)

默认参数中的表达式是在定义函数时计算的,不是时调用它。

示例:考虑将参数默认为当前时间:

>>>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']

答案 1 :(得分:60)

你应该知道如何在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

答案 2 :(得分:38)

循环和lambdas(或任何闭包,真的):变量由 name

绑定
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

答案 3 :(得分:20)

动态绑定使变量名称中的拼写错误很难找到。修理一个微不足道的bug很容易花半个小时。

编辑:一个例子......

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

答案 4 :(得分:18)

我曾经遇到的最大惊喜之一就是这个:

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

除了在更新元组的第一个条目后引发TypeError之外,这可以正常工作!因此a在执行([42, 43, 44],)语句后将为+=,但无论如何都会有例外。如果你另一方面尝试这个

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

你不会收到错误。

答案 5 :(得分:16)

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

这不起作用的原因是因为IndexError是您正在捕获的异常类型,而ValueError是您要为其分配异常的变量的名称。

纠正代码以捕获多个异常是:

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

答案 6 :(得分:12)

前面有很多关于隐藏语言功能的讨论:hidden-features-of-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解释了整数除法如何在the history of Python上起作用。

答案 7 :(得分:11)

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

定义列表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:]
[]

???

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

答案 8 :(得分:10)

包中不包含__init__。py。那个人有时候还会得到我。

答案 9 :(得分:9)

James Dumay eloquently reminded me另一个Python问题:

并非所有Python的“包含电池”都很精彩

James的具体示例是HTTP库:httpliburlliburllib2urlparsemimetoolsftplib。某些功能是重复的,您期望的某些功能完全不存在,例如重定向处理。坦率地说,这太可怕了。

如果我现在必须通过HTTP抓取一些东西,我使用从Yum项目分叉的urlgrabber模块。

答案 10 :(得分:9)

我所处理的唯一问题是CPython的GIL。如果出于某种原因你期望CPython中的python线程同时运行......那么它们就不是了,而且Python人群甚至Guido本人都记录了这一点。

对CPython线程的一个长期但彻底的解释以及一些在幕后发生的事情以及为什么CPython的真正并发性是不可能的。 http://jessenoller.com/2009/02/01/python-threads-and-the-global-interpreter-lock/

答案 11 :(得分:7)

默认情况下,浮点数不会以完全精度打印(没有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

答案 12 :(得分:7)

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

假设您有一个由超类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

与此问题相关的其他两个问题是489269770134

答案 13 :(得分:6)

def f():
    x += 1

x = 42
f()

会产生UnboundLocalError,因为静态检测到本地名称。

是一个不同的例子
def f():
    print x
    x = 43

x = 42
f()

答案 14 :(得分:5)

您不能使用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()的更改将在顶层工作。)

答案 15 :(得分:4)

使用嵌套列表列出重复

今天我抓住了我,浪费了一小时的时间调试:

>>> x = [[]]*5
>>> x[0].append(0)

# Expect x equals [[0], [], [], [], []]
>>> x
[[0], [0], [0], [0], [0]]   # Oh dear

说明:Python list problem

答案 16 :(得分:4)

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

一个创建一个新列表,而另一个修改就位

答案 17 :(得分:2)

在需要实例变量时使用类变量。大多数情况下,这不会导致问题,但如果它是一个可变值,则会引起意外。

class Foo(object):
    x = {}

可是:

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

您几乎总是想要实例变量,这需要您在__init__内分配:

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

答案 18 :(得分:2)

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

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

发生了什么? repr()救援:

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

答案 19 :(得分:0)

如果为函数内部的变量赋值,Python假定变量是在该函数内定义的:

>>> 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)声明您要设置在函数外定义的变量。

答案 20 :(得分:0)

range(end_val)的值不仅严格小于end_val,还严格小于int(end_val)。对于float的{​​{1}}参数,这可能是意外结果:

range

答案 21 :(得分:0)

由于&#39;真实性&#39;这是有道理的:

>>>bool(1)
True

但你可能不会指望它走另一条道路:

>>>float(True)
1.0

如果您将字符串转换为数字并且数据具有True / False值,则可能会出现问题。

答案 22 :(得分:-1)

如果您以这种方式创建列表列表:

arr = [[2]] * 5 
print arr 
[[2], [2], [2], [2], [2]]

然后创建一个数组,其中所有元素都指向同一对象!这可能会造成真正的混乱。考虑一下:

arr[0][0] = 5

然后如果您打印arr

print arr
[[5], [5], [5], [5], [5]]

初始化数组的正确方法例如是使用列表理解:

arr = [[2] for _ in range(5)]

arr[0][0] = 5

print arr

[[5], [2], [2], [2], [2]]