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

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

    嵌套列表的创建

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

          li = [[]] * 3
          print(li)
          # Out: [[], [], []]
    • 通过这个方法,可以得到一个包含3个list的嵌套list,我们来给第一个list增加一个元素:
          li[0].append(1)
          print(li)
          # Out: [[1], [1], [1]]
    • 通过输出的结果可以看初,我们只给第一元素增加元素,结果三个list都增加了一个元素。这是因为[[]]*3并不是创建了三个不同list,而是创建了三个指向同一个list的对象,所以,当我们操作第一个元素时,其他两个元素内容也会发生变化的原因。效果等同于下面这段代码:
          li = []
          element = [[]]
          li = element + element + element
          print(li)
          # Out: [[], [], []]
          element.append(1)
          print(li)
          # Out: [[1], [1], [1]]
    • 我们可以打印出元素的内存地址一探究竟:
          li = [[]] * 3
          print([id(inner_list) for inner_list in li])
          # Out: [6830760, 6830760, 6830760]
    • 到这我们可以明白原因了。那如何解决了?可以这样:
          li = [[] for _ in range(3)]
    • 这样我们就创建了三个不同的list对象
          print([id(inner_list) for inner_list in li])
          # Out: [6331048, 6331528, 6331488]

      列表元素的引用

    • 不要使用索引方法遍历list,例如:
          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))

      注意 == 符号的使用

          if (var == True):
              # 当var是:True、1、 1.0、 1L时if条件成立
          if (var != True):
              # 当var不是 True 和 1 时if条件成立
          if (var == False):
              # 当var是 False 或者 0 (or 0.0, 0L, 0j) if条件成立
      
          if (var == None):
              # var是None if条件成立
      
          if var:
              # 当var非空(None或者大小为0)对象 string/list/dictionary/tuple, non-0等if条件成立
      
          if not var:
              # 当var空(None或者大小为0)对象 string/list/dictionary/tuple, non-0等if条件成立
      
          if var is True:
              # 只有当var时True时 if条件成立 1也不行
      
          if var is False:
              # 只有当var时False时 if条件成立 0也不行
      
          if var is None:
          # 和var == None 一致

      捕获异常由于提前检查

    • 不够优雅的代码:
          if os.path.isfile(file_path):
              file = open(file_path)
          else:
              # do something

      比较好的做法:

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

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

          with open(file_path) as file:

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

    类变量初始化

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

      • 如果类属性更改,则初始值更改。
      • 如果将可变对象设置为默认值,您将获得跨实例共享的相同对象。

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

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

      函数默认参数

      def foo(li=[]):
          li.append(1)
          print(li)
      
      foo([2])
      # Out: [2, 1]
      foo([3])
      # Out: [3, 1]

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

      foo()
      # Out: [1] As expected...
      
      foo()
      # Out: [1, 1]  Not as expected...

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

      def foo(li=None):
          if not li:
              li = []
          li.append(1)
          print(li)
      
      foo()
      # Out: [1]
      
      foo()
      # Out: [1]

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

      x = []
      foo(li=x)
      # Out: [1]
      
      foo(li="")
      # Out: [1]
      
      foo(li=0)
      # Out: [1]

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

      def foo(li=None):
          if li is None:
              li = []
          li.append(1)
          print(li)
      
      foo()
      # Out: [1]

      在遍历时修改

    • for语句在遍历对象是会生成一个迭代器,如果你在遍历的过程中修改对象,会产生意想不到的结果:
          alist = [0, 1, 2]
          for index, value in enumerate(alist):
              alist.pop(index)
          print(alist)
          # Out: [1]
    • 第二个元素没有被删除,因为迭代按顺序遍历索引。上述循环遍历两次,结果如下:
          # Iteration #1
          index = 0
          alist = [0, 1, 2]
          alist.pop(0) # removes ‘0‘
      
          # Iteration #2
          index = 1
          alist = [1, 2]
          alist.pop(1) # removes ‘2‘
      
          # loop terminates, but alist is not empty:
          alist = [1]
    • 如果避免这个问题了,可以创建另外一个list
          alist = [1,2,3,4,5,6,7]
          for index, item in reversed(list(enumerate(alist))):
              # delete all even items
              if item % 2 == 0:
                  alist.pop(index)
          print(alist)
          # Out: [1, 3, 5, 7]

      整数和字符串定义

    • python预先缓存了一个区间的整数用来减少内存的操作,但也正是如此,有时候会出很奇特的错误,例如:
          >>> -8 is (-7 - 1)
          False
          >>> -3 is (-2 - 1)
          True
    • 另外一个例子
          >>> (255 + 1) is (255 + 1)
          True
          >>> (256 + 1) is (256 + 1)
          False
    • 通过不断的测试,会发现(-3,256)这区间的整数都返回True,有的甚至是(-8,257)。默认情况下,[-5,256]会在解释器第一次启动时创建并缓存,所以才会有上面的奇怪的行为。这是个很常见但很容易被忽略的一个坑。解决方案是始终使用equality(==)运算符而不是 identity(is)运算符比较值。
    • Python还保留对常用字符串的引用,并且可以在比较is字符串的身份(即使用)时产生类似的混淆行为。
      >>> ‘python‘ is ‘py‘ + ‘thon‘
      True       
    • python字符串被缓存了,所有python字符串都是该对象的引用,对于不常见的字符串,即使字符串相等,比较身份也会失败。
      >>> ‘this is not a common string‘ is ‘this is not‘ + ‘ a common string‘
      False
      >>> ‘this is not a common string‘ == ‘this is not‘ + ‘ a common string‘
      True
    • 所以,就像整数规则一样,总是使用equal(==)运算符而不是 identity(is)运算符比较字符串值。

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

    • 有个例子:

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

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

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

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

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

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

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

    or操作符

    • 例如

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

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

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

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

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

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

      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

    大家在学python的时候肯定会遇到很多难题,以及对于新技术的追求,这里推荐一下我们的Python学习扣qun:784758214,这里是python学习者聚集地!!同时,自己是一名高级python开发工程师,从基础的python脚本到web开发、爬虫、django、数据挖掘等,零基础到项目实战的资料都有整理。送给每一位python的小伙伴!每日分享一些学习的方法和需要注意的小细节

    点击:python技术分享交流

    原文地址:https://blog.51cto.com/14318113/2406960

    时间: 2024-11-05 23:27:46

    Python里那些深不见底的“坑”的相关文章

    致青春——写给深不见底的悲伤

    致青春 ——写给深不见底的悲伤 几卷雪雨,几卷寒风,江南早已是烟水迷离.青春却是一道明媚的忧伤,在烟雨江南的石子路上延伸而看不到终点,伤感,无穷尽. 曾经走过人间四季春秋,与美丽无言的青春有过相濡以沫的约定:茉莉在黄昏中浮动着神秘的幽香,终于,我还是长大了,伴着快乐与悲伤.只是,那悲伤,深不见底. 寒冷的夜风,给人淡淡的不安,阴寒的空气中,饱含着愤怒和不堪:破碎的灵魂,不忍心流年逝去中的自己与青春黯然相遇,四处流浪,天涯海角,一路听风的声音,听雨的声音,听青春久远无穷尽的声音. 我看到遥远的时空

    (6)“深不见底”的C#

    首先我想说下C#中的Linq,大家初学时在每个程序的开头都会见到using System.Linq;,可能猜到它跟数据库有关,但是具体不清楚.这里如果编程需要用到数据库, 则需要引入System.Data.Link.它让程序与数据库轻易的连接在一起,安卓平台也有自带的数据库,但是他们两者的性能我还没有什么好的方法来比较,这一点留给聪明的读者吧! 总之,它给C#的程序员带去一道光芒,让死寂的编程出现了一丝生机,免去了配置各种数据库的苦恼.这里为了读者的理解,奉上一张PPt的图 这张图很明白的告诉你

    对你的爱深不见底

    这题的话,其实可以先打个表找一下规律,然后会发现,n是没什么卵用的,你只要关注m就行了.然而,m很大很大很大,不得不开高精,这就为解题带来了麻烦. 我们先把答案的序列打出来: 乍一看,很有规律的样子(别说你没看出来).我们再细分一下: 我们发现,划分成的子区间都是公差为1的等差数列,且区间长都是fib数.我们在观察一下: 我们发现,每个区间的第一行元素+1也是fib数! 那么,如果我们知道一个数m,我们要知道ans,我们可以通过两个fib数列推出! 我们先可以求出m>sum(fib[1~i])(

    [高精度][规律][二分] Jzoj P4213 对你的爱深不见底

    Description 出乎意料的是,幸运E 的小R 居然赢了那个游戏.现在欣喜万分的小R 想要写一张明信片给小Y,但是因为小R 非常羞涩,所以他打算采用一些比较神奇的方式来表达.他定义了一些字符串,s1 = a,s2 = b,si =s_i-1  +  s_i-2  (i >=3).同时他定义了一个字符串s 的权值为一个最大的i <|s|满足s 长度为i 的前缀等于长度为i 的后缀.比如字符串aba 的权值就是1,abab 的权值就是2,aaaa 的权值就是3.现在小R 在明信片上给出了两个

    APICLOUD APP外包行业的水深不见底

    APP外包行业的水深不见底,里面的名堂似乎总也弄不懂. 对于创业者以及企业来说,这些不靠谱的外包公司就是创业路上一个个躲不过去的坑,为了能够让创业的每一个人能够走得更远,移动应用开发专家APICloud就来扒一扒关于APP外包公司的各种"套路". 当然此处主要是针对一些不良甚至恶劣的APP外包公司,切勿以偏概全,一棒子打死了APP行业的良心企业. 1."套牌"公司,都是假 现如今,胆子比天大.不少骗子公司为了能够骗钱可是什么都能干得出来.开发一个APP的成本可不低,

    在python里如何动态添加类的动态属性呢?

    body { font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI",Tahoma,Helvetica,Sans-Serif,"Microsoft YaHei", Georgia,Helvetica,Arial,sans-serif,宋体, PMingLiU,serif; font-size: 10.5pt; line-height: 1.5;

    python里的splitlines详解

    Python的split方法函数可以分割字符串成列表,默认是以空格作为分隔符sep来分割字符串. In [1]: s = "www jeapedu com" In [2]: print s.split() ['www', 'jeapedu', 'com'] 当然可以改变sep分割字符串为其他字符串. In [6]: t = "www.jeapedu.com" In [7]: print t.split(".") ['www', 'jeapedu'

    python里多线程的写法

    python里多线程的写法 今天用到python多线程的时候, 发现不知道如何正确的等待所有线程结束后再结束主线程. 其实到最后我才知道这都是杞人忧天, Thread()出来的实例本来就是等到主进程结束后才结束. 官方解释: daemon A boolean value indicating whether this thread is a daemon thread (True) or not (False). This must be set before start() is called

    python里的拆包、引用、递归与匿名函数

    拆包:*A拆元组,**B拆字典. 引用:在C.C++里面里面a=1,b=a,实际上相当于硬链接,相当于两份a.b各有一个1,多占一个空间,而在python里就是软连接,只有一份,通过id(a)来查看id都一样, 在python里定义的东西如一直没引用,那么就会成为垃圾,不用担心,python有自己的机制去除垃圾. 不可变类型:数字.字符串.元组. 可变类型:列表.字典,它们都不能做字典的key. 递归:一个函数里调用了这个函数自己,递归完成阶乘,3的阶乘=3*(2的阶乘) 递归时一定要想到何时要