【问题标题】:Common pitfalls in Python [duplicate]Python中的常见陷阱[重复]
【发布时间】:2009-06-18 08:19:11
【问题描述】:

多年后,今天我再次被可变的默认参数所困扰。除非需要,否则我通常不使用可变的默认参数,但我想随着时间的推移我忘记了这一点。今天在应用程序中,我在 PDF 生成函数的参数列表中添加了 tocElements=[],现在“目录”在每次调用“生成 pdf”后变得越来越长。 :)

我还应该在必须避免的事情列表中添加什么?

  • 始终以相同的方式导入模块,例如from y import ximport xtreated as different modules

  • 不要使用 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”,虽然它不是一个错误;看起来我们正在捕获这两种类型的异常,但我们只是将 KeyError 捕获为变量 TypeError, 使用它来代替:

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

【问题讨论】:

  • 我对必须避免的事情更感兴趣,看起来只有一个候选人
  • 我推荐使用 pylint,它可以捕捉到很多这样的陷阱。我使用它与 eclipse(pydev) 集成。
  • stackoverflow.com/questions/530530/… - 这个问题不是原来的问题吗?
  • 请在 Python 2.x 中使用 range() 作为列表。在 Python 3.x 中将 2to3 脚本转换为 list(range()) 是一项工作
  • @Narek:什么是“普通”编程语言?

标签: python


【解决方案1】:

不要使用索引来循环序列

不要:

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 中有新旧类。旧的,嗯,旧的。它们缺乏一些特性,并且在继承时可能会出现尴尬的行为。为了可用,您的任何类都必须是“新样式”。为此,请使其继承自“对象”:

不要:

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()]

【讨论】:

  • 在你的“只是做它并纠正错误”一节中,你有一个 except: 块,这是 evilbadscary。始终捕获最小的错误子集以避免在大型程序中出现奇怪的调试陷阱,因此在您的示例中替换 except: 与 except IOError 作为 E: (总是值得抓住异常实例进行检查;))
  • 很好的总结,你应该添加一个关于避免裸异常的部分。这可能是最危险的事情。
  • list(listing).append("test") - 这没用,新列表无法访问。
  • 你是对的,这只是一个错字。固定的。当然,如果您希望列表在函数之外更新,则它不起作用。但是在 Python 中,你通常不会使用带有副作用的函数。有一些方法可以操作可变类型。
  • 其中大部分是非常普遍的编程陷阱:它们并不是 Python 特有的。
【解决方案2】:

当您需要大量数组时,您可能会想输入如下内容:

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

【讨论】:

  • 我不会在表达式中使用 _。首先,它对交互式解释器具有特殊的意义。其次,您的意思是忽略循环变量并不完全明显。把它改成ignored 或类似的怎么样?
  • 我只会使用 'i' 作为该变量。
  • _对解释器意味着什么?
  • 在交互式解释器中 _ 是你最后一个命令的输出。
  • 定期发布与此陷阱相关的问题。这是一个经常被引用的欺骗目标:stackoverflow.com/q/240178/190597
【解决方案3】:

Python 语言陷阱——以非常晦涩的方式失败的事情

  • 使用可变的默认参数。

  • 前导零表示八进制。 09 是 Python 2.x 中一个非常晦涩的语法错误

  • 超类或子类中的重写方法名称拼写错误。超类拼写错误更严重,因为没有一个子类正确覆盖它。

Python 设计陷阱

  • 花时间进行自省(例如,尝试自动确定类型或超类身份或其他内容)。首先,通过阅读源代码很明显。更重要的是,花在奇怪的 Python 内省上的时间通常表明根本无法掌握多态性。 80% 的关于 SO 的 Python 自省问题都未能得到多态性。

  • 花时间在代码高尔夫上。仅仅因为您的应用程序的心理模型是四个关键字(“do”、“what”、“I”、“mean”),并不意味着您应该构建一个超复杂的内省装饰器驱动的框架来做到这一点。 Python 允许你将 DRY 提升到一个愚蠢的水平。其余关于 SO 的 Python 自省问题试图将复杂问题简化为编写高尔夫练习。

  • 猴子补丁。

  • 未能真正通读标准库,重新发明轮子。

  • 将 Python 中的交互式类型与适当的程序混为一谈。当您以交互方式输入时,您可能会忘记某个变量并不得不使用globals()。此外,当您打字时,几乎所有内容都是全局的。在适当的程序中,您永远不会“忘记”变量,也不会是全局的。

