【问题标题】:Python 2.x gotchas and landmines [closed]Python 2.x 的陷阱和地雷 [关闭]
【发布时间】:2009-02-09 23:25:00
【问题描述】:

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

我正在寻找类似于从我的PHP landmines 学到的东西 问题中的一些答案对我来说是众所周知的,但有几个是可怕的。

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

请注意,这里的一两个答案已经是上述网站上所写内容的原创。

【问题讨论】:

  • 不确定从 2.5 到 2.6 是否有很多“陷阱”,如果您的意图是一般的 python 2.X 系列,最好将标题更改为 2.X。
  • ferg.org/projects/python_gotchas.html 中的列表有什么问题?
  • @S. Lott - 没有错,只是我不知道,也没有人在 SO 中问过这个问题。
  • @hop ferg.org 页面中没有提到这个问题目前评分最高的答案。也许如果 Guido 写了 ferg.org 页面并且我知道它,那么我就不会打扰了,但没有一个人知道一切。
  • @S.Lott - 怎么了? ferg.org 链接已损坏

标签: python


【解决方案1】:

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

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

>>>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 好点!实际上,我在类似的情况下也依赖过这一点,但我很容易看到这让粗心大意的人措手不及!
  • 这是最广为人知的陷阱,但我在知道它之前从未被它咬过。
  • 类级变量也是如此(第一次学习python时容易犯的错误)
  • Python 设计师做出了很多好的设计决策,但其中没有一个。 +1
  • 您可以使用元组而不是列表作为默认参数。一般来说 - 默认值应该是不可更改的类型(NoneType、int、tuple 等)
【解决方案2】:

您应该了解 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

