资讯专栏INFORMATION COLUMN

Python里那些深不见底的“坑”

KaltZK / 640人阅读

摘要:是一门清晰简洁的语言,如果你对一些细节不了解的话,就会掉入到那些深不见底的坑里,下面,我就来总结一些里常见的坑。这是个很常见但很容易被忽略的一个坑。

Python是一门清晰简洁的语言,如果你对一些细节不了解的话,就会掉入到那些深不见底的“坑”里,下面,我就来总结一些Python里常见的坑。

列表创建和引用

嵌套列表的创建

使用*号来创建一个嵌套的list:

</>复制代码

  1. li = [[]] * 3
  2. print(li)
  3. # Out: [[], [], []]

通过这个方法,可以得到一个包含3个list的嵌套list,我们来给第一个list增加一个元素:

</>复制代码

  1. li[0].append(1)
  2. print(li)
  3. # Out: [[1], [1], [1]]

通过输出的结果可以看初,我们只给第一元素增加元素,结果三个list都增加了一个元素。这是因为[[]]*3并不是创建了三个不同list,而是创建了三个指向同一个list的对象,所以,当我们操作第一个元素时,其他两个元素内容也会发生变化的原因。效果等同于下面这段代码:

</>复制代码

  1. li = []
  2. element = [[]]
  3. li = element + element + element
  4. print(li)
  5. # Out: [[], [], []]
  6. element.append(1)
  7. print(li)
  8. # Out: [[1], [1], [1]]

我们可以打印出元素的内存地址一探究竟:

</>复制代码

  1. li = [[]] * 3
  2. print([id(inner_list) for inner_list in li])
  3. # Out: [6830760, 6830760, 6830760]

到这我们可以明白原因了。那如何解决了?可以这样:

</>复制代码

  1. li = [[] for _ in range(3)]

这样我们就创建了三个不同的list对象

</>复制代码

  1. print([id(inner_list) for inner_list in li])
  2. # Out: [6331048, 6331528, 6331488]
列表元素的引用

不要使用索引方法遍历list,例如:

</>复制代码

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

比较好的方法是:

</>复制代码

  1. for elem in tab:
  2. print(elem)

for语句会自动生成一个迭代器。如果你需要索引位置和元素,使用enumerate函数:

</>复制代码

  1. for i, elem in enumerate(tab):
  2. print((i, elem))

注意 == 符号的使用

</>复制代码

  1. if (var == True):
  2. # 当var是:True、1、 1.0、 1L时if条件成立
  3. if (var != True):
  4. # 当var不是 True 和 1 时if条件成立
  5. if (var == False):
  6. # 当var是 False 或者 0 (or 0.0, 0L, 0j) if条件成立
  7. if (var == None):
  8. # var是None if条件成立
  9. if var:
  10. # 当var非空(None或者大小为0)对象 string/list/dictionary/tuple, non-0if条件成立
  11. if not var:
  12. # 当var空(None或者大小为0)对象 string/list/dictionary/tuple, non-0if条件成立
  13. if var is True:
  14. # 只有当var时True时 if条件成立 1也不行
  15. if var is False:
  16. # 只有当var时False时 if条件成立 0也不行
  17. if var is None:
  18. # 和var == None 一致

捕获异常由于提前检查

不够优雅的代码:

</>复制代码

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

比较好的做法:

</>复制代码

  1. try:
  2. file = open(file_path)
  3. except OSError as e:
  4. # do something

在python2.6+的里面可以更简洁:

</>复制代码

  1. with open(file_path) as file:

之所以这么用,是这么写更加通用,比如file_path给你传个None就瞎了,还得判断是不是None,如果不判断,就又得抓异常,判断的话,代码有多写了很多。

类变量初始化

不要在对象的init函数之外初始化类属性,主要有两个问题

如果类属性更改,则初始值更改。

如果将可变对象设置为默认值,您将获得跨实例共享的相同对象。

</>复制代码

  1. 错误示范(除非你想要静态变量)
  2. ```
  3. class Car(object):
  4. color = "red"
  5. wheels = [Wheel(), Wheel(), Wheel(), Wheel()]
  6. ```
  7. 正确的做法:
  8. ```
  9. class Car(object):
  10. def __init__(self):
  11. self.color = "red"
  12. self.wheels = [Wheel(), Wheel(), Wheel(), Wheel()]
  13. ```
  14. **函数默认参数**
  15. ```
  16. def foo(li=[]):
  17. li.append(1)
  18. print(li)
  19. foo([2])
  20. # Out: [2, 1]
  21. foo([3])
  22. # Out: [3, 1]
  23. ```
  24. 该代码的行为与预期的一样,但如果我们不传递参数呢?
  25. ```
  26. foo()
  27. # Out: [1] As expected...
  28. foo()
  29. # Out: [1, 1] Not as expected...
  30. ```
  31. 这是因为函数参数类型是定义是确认的而不是运行时,所以在两次函数调用时,li指向的是同一个list对象,如果要解决这个问题,可以这样:
  32. ```
  33. def foo(li=None):
  34. if not li:
  35. li = []
  36. li.append(1)
  37. print(li)
  38. foo()
  39. # Out: [1]
  40. foo()
  41. # Out: [1]
  42. ```
  43. 这虽然解决了上述的问题,但,其他的一些对象,比如零长度的字符串,输出的结果就不是我们想要的。
  44. ```
  45. x = []
  46. foo(li=x)
  47. # Out: [1]
  48. foo(li="")
  49. # Out: [1]
  50. foo(li=0)
  51. # Out: [1]
  52. ```
  53. 最常用的办法是检查参数是不是None
  54. ```
  55. def foo(li=None):
  56. if li is None:
  57. li = []
  58. li.append(1)
  59. print(li)
  60. foo()
  61. # Out: [1]
  62. ```
  63. **在遍历时修改**