【讨论】:

  • 当第 3 部分库有一些错误时,MonkeyPatching 还有哪些其他选择?
  • 这是 python:你有源代码。如果 3rd 方库有错误,您可以修复它。
  • Python 3.0 修复了前导零八进制错误。数字上的前导零不再使其成为八进制。您仍然可以使用带有 0o(零小哦)前缀的八进制文字。
  • @Chris:“修复”?哇,蟒蛇人做出了多么愚蠢的决定。破坏所有调用 chmod 的程序!
  • @Draemon:仅当您没有遵循文档时。 docs.python.org/library/os.html#os.chmod。表示使用提供的常量而不是神奇的八进制常量来构建模式。
【解决方案4】:

改变默认参数:

def foo(bar=[]):
    bar.append('baz')
    return bar

默认值只计算一次,而不是每次调用函数时。重复调用foo() 将返回['baz']['baz', 'baz']['baz', 'baz', 'baz']、...

如果你想改变 bar 做这样的事情:

def foo(bar=None):
    if bar is None:
        bar = []

    bar.append('baz')
    return bar

或者,如果您希望参数是最终的:

def foo(bar=[]):
    not_bar = bar[:]

    not_bar.append('baz')
    return not_bar

【讨论】:

【解决方案5】:

我不知道这是否是一个常见的错误,但是虽然 Python 没有递增和递减运算符,但允许使用双符号,所以

++i

--i

是语法正确的代码,但没有做任何“有用”或您可能期望的事情。

【讨论】:

  • 应该注意python的递增/递减是用i += 1, i -= 1来完成的。(代替++i, ==i or i++, i--)。
  • 哇,从来没有新的。这怎么可能在语法上是正确的?我很困惑。
  • python语法的相关产生式规则是u_expr ::= power | "-" u_expr | "+" u_expr | "~" u_expr (见docs.python.org/reference/…)
  • 重点是,这里的 '--' 和 '++' 是 not in-/decrement 运算符,而是以下数字的双符号。因此--i 等价于-(-i) == i。因此你也可以写---i = -i,这就是Python没有in-/decrement操作符的原因。 (如果有的话,你会有未定义的行为。)
【解决方案6】:

在查看标准库之前滚动您自己的代码。例如,这样写:

def repeat_list(items):
    while True:
        for item in items:
            yield item

当你可以使用这个时:

from itertools import cycle

经常被忽视的模块示例(itertools 除外)包括:

  • optparse 用于创建命令行解析器
  • ConfigParser 以标准方式读取配置文件
  • tempfile 用于创建和管理临时文件
  • shelve 用于将 Python 对象存储到磁盘,当一个完整的数据库过度使用时很方便

【讨论】:

  • 我会将 shutil 和 glob 添加到该列表中
  • 搁置是一个非常糟糕的例子,它不适用于不同操作系统之间的可移植代码
  • optparse 已弃用。请改用argparse
【解决方案7】:

避免使用关键字作为您自己的标识符。

另外,最好不要使用from somemodule import *

【讨论】:

  • 特别是我会避免在模块中使用from ... import *。在顶级脚本中,我认为这很好。
【解决方案8】:

不使用功能性工具。这不仅仅是从风格的角度来看的错误,从速度的角度来看也是一个错误,因为很多功能工具都是在 C 中优化的。

这是最常见的例子:

temporary = []
for item in itemlist:
    temporary.append(somefunction(item))
itemlist = temporary

正确的做法:

itemlist = map(somefunction, itemlist)

同样正确的做法:

itemlist = [somefunction(x) for x in itemlist]

如果您只需要一次处理一个而不是一次全部可用的处理项,则可以通过使用可迭代等效项来节省内存并提高速度

# itertools-based iterator
itemiter = itertools.imap(somefunction, itemlist)
# generator expression-based iterator
itemiter = (somefunction(x) for x in itemlist)

【讨论】:

  • 在 Python 3.x 中,来自 builtins 的map 不会返回一个列表,而是一个迭代器(在 Python 2.x 中的行为类似于 itertools.imap)。要从map 获取列表,您必须使用list(map(...))
【解决方案9】:

很惊讶没有人这么说:

缩进时混合制表符和空格。

真的,这是一个杀手。相信我。 特别是,如果它运行的话。

【讨论】:

  • @e-satis:如果您按时间顺序阅读答案,则不会。
  • 简单的解决方案:使用 #!/usr/bin/env python -t 启动源代码
【解决方案10】:

如果您来自 C++,请意识到在类定义中声明的变量是静态的。您可以在 init 方法中初始化非静态成员。

例子:

class MyClass:
  static_member = 1

  def __init__(self):
    self.non_static_member = random()

