读书笔记:《Effective Python 编写高质量Python代码的59个有效方法》

一、用Pythonic的方式来思考

  1. 确认自己所用的Python版本

  2. 遵循PEP8风格指南

    PEP8风格指南

  3. 了解bytes、str与unicode的区别

    1. Python3中,bytes是包含8位值的序列,str是包含Unicode字符的序列
    2. Python2中,str是包含8位值的序列,unicode是包含Unicode字符的序列
  4. 用辅助函数来取代复杂的表达式

  5. 了解切割序列的办法

    切割操作可以延伸到实现了__getitem__和__setitem__两个特殊方法的类上

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

    既有start和end,又有stride的切割操作,可能会令人费解

    [::-1]反转技巧对字符串和ASCII字符有用

  7. 用列表推导来取代map和filter

    a = [1,2,3,4,5,6,7,8,9,10]
    even_suqares = [x**2 for x in a if x % 2 == 0]
    

    内置的filter和map结合起来,也能达到同样的效果,但是代码会写得非常难懂

    chile_ranks = {'ghost': 1, 'habanero': 2, 'cayenne': 3}
    rank_dict = {rank: name for name, rank in chile_ranks.items()}
    chile_len_set = {len(name) for name in rank_dict_values()}
    >>>
    {1: 'ghost', 2: 'habanero', 3: 'cayenne'}
    {8, 5, 7}
    
  8. 不要使用含有两个以上表达式的列表推导

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

    it = (len(x) for x in open('/tmp/my_file.txt'))
    print(it)
    >>>
    <generator object <genexpr> at 0x101b81480>
    
  10. 尽量用enumerate取代range

    # 啰嗦的语法
    for i in range(len(flavor_list)):
    	flavor = flavor_list[i]
    	print('%d: %s' %(i+1, flavor))
    # 简洁的语法
    for i, flavor in enumerate(flavor_list):
    	print('%d: %s' %(i+1, flavor))
    # 更简洁的语法
    for i, flavor in enumerate(flavor_list, 1):
    	print('%d: %s' %(i, flavor))
    
  11. 用zip函数同时遍历两个迭代器

    names = ['Cecilia', 'Lise', 'Marie']
    letters = [len(n) for n in names]
    max_letters = 0
    for name, count in zip(names,  letters):
    	if count > max_letters:
    		longest_name = name
    		max_letters = count
    
  12. 不要在for和while循环后面写else块

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

二、函数

  1. 尽量用异常来表示特殊情况,而不要返回None

  2. 了解如何在闭包里使用外围作用域中的变量

    Python支持闭包(closure),闭包是一种定义在某个作用域中的函数,这种函数引用了那么作用域里面的变量

    在闭包内用nonlocal语句,可以修改外围 作用域中的同名变量

  3. 考虑用生成器来改写直接返回列表的函数

    代码简洁、节省内存,定义这种生成器函数的时候,唯一需要留意的就是:函数返回的那个迭代器,是有状态的,调用者不应该反复使用它

  4. 在参数上面迭代式,要多加小心

    1. 函数在输入的参数上面多次迭代时要当心:如果参数是迭代器,那么可能会导致奇怪的行为并错失某些值
    2. Python的迭代器协议,描述了容器和迭代器应该如何与iter和next内置函数、for循环及相关表达式互相配合
    3. 把__iter__方法实现为生成器,即可定义自己的容器类型
    4. 想判断某个值是迭代器还是容器,可以拿该值为参数,两次调用iter函数,若结果相同,则是迭代器,调用内置的next函数,即可令该迭代器前进一步
  5. 用数量可变的位置参数减少视觉杂讯

    *args可接受数量可变的位置参数,变长参数传给函数时,总是要先转化为元组

  6. 用关键字参数来表达可选的行为

  7. 用None和文档字符串来描述具有动态默认值的参数

    函数的默认值,只会在程序加载模块并读到本函数的定义时评估一次。对于{}或[]等动态的值,这可能会导致奇怪的行为

  8. 用只能以关键字形式指定的参数来确保代码明晰