for语句在遍历对象是会生成一个迭代器,如果你在遍历的过程中修改对象,会产生意想不到的结果:

</>复制代码

  1. alist = [0, 1, 2]
  2. for index, value in enumerate(alist):
  3. alist.pop(index)
  4. print(alist)
  5. # Out: [1]

第二个元素没有被删除,因为迭代按顺序遍历索引。上述循环遍历两次,结果如下:

</>复制代码

  1. # Iteration #1
  2. index = 0
  3. alist = [0, 1, 2]
  4. alist.pop(0) # removes "0"
  5. # Iteration #2
  6. index = 1
  7. alist = [1, 2]
  8. alist.pop(1) # removes "2"
  9. # loop terminates, but alist is not empty:
  10. alist = [1]

如果避免这个问题了,可以创建另外一个list

</>复制代码

  1. alist = [1,2,3,4,5,6,7]
  2. for index, item in reversed(list(enumerate(alist))):
  3. # delete all even items
  4. if item % 2 == 0:
  5. alist.pop(index)
  6. print(alist)
  7. # Out: [1, 3, 5, 7]

整数和字符串定义

python预先缓存了一个区间的整数用来减少内存的操作,但也正是如此,有时候会出很奇特的错误,例如:

</>复制代码

  1. >>> -8 is (-7 - 1)
  2. False
  3. >>> -3 is (-2 - 1)
  4. True

另外一个例子

</>复制代码

  1. >>> (255 + 1) is (255 + 1)
  2. True
  3. >>> (256 + 1) is (256 + 1)
  4. False

通过不断的测试,会发现(-3,256)这区间的整数都返回True,有的甚至是(-8,257)。默认情况下,[-5,256]会在解释器第一次启动时创建并缓存,所以才会有上面的奇怪的行为。这是个很常见但很容易被忽略的一个坑。解决方案是始终使用equality(==)运算符而不是 identity(is)运算符比较值。

Python还保留对常用字符串的引用,并且可以在比较is字符串的身份(即使用)时产生类似的混淆行为。

</>复制代码

  1. >>> "python" is "py" + "thon"
  2. True

python字符串被缓存了,所有python字符串都是该对象的引用,对于不常见的字符串,即使字符串相等,比较身份也会失败。

</>复制代码

  1. >>> "this is not a common string" is "this is not" + " a common string"
  2. False
  3. >>> "this is not a common string" == "this is not" + " a common string"
  4. True

所以,就像整数规则一样,总是使用equal(==)运算符而不是 identity(is)运算符比较字符串值。

列表推导和循环中的变量泄漏

有个例子:

</>复制代码

  1. i = 0
  2. a = [i for i in range(3)]
  3. print(i) # Outputs 2

python2中列表推导改变了i变量的值,而python3修复了这个问题:

</>复制代码

  1. i = 0
  2. a = [i for i in range(3)]
  3. print(i) # Outputs 0

类似地,for循环对于它们的迭代变量没有私有的作用域

</>复制代码

  1. i = 0
  2. for i in range(3):
  3. pass
  4. print(i) # Outputs 2

这种行为发生在Python 2和Python 3中。

为了避免泄漏变量的问题,请在列表推导和for循环中使用新的变量。

or操作符

例如

</>复制代码

  1. if a == 3 or b == 3 or c == 3:

这个很简单,但是,再看一个:

</>复制代码

  1. if a or b or c == 3: # Wrong

这是由于or的优先级低于==,所以表达式将被评估为if (a) or (b) or (c == 3):。正确的方法是明确检查所有条件:

</>复制代码

  1. if a == 3 or b == 3 or c == 3: # Right Way

或者,可以使用内置函数any()代替链接or运算符:

</>复制代码

  1. if any([a == 3, b == 3, c == 3]): # Right

或者,为了使其更有效率:
if any(x == 3 for x in (a, b, c)): # Right
更加简短的写法:
if 3 in (a, b, c): # Right

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/44015.html

相关文章

  • 前端入体验与分享

    摘要:同源策略同源策略是一种约定,由公司年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到等攻击。 一、Vue变化检测 背景 初始化对象,属性未知;某些事件触发时,对象改变(新增属性),Vue监听不到 原因 Vue.js 不能检测到对象属性的添加或删除,因为Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 dat...

    hss01248 评论0 收藏0
  • 略谈人类与计算机的智能

    摘要:熵增原理自发的有序是不存在的我坦白这一点是过于复杂的物理概念,我解释不明白,但结论能够最简单的说明有序必须在某种介入之下完成。还是熵增原理那就容易理解为什么数据的准确和恒定,对计算机是如此的重要。 来自这个问题的答案。 人类思维和智能的本质是什么?思考这个问题,也许能让我们更好的了解计算机和人类自己。 ——甚至某种方面上来看,我都有点觉得:计算机其实就是我们自己…… 人类总体上是...

    Eastboat 评论0 收藏0
  • JS专题之浅拷贝

    摘要:在之前的文章专题之数据类型和类型检测中我有讲过,中的数据类型分为两种,基本数据类型和引用数据类型,基本数据类型是保存在栈的数据结构中的是按值访问,所以不存在深浅拷贝问题。 前言 在开发过程中,偶尔会遇到这种场景,拿到一个数据后,你打算对它进行处理,但是你又希望拷贝一份副本出来,方便数据对比和以后恢复数据。 那么这就涉及到了 JS 中对数据的深浅拷贝问题,所谓深浅拷贝,浅拷贝的意思就是,...

    ASCH 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<