【讨论】:

    【解决方案11】:

    【讨论】:

    • 嗯,更多的是关于做什么,而不是不做什么
    【解决方案12】:

    正常的复制(分配)是通过引用完成的,因此通过调整相同的对象并插入来填充容器,最终得到一个引用了最后添加的对象的容器。

    请改用copy.deepcopy

    【讨论】:

    • 值得注意的是a=[["foo"]]; b=copy.deepcopy(a);id(a[0][0]) == id(b[0][0]))字符串foo是同一个字符串对象。你不能复制一个字符串。它是被实习的并且是不可变的,所以无论如何都没有什么可以复制是有道理的。
    【解决方案13】:

    导入re 并使用完整的正则表达式方法进行字符串匹配/转换,而string methods 对于每个常见操作(例如大写字母、简单匹配/搜索)都存在完美。

    【讨论】:

    • 考虑到 unicode 中有多少不同的字母和符号,unicode 方法可以更好地处理大小写、isalpha 等等等。
    【解决方案14】:

    在错误消息中使用%s 格式化程序。几乎在所有情况下,都应该使用%r

    例如,想象这样的代码:

    try:
        get_person(person)
    except NoSuchPerson:
        logger.error("Person %s not found." %(person))
    

    打印出这个错误:

    错误:找不到人wolever。

    无法判断person 变量是字符串"wolever"、unicode 字符串u"wolever" 还是Person 类的实例(__str__ 定义为def __str__(self): return self.name)。而如果使用%r,则会出现三种不同的错误消息:

    ...
    logger.error("Person %r not found." %(person))
    

    会产生更多有用的错误:

    错误:找不到人“wolever”。
    错误:找不到人 u'wolever'。
    错误:找不到人。

    另一个很好的原因是路径更容易复制/粘贴。想象一下:

    try:
        stuff = open(path).read()
    except IOError:
        logger.error("Could not open %s" %(path))
    

    如果pathsome path/with 'strange' "characters",则错误消息为:

    错误:无法打开某些路径/带有“奇怪”“字符”

    这很难在视觉上解析并且很难复制/粘贴到外壳中。

    而如果使用%r,则会出现以下错误:

    错误:无法打开 'some path/with \'strange\' "characters"'

    易于视觉解析,易于复制粘贴,更好。

    【讨论】:

    • 个人而言,我更喜欢使用 "{0}".format(person) 而不是 "%r" % person。我认为它更有能力。更多的 Python 文档说:“这种字符串格式化方法是 Python 3.0 中的新标准,应该优先于新代码中字符串格式化操作中描述的 % 格式化。”
    • 确实,新的.format 更好,但我认为您已经错过了我所说的重点:我是说,当您打印诊断信息时,@987654337 @'d 消息比直接 str'd 消息更有帮助(例如,使用 {0!r} 而不是 {0}
    【解决方案15】:

    最后一个链接是原始链接,这个 SO 问题是重复的。

    【讨论】:

    • str.join 的另一种替代方法是 str.format,当您需要本地化字符串时,它会非常有用。
    【解决方案16】:

    我必须训练自己改掉的一个坏习惯是使用 X and Y or Z 进行内联逻辑。

    除非您可以 100% 始终保证 Y 将是一个真实值,否则即使您的代码在 18 个月内发生更改,您也会为某些意外行为做好准备。

    谢天谢地,在以后的版本中你可以使用Y if X else Z

    【讨论】:

    • 没有条件运算符的旧版 python 的解决方法是 (X and [y] or [z])[0]
    • 使用not not 前缀是确保它是布尔值的丑陋技巧。 ;)
    【解决方案17】:

    我将在 2.6 中停止使用已弃用的方法,以便您的应用或脚本准备就绪并且更容易转换为 Python 3。

    【讨论】:

      【解决方案18】:

      我也开始学习 Python,我犯的最大错误之一就是经常使用 C++/C# 索引的“for”循环。 Python 有 for(i ; i

      示例: 我有一个迭代列表并返回所选项目索引的方法:

      for i in range(len(myList)):
          if myList[i].selected:
              retVal.append(i)
      

      相反,Python 具有列表推导式,它以更优雅、更易于阅读的方式解决了相同的问题:

      retVal = [index for index, item in enumerate(myList) if item.selected]
      

      【讨论】:

        【解决方案19】:

        ++n--n 可能无法像来自 C 或 Java 背景的人预期的那样工作。

        ++n 是一个正数的正数,即n

        --n 是一个负数的负数,就是n

        【讨论】:

          【解决方案20】:

          一些个人意见,但我认为最好

          • 使用已弃用的模块(对它们使用警告)

          • 过度使用类和继承(可能是静态语言遗留的典型特征)

          • 显式使用声明性算法(如使用 for 的迭代与使用 itertools)

          • 从标准库重新实现函数,“因为我不需要所有这些功能”

          • 为了它而使用功能(降低与旧 Python 版本的兼容性)

          • 在你真的不需要的时候使用元类,更普遍的是让事情变得太“神奇”

          • 避免使用生成器

          • (更个人化)尝试在低级基础上对 CPython 代码进行微优化。更好地花时间在算法上,然后通过创建一个由ctypes 调用的小型 C 共享库进行优化(在内部循环上获得 5 倍性能提升非常容易)

          • 在迭代器足够的情况下使用不必要的列表

          • 在你需要的库全部可用之前直接为 3.x 编写一个项目(这一点现在可能有点争议!)

          【讨论】:

            【解决方案21】:
            import this    
            

            美丽胜于丑陋。
            显式优于隐式。
            简单胜于复杂。
            复杂胜于复杂。
            平面比嵌套好。
            稀疏优于密集。
            可读性很重要。
            特殊情况不足以打破规则。
            虽然实用胜过纯粹。
            错误绝不应无声无息地过去。
            除非明确静音。
            面对模棱两可,拒绝猜测的诱惑。
            应该有一种——最好只有一种——明显的方法。
            虽然这种方式一开始可能并不明显,除非你是荷兰人。
            现在总比没有好。
            虽然现在从来没有比现在更好正确
            如果实现难以解释,那就是个坏主意。
            如果实现很容易解释,那可能是个好主意。
            命名空间是一个很棒的想法——让我们做更多的事情!

            import not_this
            

            编写丑陋的代码。
            编写隐式代码。
            编写复杂的代码。
            编写嵌套代码。
            编写密集代码。
            编写不可读的代码。
            写特殊情况。
            追求纯洁。
            忽略错误和异常。
            在发布之前编写最佳代码。
            每个实施都需要流程图。
            不要使用命名空间。

            【讨论】:

            • 有一瞬间我以为stdlib中还有一个我不知道的复活节彩蛋;但后来我尝试了 import not_this 并得到了 ImportError
            【解决方案22】:

            永远不要认为拥有多线程 Python 应用程序和支持 SMP 的机器(例如配备多核 CPU 的机器)会给您带来将真正的并行性引入应用程序的好处。很可能不会因为 GIL(全局解释器锁)在字节码解释器级别同步您的应用程序。

            有一些变通方法,例如通过将并发代码放在 C API 调用中或通过包装器使用多个进程(而不是线程)来利用 SMP(例如 http://www.parallelpython.org 提供的那个),但如果需要真正的多- Python 中的线程应该查看 Jython、IronPython 等(GIL 是 CPython 解释器的一项功能,因此其他实现不受影响)。

            根据 Python 3000 FAQ(Artima 提供),即使是最新的 Python 版本,上述内容仍然适用。

            【讨论】:

            • CPython 具有“真正的多线程”。多亏了 GIL,它所没有的是可用于负载平衡的威胁。但话又说回来,线程从来没有打算这样做。线程的设计使您可以在不创建单独进程的情况下进行锁定调用。然而,随着 Unix 和 Java 的重线程,线程负载平衡成为可能,许多人似乎认为这是你“应该”这样做的方式。
            • 好的,让我将“真正的多线程”改写为“支持 SMP 的多线程”。
            【解决方案23】:

            与默认的可变参数有点相关,当传递一个空列表时,如何检查“缺失”情况会导致差异:

            def func1(toc=None):
                if not toc:
                    toc = []
                toc.append('bar')
            
            def func2(toc=None):
                if toc is None:
                    toc = []
                toc.append('bar')
            
            def demo(toc, func):
                print func.__name__
                print '  before:', toc
                func(toc)
                print '  after:', toc
            
            demo([], func1)
            demo([], func2)
            

            这是输出:

            func1
              before: []
              after: []
            func2
              before: []
              after: ['bar']
            

            【讨论】:

            • 一个更清晰的表达方式是说“当检查无时,使用is None 而不是not”,因为它不仅适用于缺失的案例,还适用于任何无测试。
            【解决方案24】:

            您提到了默认参数...几乎与可变默认参数一样糟糕:默认值不是None

            考虑一个可以烹饪食物的函数:

            def cook(breakfast="spam"):
                arrange_ingredients_for(breakfast)
                heat_ingredients_for(breakfast)
                serve(breakfast)
            

            因为它为breakfast 指定了一个默认值,所以如果没有特殊情况,其他一些函数就不可能说“煮你的默认早餐”:

            def order(breakfast=None):
                if breakfast is None:
                    cook()
                else:
                    cook(breakfast)
            

            但是,如果 cook 使用 None 作为默认值,则可以避免这种情况:

            def cook(breakfast=None):
                if breakfast is None:
                    breakfast = "spam"
            
            def order(breakfast=None):
                cook(breakfast)
            

            Django 错误#6988 就是一个很好的例子。 Django 的缓存模块有一个“保存到缓存”功能,如下所示:

            def set(key, value, timeout=0):
                if timeout == 0:
                    timeout = settings.DEFAULT_TIMEOUT
                _caching_backend.set(key, value, timeout)
            

            但是,对于 memcached 后端,0 的超时意味着“永不超时”……正如您所见,无法指定。

            【讨论】:

            • +1 代表无所事事(无):00
            【解决方案25】:

            在迭代列表时不要修改它。

            odd = lambda x : bool(x % 2)
            numbers = range(10)
            for i in range(len(numbers)):
                if odd(numbers[i]):
                    del numbers[i]
            

            解决此问题的一个常见建议是反向迭代列表:

            for i in range(len(numbers)-1,0,-1):
                if odd(numbers[i]):
                    del numbers[i]
            

            但更好的是使用列表推导构建一个新列表来替换旧列表:

            numbers[:] = [n for n in numbers if not odd(n)]
            

            【讨论】:

              【解决方案26】:

              常见陷阱:默认参数只计算一次

              def x(a, l=[]):
                  l.append(a)
                  return l
              
              print x(1)
              print x(2)
              

              打印:

              [1]
              [1, 2]
              

              即你总是得到相同的列表。

              【讨论】:

                【解决方案27】:

                开始之前的第一个错误:不要害怕空格

                当您向某人展示一段 Python 代码时,他们会留下深刻的印象,直到您告诉他们必须正确缩进。出于某种原因,大多数人认为一种语言不应该强加给他们某种风格,但所有人都会缩进代码。

                【讨论】:

                • 有趣的是,我永远不会用那些到处都是 shi* 和不正确的缩进来触摸某人的讨厌的 Java 代码。读起来太可怕了。我的缩进总是正确的,所以当我拿起 Python 时,它就像是天赐良机。
                • 绝对是,严格缩进的副产品之一是每个人的代码看起来都一样。
                【解决方案28】:
                my_variable = <something>
                ...
                my_varaible = f(my_variable)
                ...
                use my_variable and thinking it contains the result from f, and not the initial value
                

                Python 不会以任何方式警告您在第二次赋值时您拼错了变量名并创建了一个新变量。

                【讨论】:

                • 避免这种情况的简单规则:“具有特定名称的变量必须始终包含可比较的值”。例如,breakfast 可以包含spam()eggs(),但不能包含[spam(), eggs()]"spam"
                • 我发现,在处理字符串时,这一点尤为重要。例如,person = "joe"; person = get_person_by_name(person) 不好,但可以通过简单地使用来避免:person_name = "joe"; person = get_person_by_name(person_name)
                【解决方案29】:

                从 stdlib 创建一个同名的本地模块。这几乎总是意外完成(如this question 中所报告的),但通常会导致神秘的错误消息。

                【讨论】:

                  【解决方案30】:

                  混杂异常处理

                  这是我在生产代码中看到的数量惊人的东西,它让我感到畏缩。

                  try:
                      do_something() # do_something can raise a lot errors e.g. files, sockets
                  except:
                      pass # who cares we'll just ignore it
                  

                  是您想要抑制的异常,还是更严重的异常?但还有更微妙的情况。这会让你费尽心思去想办法。

                  try: 
                      foo().bar().baz()
                  except AttributeError: # baz() may return None or an incompatible *duck type*
                      handle_no_baz() 
                  

                  问题是 foo 或 baz 也可能是罪魁祸首。我认为这可能更阴险,因为这是惯用的python,您可以在其中检查您的类型是否有正确的方法。但是每个方法调用都有机会返回一些意想不到的东西并抑制应该引发异常的错误。

                  知道一个方法可以抛出什么异常并不总是显而易见的。例如,urllib 和 urllib2 使用的 socket 有它自己的异常,当你最意想不到的时候,它们会向上渗透并抬起它们丑陋的脑袋。

                  异常处理在处理 C 等系统级语言的错误方面是一种生产力提升。但我发现不正确地抑制异常会创建真正神秘的调试会话并带走解释语言提供的主要优势。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-10-15
                    • 2020-04-04
                    • 1970-01-01
                    • 2017-03-27
                    • 2011-11-27
                    相关资源
                    最近更新 更多