微信号:imooc-com

介绍:慕课网是一个超酷的互联网、IT技术免费学习平台,创新的网络一站式学习、实践体验;服务及时贴心,内容专业、有趣易学。专注服务互联网工程师快速成为技术高手!

小心了,Python工程师容易犯的一些坑

2018-07-12 07:50 七寸法师


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-0等if条件成立

  11.  if not var:

  12.  # 当var空(None或者大小为0)对象 string/list/dictionary/tuple, non-0等if条件成立

  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. 如果将可变对象设置为默认值,您将获得跨实例共享的相同对象


错误示范(除非你想要静态变量)

 
           
  1. class Car(object):

  2.  color = "red"

  3.  wheels = [Wheel(), Wheel(), Wheel(), Wheel()]

正确的做法:

 
           
  1. class Car(object):

  2.  def __init__(self):

  3.  self.color = "red"

  4.  self.wheels = [Wheel(), Wheel(), Wheel(), Wheel()]


六、函数默认参数

 
           
  1. def foo(li=[]):

  2.  li.append(1)

  3.  print(li)

  4. foo([2])

  5. # Out: [2, 1]

  6. foo([3])

  7. # Out: [3, 1]

该代码的行为与预期的一样,但如果我们不传递参数呢?

 
           
  1. foo()

  2. # Out: [1] As expected...

  3. foo()

  4. # Out: [1, 1] Not as expected...


这是因为函数参数类型是定义是确认的而不是运行时,所以在两次函数调用时,li指向的是同一个list对象,如果要解决这个问题,可以这样:

 
           
  1. def foo(li=None):

  2.  if not li:

  3.  li = []

  4.  li.append(1)

  5.  print(li)

  6. foo()

  7. # Out: [1]

  8. foo()

  9. # Out: [1]


这虽然解决了上述的问题,但,其他的一些对象,比如零长度的字符串,输出的结果就不是我们想要的。

 
           
  1. x = []

  2. foo(li=x)

  3. # Out: [1]

  4. foo(li="")

  5. # Out: [1]

  6. foo(li=0)

  7. # Out: [1]


最常用的办法是检查参数是不是None

 
           
  1. def foo(li=None):

  2.  if li is None:

  3.  li = []

  4.  li.append(1)

  5.  print(li)

  6. foo()

  7. # Out: [1]



七、在遍历时修改

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

 
           
  1. alist = [012
     

  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 = [012]

  4. alist.pop(0# removes '0'

  5. # Iteration #2

  6. index = 1

  7. alist = [12]

  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 + 1is (255 + 1)

  2. True

  3. >>> (256 + 1is (256 + 1)

  4. False

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

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

 
           
  1. >>> 'python' is 'py' + 'thon' 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


或者,为了使其更有效率:

 
           
  1.  if any(x == 3 for x in (a, b, c)): # Right


更加简短的写法:

 
           
  1.  if 3 in (a, b, c): # Right



 推荐Python教程



本文作者的教程

Python3 通用自动化运维架构

点击查看 >

全网最热Python3入门+进阶

入门就是最新版本

点击查看 > 

Python3迎战Spark核心组件

结合调度爆款框架Azkaban

点击查看 > 






 
慕课网 更多文章 2小时搞定移动直播App开发 开始学习 4 年前端狗,2 年 CTO,一个程序员六年的奋斗史 教师节,老师竟然送你礼物!(9.10日,不限名额!!!) 【慕课网实战课】从0开始双剑合璧Laravel,AngularJS全栈开发知乎 五功大成!慕课网安卓5.0客户端重磅发布!
猜您喜欢 Debian 8 "Jessie" 发布! 各厂推荐算法! HBase作为在线存储解决方案的稳定性保障探讨 你假笨的北京之行,附上QCon分享的PPT《JVM问题定位典型案例分析》 上海地区IC工程师月薪调查