三、类与继承

  1. 尽量用辅助类来维护程序的状态,而不要用字典和元组

  2. 简单的接口应该接受函数,而不是类的实例

    三种实现:函数闭包、类、类实现__call__

  3. 以@classmethod形式的多态去通用地构造对象

  4. 用super初始化父类

  5. 只在使用Mix-in组件制作工具类时进行多重继承

    class ToDictMixin(object):
        def to_dict(self):
            return
    
        def _traverse_dict(self, instance_dict):
            output = {}
            for key, value in instance_dict.items():
                output[key] = self._traverse(key, value)
            return output
    
        def _traverse(self, key, value):
            if isinstance(value, ToDictMixin):
                return value.to_dict()
            elif isinstance(value, dict):
                return self._traverse_dict(value)
            elif isinstance(value, list):
                return [self._traverse(key, i) for i in value]
            elif hasattr(value, '__dict__'):
                return self._traverse_dict(value.__dict__)
            else:
                return value
    
    
    class JsonMixin(object):
        @classmethod
        def from_json(cls, data):
            kwargs = data.loads(data)
            return cls(**kwargs)
    
        def to_json(self):
            return json.dumps(self.to_dict())
    
  6. 多用public属性,少用private属性

    1. Python会对私有属性的名称做一些简单的变换,无法严格保证private字段的私密性
    2. 应该多用protected属性,并在文档中把这些字段的合理用法告诉子类的开发者,而不要试图用private属性来限制子类访问这些字段
    3. 只有当子类不受自己控制时,才可以考虑用private属性来避免名称冲突
  7. 继承collections.abc以实现自定义的容器类型

    __getitem__:支持下标

    __len__:支持len()

    可以使用内置的collections.abc模块,该模块定义了一系列抽象基类,它们提供了每一种容器所应具备的常用方法。从这样的基类中继承了子类之后,如果忘记实现某个方法,那么collections.abc模块就会指出这个错误

四、元类及属性

  1. 用纯属性取代get和set方法

    @property@xxx.setter

    class Resistor(object):
        def __init__(self, ohms):
            self._ohms = ohms
            self.voltage = 0
            self.current = 0
    
    
    class FixedResistance(Resistor):
        def __init__(self, ohms):
            super().__init__(ohms)
    
        @property
        def ohms(self):
            return self._ohms
    
        @ohms.setter
        def ohms(self, ohms):
            if hasattr(self, '_ohms'):
                raise AttributeError("Can't set attribute")
            self._ohms = ohms
    
  2. 考虑用@property来代替属性重构

  3. 用描述符来改写需要复用的@property方法

  4. 用__getattr__,__getattribute__和__setattr__实现按需生成的属性

  5. 用元类来验证子类

  6. 用元类来注册子类

  7. 用元类来注解类的属性

五、并发及并行

  1. 用subprocess模块来管理子进程
  2. 可以用线程来执行阻塞式I/O,但不要用它做平行计算
    1. 因为受到全局解释器锁(GIL)的限制,所有多条Python线程不能在多个CPU核心上面平行地执行字节码
    2. 尽管受制于GIL,但是Python的多线程功能依然很有用,它可以轻松地模拟出同一时刻执行多项任务的效果
    3. 通过Python线程,我们可以平行地执行多个系统调用,这使得程序能够在执行阻塞式I/O操作的同时,执行一些运算操作
  3. 在线程中使用Lock来防止数据竞争
  4. 用Queue来协调各线程的工作
  5. 考虑用协程来并发地运行多个函数
  6. 考虑用concurrent.futures来实现真正的平行计算

六、内置模块

  1. 用functools.wraps定义函数修饰器

    内置的functools模块提供了名为wraps的修饰器,开发者在定义自己的修饰器时,应该用wraps对其做一些处理,以避免一些问题

  2. 考虑用contextlib和with语句来改写可复用的try/finally代码

    with open('/tmp/my_output.txt', 'w') as handle:
    	handle.write('This is some data!')
    

    内置的contextlib模块提供了名叫contextmanager的修饰器,开发者只需用它来修饰自己的函数,即可令该函数支持with语句

  3. 用copyreg实现可靠的pickle操作

  4. 应该用datetime模块来处理本地时间,而不是用time模块

  5. 使用内置算法与数据结构

    双向队列、有序字典、带有默认值的字典、堆排列、二分查找、itertools

  6. 在重视精确度的场合,应该使用decimal

  7. 学会安装由Python开发者社区所构建的模块

七、协作开发

  1. 为每个函数、类和模块编写文档字符串

  2. 用包来安排模块,并提供稳固的API

    __all__、__init__.py

  3. 为自编的模块定义根异常,以便将调用者与API相隔离

  4. 用适当的方式打破循环依赖关系

    1. 调整引入顺序
    2. 先引入、再配置、最后运行
    3. 动态引入
  5. 用虚拟环境隔离项目,并重建其依赖关系

八、部署

  1. 考虑用模块级别的代码来配置不同的部署环境

  2. 通过repr字符串来输出调试信息

    针对内置的Python类型来调用repr函数,会根据该值返回一条可供打印的字符串。把这个repr字符串传给内置的eval函数,就可以将其还原为初始的那个值

  3. 用unittest来测试全部代码

  4. 考虑用pdb实现交互调试

  5. 先分析性能,然后再优化

  6. 用tracemalloc来掌握内存的使用及泄漏情况

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值