【讨论】:

    【解决方案3】:

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

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

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

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

    【讨论】:

      【解决方案4】:

      动态绑定使变量名中的拼写错误非常难以找到。花半个小时修复一个小错误很容易。

      编辑:一个例子...

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

      【讨论】:

      • +1 是的,这把我搞砸了一两次,你有没有机会在你的答案中提供一个例子?
      • 我想是这样,但这只是为了说明。此类错误的实际发生往往涉及更多。
      • 您可以使用像 PyLint 这样的静态检查器来发现这些错误——tiem 将被标记为未使用的变量。
      【解决方案5】:

      我对 Python 最大的惊喜之一就是:

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

      这可以正常工作,除了在更新元组的第一个条目后引发 TypeError !所以a在执行+=语句后会是([42, 43, 44],),但无论如何都会有异常。另一方面,如果您尝试这样做

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

      您不会收到错误消息。

      【讨论】:

      • 或者你可以直接写:a[0].extend([43, 44]).
      • 哇。我考虑在 Python 中的一个错误之后更改并引发异常。为什么这可能只是一个疣?
      • 哇。我预料到了这个错误,但我没想到它也会真正修改列表。那是丑陋的。但是,我不相信我曾经遇到过这种情况,因为我养成了不将元组与我想要更改的数据一起使用的习惯。即使它指向同一个列表,我也希望这些值保持不变。如果我想拥有可以改变的位置元素,我会使用列表、字典或类。
      【解决方案6】:
      try:
          int("z")
      except IndexError, ValueError:
          pass
      

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

      捕获多个异常的正确代码是:

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

      【讨论】:

        【解决方案7】:

        不久前有很多关于隐藏语言功能的讨论: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 上的工作原理。

        【讨论】:

        • 绝对是一个陷阱。它比在我创建的每个新 .py 文件中添加“从 future 导入部门”实际上是一种反射。
        • 假设 5 和 2 实际上是变量是有道理的。否则你可以写 5./2
        • 为什么要乘以 1.0?如果 5 隐藏在变量中,那么将 5 设为 5.0 或 float(5) 岂不是同样容易吗?
        • "正确的解决方法是微妙的:如果参数可能是复数,则将参数强制转换为 float() 是错误的;如果参数添加 0.0 并不会保留参数的符号是负零。没有任何缺点的唯一解决方案是将参数(通常是第一个)乘以 1.0。这会使浮点和复数的值和符号保持不变,并将 int 和 long 转换为具有相应值的浮点数。 (PEP 238 - python.org/dev/peps/pep-0238)
        • 使用 float() 而不是 *1.0 在绝大多数情况下不能涉及复数是更好的风格恕我直言,因为它说明了你的真正意图。乘以 1.0 来实现这一点有点混淆了你想要的东西,任何不了解代码的读者可能会认为 1.0 可能只是一个错字(也许应该改为 10.0?)。
        【解决方案8】:

        在您的包中不包括__init__.py。那个有时还是会吸引我。

        【讨论】:

          【解决方案9】:

          列表切片让我很伤心。我实际上认为以下行为是一个错误。

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

          ???

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

          【讨论】:

          • 我同意。它确实隐藏了那些一次性的错误。
          • 这个对于 Python 来说实际上是非常可预测的,并且在迭代空切片时很有用。
          • 我认为这不是不良行为。如果您考虑切片背后的逻辑,这是可预测和可取的......这有点像在没有列表索引满足您放在列表中定义它的约束的情况下进行列表理解。这很有用!
          【解决方案10】:

          我遇到的唯一问题/惊喜是 CPython 的 GIL。如果出于某种原因,您希望 CPython 中的 python 线程同时运行……那么它们不是,Python 人群甚至 Guido 本人都很好地记录了这一点。

          对 CPython 线程和幕后发生的一些事情以及为什么无法使用 CPython 实现真正的并发性进行了长而透彻的解释。 http://jessenoller.com/2009/02/01/python-threads-and-the-global-interpreter-lock/

          【讨论】:

          • 如果 GIL 困扰您,请查看 2.6 中提供的新多处理模块,以便使用单独的进程进行类似线程的处理。 docs.python.org/library/multiprocessing.html
          • @David - 一定是 pyprocessing,它以多处理为幌子成为标准库的一部分
          【解决方案11】:

          James Dumay eloquently reminded me 的另一个 Python 陷阱:

          并非所有 Python 的“随附电池”都很棒

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

          如果这些天我必须通过 HTTP 获取某些东西,我会使用从 Yum 项目派生的 urlgrabber 模块。

          【讨论】:

          • 我记得几年前放弃尝试使用上面的工具套件完成我想要的事情,最终使用了 pyCurl。
          • 有一个名为 urllib 的模块和一个名为 urllib2 的模块的事实仍然让我难以理解。
          • 这可能是使用 Python 3 的真正原因 :) 他们直截了当地说,“等等,这在哪里……让我们重新开始吧”。
          • Python 3 在这些库方面组织得更好。
          【解决方案12】:

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

          【讨论】:

          • 这是一个折衷方案,以便浮点字符串可以合理地跨 python 平台移植,因为 python 使用硬件浮点数。
          【解决方案13】:

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

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

          【讨论】:

            【解决方案14】:
            def f():
                x += 1
            
            x = 42
            f()
            

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

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

            【讨论】:

            • 如果你在 f() 中使用全局 x,这将允许你引用 f 范围之外的变量。
            • @David:我知道你为什么会出错。这篇文章的重点是大多数人不期望在这里得到一个错误,所以这是一个陷阱。
            • 我为他人着想发表了更多评论,并非所有问题,但这里的大多数问题都有解决方案。就我自己而言,直到我开始使用 Python 两年后,我才知道全局符号/运算符。
            • 无论如何,使用诸如这样的全局变量是一种不好的代码气味。我很高兴您在 2 年后才遇到它。这意味着您没有让函数在全局范围变量上执行。
            • @ZoranPavlovic:使用闭包而不是全局变量时会遇到同样的问题。将计数器实现为闭包是完全合理的,但您必须小心。
            【解决方案15】:

            你不能使用 locals()['x'] = whatever 来改变你所期望的局部变量值。

            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 上的答案中被烧死了,因为我已经在函数之外对其进行了测试并得到了我想要的更改。之后,我发现它被提及并与“潜入 Python”中的 globals() 的情况进行了对比。参见示例 8.12。 (虽然它没有注意到通过 locals() 进行的更改将在我上面显示的顶层起作用。)

            【讨论】:

            • 模块级别的 locals() 与模块中任何位置的 globals() 相同,不是吗?它指出 globals() 将接受更改。
            【解决方案16】:

            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】:

              嵌套列表的列表重复

              这让我今天搞砸了,浪费了我一个小时的调试时间:

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

              解释:Python list problem

              【讨论】:

                【解决方案18】:

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

                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 = {}
                

                【讨论】:

                • 我见过一些人对这个问题措手不及,尤其是那些来自 PHP 的人。
                • 对于不可变变量,您几乎总是希望它们是类变量并且大写:)
                【解决方案19】:

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

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

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

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

                【讨论】:

                • 这实际上在 Python 3 中已修复:TypeError: unorderable types: str() &lt; int().
                • 我无法在 Python 2.7.7 中复制它
                • print 的结果不是更令人惊讶吗,而不是比较中发生的情况? x + y 也会导致一些意想不到的事情,因为xy 之间的各种操作。
                • @hello_there_andy 这个问题在 Python 2.7.12 中绝对存在,尽管它在 Python 3.5.2 中引发了TypeError。试试'0' &lt; 1
                【解决方案20】:

                如果你在一个函数内赋值给一个变量,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)声明您要设置在函数外部定义的变量。

                【讨论】:

                  【解决方案21】:

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

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

                  【讨论】:

                  • 我无法复制。 range(2.0) 在 Python 2.7.12 和 3.5.2 中导致 TypeError: 'float' object cannot be interpreted as an integer
                  • 谢谢,我更新了示例。只有在使用 future 模块的 range 时,它才会“工作”。
                  • from future.builtins import range 在 Python 2.7 和 3.5 上生成 ImportError: No module named future.builtins。无论如何,我认识的人都没有这样做过。我建议删除答案。十五年前也许是这样,但现在应该不重要了。
                  • 显然你需要安装future 包。该包处理 Python 2 和 3 之间的转换和兼容性,因此自 15 年以来还没有完全过时,请参阅python-future.org。该包允许编写与 Python 2 兼容的 Python 3 代码。在没有任何其他限制的情况下,这是每个人都应该在 2016 年这个时间点做的事情,恕我直言。
                  • 考虑到我已经注意到在 Python 3.5.2 上也无法使用range 复制答案,所以你会绕圈子。问题是关于 Python 2.x 的陷阱,基本上你的答案不是。
                  【解决方案22】:

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

                  >>>bool(1)
                  True
                  

                  但你可能没想到它会走另一条路:

                  >>>float(True)
                  1.0
                  

                  如果您将字符串转换为数字并且您的数据具有真/假值,这可能是一个问题。

                  【讨论】:

                    【解决方案23】:

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

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

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2010-09-19
                      • 2010-09-14
                      • 2012-02-19
                      • 2010-11-16
                      • 2010-10-16
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多