欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

中知识点的总结和扩展

程序员文章站 2022-03-20 15:44:34
对中知识点的总结和扩展 《Effective Python》一书结合Python的语言特性,对代码规范进行了详细总结,是一本非常不错的Python实操指南。但我在阅读的过程中发现有些地方仅仅是告知读者“怎么做”,但是具体“为什么”不是很深入。下面内容是我对这些知识点的总结和相应原理的扩展。 (如有不 ......

对<effective python: 编写高质量python代码的59个有效方法>中知识点的总结和扩展

《effective python》一书结合python的语言特性,对代码规范进行了详细总结,是一本非常不错的python实操指南。但我在阅读的过程中发现有些地方仅仅是告知读者“怎么做”,但是具体“为什么”不是很深入。下面内容是我对这些知识点的总结和相应原理的扩展。

(如有不准确之处欢迎指正)

  1. python版本问题,略。


  1. 关于pep8:这是python代码风格的一些规范,感兴趣的同学可以自行了解。


  1. 在python3中,bytes和str是两种截然不同的类型:

    bytes是计算机原始的二进制格式,而str是包含unicode字符的,开发者不能以+号之类的操作符直接对它们两个进行混合操作。

    实际上,它们互相之间是编码(encode)与解码(decode)的关系。

    >>> s = "哇哈"
    >>> b = bytes(s,encoding="utf-8")  # encode
    >>> print(s)
    哇哈
    >>> print(b)
    b'\xe5\x93\x87\xe5\x93\x88'
    

    可以看到,s是str类型,返回的依旧是人类能懂的文字,而b则返回的实际上是6个16进制,每一个代表一字节。

    注意,在bytes函数中使用了encoding参数并且赋值"utf-8"。为什么呢?这是因为s中保存的是unicode字符(也叫万国码),这种字符人类能看懂,但计算机是不懂的。如果要把它转换成计算机能懂的语言(二进制),就需要进行编码(encode),而utf-8是一种编码的方式,通过这个方式可以将unicode编码成bytes格式,反之就是解码。

    一般而言python在使用str的时候会自动编码解码,不需要我们操心。但如果开发者需要手动操作bytes类型的数据则需要显式编码。

    >>> s2 = str(b,encoding="utf-8")  # 这里参数是encoding但实际是decode了
    >>> print(s2)
    哇哈
    

    当我们需要把bytes转成str是一样的,显示注明编码(解码)方式,然后将bytes类型对象进行解码,得到原本的unicode字符。

  2. 不要写巨复杂的单行表达式

    刚参加工作时写了这么一句代码:

    if (is_one_digit or is_two_digits or is_third_digits) \
                    and ((0< (current_digit-last_chinese_digit) <= 2)
                            or ((last_chinese_digit == 9 or last_chinese_digit == 8) and current_digit == 0)
                            or (last_chinese_digit == 0))\
                    and is_selection_line_score<=0 \
                    and calculation_or_not(rect_list)[0]>0.2:
    

    是不是很恶心?一般人看见这种代码心里肯定万马狂奔。单行如果有多个and或or这种东西,最好是要拆开几行来写,然后再放到if语句中做判断。

  3. 关于切片操作


    • 不要写多余的代码:能省略的就省略:
      >>> a = [1,2,3]
      >>> print(a[0:2])  # 0多余,可以省略。
      [1, 2]
      >>> print(a[:2])  # 如果从表头开始,0可以省略:同理如果到表尾,表尾也可以省略。
      [1, 2]
      
    • 切片操作不计较索引是否越界,但访问列表单个元素时索引不能越界:
      >>> a = [1,2,3]
      >>> b = a[:100]  # 切片无视越界
      >>> b
      [1, 2, 3]
      >>> c = a[100]  # 访问单个元素索引越界报错
      traceback (most recent call last):
      file "<stdin>", line 1, in <module>
      indexerror: list index out of range
      
    • 左侧list也可以使用切片操作:
      >>> a = [1,2,3,4,5,6]
      >>> a[:3] = [10,11]  # 右侧值会将左侧列表指定范围内的值替换掉。
      >>> a
      [10, 11, 4, 5, 6]
      
    • 切片操作是浅拷贝!
      深浅拷贝可参考我的另一篇博文:

  1. 在单次切片操作内,不要同时指定start,end与stride

    >>> a = [1,2,3,4,5,6,7,8,9,10]
    >>> print(a[1:5:2])  # 这样写显得有些乱
    [2, 4]
    
    >>> b = a[1:5]  # 可以先做范围切割
    >>> print(b[::2])  # 再做步进切割
    [2, 4]
    

  1. 用列表推导式取代map和filter

    列表推导式异常好用,而且使得代码看起来更简洁:
    >>> a = [1,2,3,4,5,6,7,8,9,10]
    >>> b = [x+1 for x in a]  # 用一份列表制作另外一份
    >>> b
    [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    >>> c = [x+1 for x in a if x>5]  # 还可以添加条件判断过滤掉一部分元素
    >>> c
    [7, 8, 9, 10, 11]
    

  1. 不要使用含有两个以上表达式的列表推导

    列表推导支持多级循环,也支持多个条件判断,但最好不要写太多,不然代码很难懂。

    建议:
    2个条件,2个循环,或者1个条件1个循环.

  2. 使用生成器表达式来改写数据量较大的列表推导

    生成器真的是python中极为强大的一个功能,它与列表推导的不同在于:列表推导得到的是一个实实在在的列表,而生成器得到的是一个算法,通过这个算法可以一项一项计算得到我们想要的结果,这样做就带来了一个好处:节约内存。

    >>> a = [1,2,3,4,5,6,7,8,9,10]
    >>> b = [x+1 for x in a]  # 列表推导式
    >>> c = (x+1 for x in a)  # 生成器表达式
    >>> b
    [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    >>> c
    <generator object <genexpr> at 0x000001f0cce7d5c8>
    

    可以看到,通过列表推导得到的列表b保存的是一个完整的列表。如果这个列表有上千万个元素,那么它占用的内存空间无疑是巨大的。而c则只保存了一个生成器对象,它会在在你需要的时候一个一个计算出值。

    >>> for x in c:
    ...    print(x)
    ...
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    

    生成器表达式还有另外一个好处:可以互相结合。

    >>> a = [1,2,3]
    >>> b = (x+1 for x in a)  # 通过b可以得到2,3,4
    >>> c = (y**2 for y in b)  # 通过c可以得到4,9,16
    >>> for y in c:
    ...     print(y)
    ...
    4
    9
    16
    

    外围的生成器每次前进时,都会推动内部的那个生成器,于是产生连锁反应。而且这种连锁生成器表达式可以在python中高效执行。


    生成器是迭代器的一种。那么迭代器是什么呢?

    python中有一种对象,它可以被for循环进行遍历,我们统称这种对象为“可迭代对象”(iteralbe)。可迭代对象之所以可以被循环遍历,是因为当循环体作用到它身上时,会自动调用它内部的__iter__方法使得它返回一个类似于“传送带”功能一样的对象,这个对象会一个接一个把元素进行返回。这个“传送带”即是迭代器(iterator)。

    a = [1,2,3,4]
    for x in a:
        print(x)
    # --------------------------
    # 下面这个语句块与上面是等价的
    # --------------------------
    it = iter(a)  # 手动调用iter(),实际是调用a中__iter__方法,返回一个迭代器it
    while true:
        try:
            x = next(it)  # 使用next()函数不断取迭代器中的下一个值。
            print(x)
        except stopiteration:  # 当没有值可取时会发生异常并结束循环。
            break
    

    在python中,list,dict,str等统统都是可迭代对象,也就是说,它们都可以执行iter()函数。但它们并不是迭代器;迭代器是一种惰性计算序列,它只有当你需要时才会计算相应的值给你,生成器就是一种迭代器。

    总结一下:

    • 可迭代对象指的是可以作用于for循环的对象(或者说实现了__iter__方法的对象),iter()方法可以通过可迭代对象返回一个迭代器。
    • 迭代器指的是可以作用于next()函数的对象,它是一个惰性计算序列。
    • 生成器是一种迭代器。
  3. 使用enumerate取代range

    enumerate 可以直接得到当前迭代器中每个元素的索引,写起来更简洁。

  4. 使用zip同时遍历多个迭代器

    python3 中的zip相当于生成器,会在遍历过程中逐次产生元组。需要注意的是,如果提供的迭代器长度不等,则zip会自动提前停止。

  5. 不要再for和while循环后面写else块

    从来没这么写过......略

  6. 合理运用try/except/else/final

    final语句块用于执行那些无论如何都要执行的部分。
    else则用于将异常与非异常语句块区分开,提升代码可读性。

(未完待续)