zoukankan      html  css  js  c++  java
  • Python -- Effective Python:编写高质量Python代码的59个有效方法

    第 1 章 用 Pythonic 方式来思考

    第 1 条:确认自己所用的 Python 版本

    1. python --version

    2. import sys print(sys.version_info) print(sys.version)

    第 2 条:遵循 PEP8 风格标准指南

    《 Python Enhancement Proposal #8》(8 号 Python 增强提案)又叫 PEP 8

    第 3 条:了解 bytes、str 与 unicode 的区别

    1. Unicode 字符转换成二进制数据 : encode 方法
    2. 二进制数据转换成Unicode 字符 :decode 方法

    第 4 条:用辅助函数代替复杂的函数表达式

    如果出现复杂的表达式,可以抽调成一个函数方法,如果需要反复使用相同逻辑,更应该把复杂的表达式放进辅助函数中。

    第 5 条:了解切割序列的方法

    1.不要写多余的代码:当 start 索引为 0,或 end 索引为序列长度时,应该将其省略。

    1. 切片操作不会计较 start 与 end 索引是否越界(如 a[:20] 或 a[-20:])。

    3.对 list 赋值的时候,如果使用切片操作,就会把原列表中处在相关范围内的值替
    换成新值,即便它们的长度不同也依然可以替换

    第 6 条:在单次切片操作内,不要同时指定 start 、end 和 stride

    somelist[开始索引:结束索引:步进值]

    不要同时使用 start 、 end 和 stride ,理解很困难;可以拆作范围切割和步进切割两条赋值表达,或考虑使用内置 itertools 模块中的 islice
    尽量不使用负值的 stride 。

    第 7 条:用列表推导来取代 map 和 filter

    a = [1, 2 ,3 ,4 ,5 ,6 , 7, 8, 9, 10]

    1. squares = [x x**2 for x in a if x % 2 == 0]

    2. squares = map(lambda x: x**2, filter(lambda x: x % 2 == 0), a)

    结果均为 [4, 16, 36, 64, 100] 。第1个中的采用列表推导来做,那么只需在循环后面添加条件表达式即可;第2个把内置的 f ilter 函数与 map 结合起来,也能达成同样的效果,但是代码会写得非常
    难懂。
    字典与集也支持推导表达式。

    第 8 条:不要使用两个以上的表达式的列表推导

    列表推导支持多级循环,每一级循环也支持多项条件。超过两个表达式的列表推导是很难理解的,应该尽量避免。
    可以使用两个条件、两个循环或
    一个条件搭配一个循环。如果要写的代码比这还复杂,那就应该使用普通的 if 和 for 语句,并编写辅助函数。

    第 9 条:用生成器表达式来改写数据量较大的列表推导

    当输入的数据量较大时,列表推导可能会因为占用太多内存而出问题。为了解决这个问题,Python 提供了生成器表达式(generator expression),它是对列表
    推导和生成器的一种泛化(generalization)。

    把实现列表推导所用的那种写法放在一对圆括号中,就构成了生成器表达式。此时立刻返回一个迭代器,逐次调用内置的 next 函数,以这个迭代器为参数,输出一个值,做循环输出即可。

    第 10 条:尽量用 enumerate 取代 range

    name_list = ['aa', 'bbb', 'cccc', 'ddddd', 'eeeeee']
    
    for i, name in enumerate(name_list):
        print('%d : %s' % (i + 1, name))
    
    

    结果:

    1 : aa
    2 : bbb
    3 : cccc
    4 : ddddd
    5 : eeeeee
    
    

    还可以直接指定 enumerate 函数开始计数时所用的值(默认为0,本例从 1 开始计数),结果不变,这样能
    把代码写得更短。

    name_list = ['aa', 'bbb', 'cccc', 'ddddd', 'eeeeee']
    
    for i, name in enumerate(name_list, 1):
        print('%d : %s' % (i , name))
    
    

    3.11

    第 11 条:用 zip 函数同时遍历两个迭代器

    1. 内置的 zip 函数里可以平行的同时遍历两个迭代器,如果长度不一样则会以较短的迭代器为准而结束循环。

    2. python 3 中的 zip 函数相当于生成器,可以逐次产生元组。

    3. python 2 则时一次性生成返回整份列表;如果用 zip 函数遍历的数据比较多,则会导致程序崩溃,需要用 itertools 内置模块的 izip 函数

    第 12 条:不要在 for 和 while 循环后面写 else 模块

    # 判断两个数是否互质
    a = 4
    b = 9
    for i in range(2, min(a, b) + 1):
        print('Testing', i)
        if  a % i == 0 and b % i ==0:
            print('Not coprime')
            break
    else:
        print('Coprime')
    

    结果:

    Testing 2
    Testing 3
    Testing 4
    Coprime
    
    

    只有当 for 或 while 整个循环主体没有遇到 break 语句时,循环后面的 else 模块才会运行。不要在循环后面使用 else 块,难以理解,用函数返回的形式代替。

    第 13 条:合理利用 try/except/else/finally 结构中的每个代码块

    else 块:try 块没有发生异常时才会运行。

    例子:要从文件中读取某项事务的描述信息,处理该事务,然后就地更新该文件。

    解题思路:为了实现此功能,我们可以用 try 块来读取文件并处理其内容,用 except 块来应对 try 块中可能发生的相关异常,用 else 块实时地更新文件并把更新中可能出现的异
    常回报给上级代码,然后用 finally 块来清理文件句柄。
    
    UNDEFINED = object()
    
    def divide_json(path):
        handle = open(path, 'r+')    # May raise IOError
        try:
            data = handle.read()        # May raise UnicodeDecodeError
            op = json.loads(data)        # May raise ValueError
            value = (
                    op['numerator'] /
                    op['denominator'])    # May raise ZeroDivisionError
        except ZeroDivisionError as e:
            return UNDEFINED
        else:
            op['result'] = value
            result = json.dumps(op)
            handle.seek(0)
            handle.write(result)            # May raise IOError
            return value
        finally:
            handle.close()                    # Always runs
    
    

    第 2 章 函数

    第 14 条:尽量用异常来表示特殊情况,而不要返回 None

    # 处理分母为 0 的方法(异常)
    
    def divide(a, b):
        try:
            return a / b
        except ZeroDivisionError as e:
            raise ValueError('Invalid inputs') from e
    
    x, y = 5, 2
    try:
        result = divide(x, y)
    except ValueError:
        print('Invalid inputs')
    else:
        print('Result is %.1f' % result)
    
    
    # 结果:Result is 2.5
    
    

    第 15 条:了解如何在闭包里使用外围作用域中的变量

    python支持闭包。
    获取闭包内的数据:

    1. nonlocal :nonlocal 语句清楚地表明(python 2 不支持):如果在闭包内给该变量赋值,那么修改的其实是闭包外那个作用域中的变量。这与 global 语句互为补充,global 用来表示对该变量的赋值操作,将会直接修改模块作用域里的那个变量。尽量不要用。
    
    2. 使用可变量(例如,包含单个元素的列表,字典,元组)
    

    如果使用 nonlocal 的那些代码,已经写得越来越复杂,那就应该将相关的状态封装成辅助类(helper class)。

    第 16 条:考虑用生成器来改写直接返回列表的函数

    如果返回的列表量比较大,那么程序可能耗尽内存并崩溃,用生成器改写则不会出现这种情况。生成器是使用 yield 表达式的函数。调用生成器函数时候,它并不会真的运行,而是会返回迭代器。每次在这个迭代器上面调用内置的 next 函数是,迭代器会把生成器推到下一个 yield 表达式那里。生成器传给 yield 的每一个值,都会有迭代器返回给调用者。

    # 计空格位置索引
    def index_words_iter(text):
        if text:
            yield 0
        for index, letter in enumerate(text):
            if letter == ' ':
                yield index + 1
    
    

    第 17 条:在参数上面迭代时,要多加小心

    迭代器只能欸遍历一次,如果多次遍历则会出现意想不到的错误。
    (列表可以被多次迭代)

    1. 为解决迭代器不可多次遍历,可以使用迭代器制作一份列表,缺点在于列表的数据量大的话,会造成程序崩溃;

    2. ① 可以使用 lambda 表达式代替,该表达式在调用生成器的时候,可以每次产生新的迭代器;(略显生硬)
      ② 新编一种实现迭代器协议的容器类。(建议使用)

    # 可以迭代的容器类
    class ReadVisits(object):
        def __init__(self, data_path):
            self.data_path = data_path
    
        def __iter__(self):
            with open(self.data_path) as f:
                for line in f:
                    yield int(line)
    
    # 可以判断是否传入的是容器类的求取百分比的函数
    def normalize_defensive(numbers):
        if iter(numbers) is iter(numbers):    # An iterator -- bad !
            raise TypeError('Must supply a container !')
        total = sum(numbers)
        result = []
        for value in numbers:
            percent = 100 * value / total
            result.append(percent)
        return result
    
    # visits = TreadVisits(path)
    visits = [15, 35, 80]
    per = normalize_defensive(visits)    # No error
    print(per)
    # it = iter(visits)
    # normalize_defensive(visits)    # Error
    
    

    第 18 条:用数量可变的位置参数减少视觉杂讯

    *args :(习惯写成这样 starargs)

    1. 做函数参数时可以接收国定形参以外的任意数量的位置参数;

    2. 调用函数时,使用 * 操作符,把序列中的元素当成位置参数传给函数;

    3. 在已经接受 *args 参数的函数上面继续添加位置参数,可能会产生难以排查的
      bug。

    第 19 条:用关键字参数来表达可选的行为

    1. 函数参数可以按位置或关键字来指定。

    2. 只使用位置参数来调用函数,可能会导致这些参数值的含义不够明确,而关键
      字参数则能够阐明每个参数的意图。

    3. 给函数添加新的行为时,可以使用带默认值的关键字参数,以便与原有的函数
      调用代码保持兼容。

    4. 可选的关键字参数,总是应该以关键字形式来指定,而不应该以位置参数的形
      式来指定。

    第 20 条:用 None 和文档字符串来描述具有动态默认值的参数

    如果参数的实际默认值是可变类型(mutable),那就一定要记得用 None 作为形式
    上的默认值。(即形参值设为 None)

    def log(message, when=None):
        """Log a message with a timestamp.
    
        Args:
                message: Message to print.
                when: datetime of when the message occurred.
                        Defaults to the present time.
        """
        when = datetime.now() if when is None else when
        print('%s: %s' % (when, message))
    
    
    # 两个时间戳就会不一样,如果形参 when=datetime.now() ,则输出相同时间戳
    log('Hi there!')
    sleep(0.1)
    log('Hi again!')
    
    
    
    1. 参数的默认值,只会在程序加载模块并读到本函数的定义时评估一次。对于 {}
      或 [] 等动态的值,这可能会导致奇怪的行为。

    2. 对于以动态值作为实际默认值的关键字参数来说,应该把形式上的默认值写为
      None,并在函数的文档字符串里面描述该默认值所对应的实际行为。

    第 21 条:用只能以关键词形式指定的参数来确保代码的明晰

    1. 关键字参数能够使函数调用的意图更加明确。

    2. **kwargs 参数:可以接受任意数量的关键字参数。

    第 22 条:尽量用辅助类来维护程序的状态,而不要用字典和元组

    1. 我们很容易就能用 Python 内置的字典与元组类型构建出分层的数据结构,从而保存程序的内部状态。
      但是,当前套多于一层的时候,就应该避免使用这种做法了(例如,不要使用包含字典的字典,不要使用过长的元组)

    2. 如果容器中包含简单而又不可变的数据,可以使用 namedtuple 来表示

    3. 保存内部状态的字典如果变得比较复杂,那就应该把这些代码拆解为多个辅助类。(建议使用)

    第 23 条:简单的接口应该接收函数,而不是类的实例

    简单的接口应该接收函数,类的实例化可以在函数中完成。

    通过 call 特殊方法,可以使类的实例能够像普通的 Python 函数那样得到调用

    第 24 条:以 @classmethod 形式的多态去通用地构建对象

    1. python 中每个类只有一个构造器: init

    2. 通过 @classmethod 机制,用一种与构造器相仿的方式构造类的对象

    3. 通过类方法多态机制,可以以更加通用的方式创建并拼接具体的子类。

    第 25 条:用 super 初始化父类

    1. 直接在子类中调用超类的 init 方法,可能会产生无法预知的行为,问题之一就是一个类继承多个类,全部调用超类的 init 方法,实际调用顺序并不固定。

    2. 钻石行继承体系:如果子类继承自两个单独的超类,而那两个超类有继承自同一个公共基类,那么就构成了钻石行继承体系。

    3.总是应该使用内置的 super 函数来初始化父类。

  • 相关阅读:
    Pytest中参数化之Excel文件实战
    PyCharm 专业版 2018激活(pycharm-professional-2018.2.4.exe)
    python语言编写一个接口测试的例子,涉及切割获取数据,读取表格数据,将结果写入表格中
    webdriver之UI界面下拉框的选择
    git的安装与详细应用
    安装jenkins
    python对数据库mysql的操作(增删改查)
    批量执行测试用例
    面向对象中多态的讲解+工厂设计模式的应用与讲解
    面向对象
  • 原文地址:https://www.cnblogs.com/darksouls/p/8589777.html
Copyright © 2011-2022 走看看