Python中的常见陷阱

80 投票
34 回答
22306 浏览
提问于 2025-04-15 12:19

今天我又被可变默认参数坑了一把,虽然已经很多年没遇到过这种情况了。我通常不会使用可变的默认参数,除非真的需要,但随着时间的推移,我好像忘记了这一点。今天在一个生成PDF的函数里,我在参数列表中加了tocElements=[],结果每次调用“生成PDF”时,“目录”就越来越长了。:)

还有什么我应该加入到必须避免的事情清单里呢?

  • 始终以相同的方式导入模块,比如 from y import ximport x 会被 视为不同的模块

  • 不要用 range 来代替列表,因为 range() 最终会变成一个迭代器,下面的代码会失败:

      myIndexList = [0, 1, 3]
      isListSorted = myIndexList == range(3)  # will fail in 3.0
      isListSorted = myIndexList == list(range(3))  # will not
    

    同样的错误也可能出现在 xrange:

      myIndexList == xrange(3)
    
  • 处理多个异常类型时要小心:

      try:
          raise KeyError("hmm bug")
      except KeyError, TypeError:
          print TypeError
    

    这段代码会打印“hmm bug”,但其实并不是bug;看起来我们好像同时捕获了两种类型的异常,但实际上我们只捕获了 KeyError,作为变量 TypeError,应该这样做:

      try:
          raise KeyError("hmm bug")
      except (KeyError, TypeError):
          print TypeError
    

34 个回答

28

Python语言中的一些小陷阱——那些以非常隐蔽的方式出错的事情

  • 使用可变的默认参数。

  • 前导零表示八进制。比如 09 在Python 2.x中会引发一个非常隐蔽的语法错误。

  • 在父类或子类中拼错重写的方法名。拼错父类的方法名会更糟糕,因为所有子类都无法正确重写它。

Python设计中的一些小陷阱

  • 花时间在自省上(例如,试图自动确定类型、父类身份或其他东西)。首先,从源代码中阅读是显而易见的。更重要的是,花时间在奇怪的Python自省上通常表明对多态性理解的根本失败。在StackOverflow上,80%的Python自省问题都是因为没搞懂多态性。

  • 花时间在代码高尔夫上。仅仅因为你对应用程序的理解是四个关键词(“做”,“什么”,“我”,“意思”),并不意味着你应该构建一个超复杂的自省装饰器驱动框架来实现它。Python允许你把“不要重复自己”(DRY)做到荒谬的程度。StackOverflow上其余的Python自省问题尝试把复杂问题简化成代码高尔夫练习。

  • 猴子补丁(Monkeypatching)。

  • 没有认真阅读标准库,结果重新发明了轮子。

  • 把交互式的逐步输入Python和一个完整的程序混为一谈。当你在交互式环境中输入时,可能会失去对某个变量的追踪,需要使用 globals()。而且,在你输入时,几乎所有东西都是全局的。在完整的程序中,你永远不会“失去追踪”一个变量,也不会有全局变量。

39

当你需要一组数组的时候,你可能会想要这样写:

>>> a=[[1,2,3,4,5]]*4

没错,这样写出来的结果会和你预期的一样,看起来也没问题。

>>> from pprint import pprint
>>> pprint(a)

[[1, 2, 3, 4, 5],
 [1, 2, 3, 4, 5],
 [1, 2, 3, 4, 5],
 [1, 2, 3, 4, 5]]

但是,不要指望这些数组里的元素是独立的对象:

>>> a[0][0] = 2
>>> pprint(a)

[[2, 2, 3, 4, 5],
 [2, 2, 3, 4, 5],
 [2, 2, 3, 4, 5],
 [2, 2, 3, 4, 5]]

除非你确实需要这样...

这里有一个值得一提的解决办法:

a = [[1,2,3,4,5] for _ in range(4)]
73

不要用索引来遍历序列

不要这样:

for i in range(len(tab)) :
    print tab[i]

这样做:

for elem in tab :
    print elem

for 循环会自动帮你处理大部分的遍历操作。

如果你真的需要同时获取索引和元素,可以使用 enumerate

for i, elem in enumerate(tab):
     print i, elem

小心使用 "==" 来检查 TrueFalse

if (var == True) :
    # this will execute if var is True or 1, 1.0, 1L

if (var != True) :
    # this will execute if var is neither True nor 1

if (var == False) :
    # this will execute if var is False or 0 (or 0.0, 0L, 0j)

if (var == None) :
    # only execute if var is None

if var :
    # execute if var is a non-empty string/list/dictionary/tuple, non-0, etc

if not var :
    # execute if var is "", {}, [], (), 0, None, etc.

if var is True :
    # only execute if var is boolean True, not 1

if var is False :
    # only execute if var is boolean False, not 0

if var is None :
    # same as var == None

不要先检查能否执行,直接去做并处理错误

Python程序员通常会说:“请求原谅比请求许可更简单。”

不要这样:

if os.path.isfile(file_path) :
    file = open(file_path)
else :
    # do something

这样做:

try :
    file =  open(file_path)
except OSError as e:
    # do something

或者更好的是,使用 Python 2.6+ / 3:

with open(file_path) as file :

这样做更好,因为它更通用。你几乎可以把 "try / except" 应用到任何地方。你不需要担心如何预防错误,只需关注你可能遇到的错误。

不要检查类型

Python 是动态类型的,因此检查类型会让你失去灵活性。相反,使用鸭子类型来检查行为。例如,如果你期望一个字符串作为函数的输入,可以用 str() 将任何对象转换为字符串。如果你期望一个列表,可以用 list() 将任何可迭代对象转换为列表。

不要这样:

def foo(name) :
    if isinstance(name, str) :
        print name.lower()

def bar(listing) :
    if isinstance(listing, list) :
        listing.extend((1, 2, 3))
        return ", ".join(listing)

这样做:

def foo(name) :
    print str(name).lower()

def bar(listing) :
    l = list(listing)
    l.extend((1, 2, 3))
    return ", ".join(l)

使用这种方式,foo 可以接受任何对象。Bar 可以接受字符串、元组、集合、列表等等。这样做很简单,避免重复代码 :-)

不要混用空格和制表符

就是不要这样。这样做会让你很痛苦。

object 作为第一个父类

这有点复杂,但随着程序的增长,你会发现这样做会给你带来麻烦。Python 2.x 有旧类和新类。旧类比较老,缺少一些功能,并且在继承时可能会有奇怪的行为。为了让你的类可用,必须使用“新风格”。为此,你需要让它继承自 "object":

不要这样:

class Father :
    pass

class Child(Father) :
    pass

这样做:

class Father(object) :
    pass


class Child(Father) :
    pass

在 Python 3.x 中,所有类都是新风格的,所以你可以直接声明 class Father:

不要在 __init__ 方法外初始化类属性

来自其他语言的人可能会觉得这样做很诱人,因为在 Java 或 PHP 中是这样做的。你写类名,然后列出属性并给它们一个默认值。在 Python 中,这样做似乎也能工作,但实际上并不是你想的那样。

这样做会设置类属性(静态属性),当你尝试获取对象属性时,它会返回属性的值,除非它是空的。在这种情况下,它会返回类属性。

这会带来两个大问题:

  • 如果类属性被改变,初始值也会被改变。
  • 如果你将一个可变对象作为默认值,那么你会在多个实例之间共享同一个对象。

不要这样(除非你想要静态属性):

class Car(object):
    color = "red"
    wheels = [wheel(), Wheel(), Wheel(), Wheel()]

这样做:

class Car(object):
    def __init__(self):
        self.color = "red"
        self.wheels = [wheel(), Wheel(), Wheel(), Wheel()]

撰写回答