元数据

Effective Python:编写高质量Python代码的90个有效方法(原书第2版)

  •  Effective Python:编写高质量Python代码的90个有效方法(原书第2版)|200
  • 书名: Effective Python:编写高质量Python代码的90个有效方法(原书第2版)
  • 作者: 布雷特·斯拉特金
  • 简介: 本书可以帮你掌握真正的Pythonic编程方式,令你能够完全发挥出Python语言的强大功能,并写出健壮而高效的代码。Scott Meyers在畅销书《Effective C++》中开创了一种以使用场景为主导的精练教学方式,本书作者Brett Slatkin就以这种方式汇聚了90条实践原则、开发技巧和便捷方案,并以实用的代码范例来解释它们。Slatkin根据自己在Google公司多年开发Python基础架构所积累的经验,揭示Python语言中一些鲜为人知的微妙特性,并给出了能够改善代码功能及运行效率的习惯用法。通过本书,你能够了解到解决关键编程任务所用的奇妙方式,并学会编写易于理解、便于维护且利于改进的代码。
  • 出版时间 2021-01-01 00:00:00
  • ISBN: 9787111674023
  • 分类: 计算机-编程设计
  • 出版社: 机械工业出版社

高亮划线

第3条 了解bytes与str的区别

  • 📌 要把Unicode数据转换成二进制数据,必须调用str的encode方法。要把二进制数据转换成Unicode数据,必须调用bytes的decode方法。 ^39980417-10-1353-1609
    • ⏱ 2022-07-15 06:12:13

第5条 用辅助函数取代复杂的表达式

  • 📌 会把空白字符串、空白list以及0值,全都当成False看待。 ^39980417-12-1448-1569
    • ⏱ 2022-07-15 06:51:49

第7条 尽量用enumerate取代range

  • 📌 enumerate能够把任何一种迭代器(iterator)封装成惰性生成器 ^39980417-14-1880-1924
    • ⏱ 2022-07-15 17:01:39

第11条 学会对序列做切片

  • 📌 如果起点与终点所确定的范围超出了列表的边界,那么系统会自动忽略不存在的元素。 ^39980417-19-2263-2301

    • ⏱ 2022-07-15 17:35:36
  • 📌 切割出来的列表是一份全新的列表。 ^39980417-19-3509-3525

    • ⏱ 2022-07-15 17:36:45
  • 📌 切片可以出现在赋值符号的左侧,表示用右侧那些元素把原列表中位于这个范围之内的元素换掉。 ^39980417-19-3811-3854

    • ⏱ 2022-07-15 17:37:54

第14条 用sort方法的key参数来表示复杂的排序逻辑

  • 📌 如果这些指标不支持一元减操作符,可以多次调用sort方法,并在每次调用时分别指定key函数与reverse参数。最次要的指标放在第一轮处理,然后逐步处理更为重要的指标,首要指标放在最后一轮处理。 ^39980417-22-10854-11086
    • ⏱ 2022-07-15 18:30:01

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

  • 📌 如果当前作用域中不存在这个变量,那么即便外围作用域里有同名的变量,Python也还是会把这次的赋值操作当成变量的定义来处理 ^39980417-30-4359-4420

    • ⏱ 2022-07-16 08:23:31
  • 📌 Python有一种特殊的写法,可以把闭包里面的数据赋给闭包外面的变量。用nonlocal语句描述变量,就可以让系统在处理针对这个变量的赋值操作时,去外围作用域查找。然而,nonlocal有个限制,就是不能侵入模块级别的作用域(以防污染全局作用域)。 ^39980417-30-5627-5848

    • ⏱ 2022-07-16 08:28:26
  • 📌 有一种跟它互补的语句,叫作global,用这种语句描述某个变量后,在给这个变量赋值时,系统会直接把它放到模块作用域(或者说全局作用域)中。 ^39980417-30-6252-6321

    • ⏱ 2022-07-16 08:29:18

第22条 用数量可变的位置参数给函数设计清晰的参数列表

  • 📌 叫作star args,因为我们习惯用*args指代 ^39980417-31-465-568

    • ⏱ 2022-07-16 08:34:34
  • 📌 在Python里,可以给最后一个位置参数加前缀* ^39980417-31-1021-1090

    • ⏱ 2022-07-16 08:40:24
  • 📌 接受*args参数的函数,适合处理输入值不太多,而且数量可以提前预估的情况。 ^39980417-31-2414-2497

    • ⏱ 2022-07-16 08:46:02
  • 📌 在给这种*arg函数添加参数时,应该使用只能通过关键字来指定的参数(keyword-only argument ^39980417-31-3419-3519

    • ⏱ 2022-07-16 08:55:29

第23条 用关键字参数来表示可选的行为

  • 📌 定义函数时,如果想让这个函数接受任意数量的关键字参数,那么可以在参数列表里写上万能形参**kwargs,它会把调用者传进来的参数收集合到一个字典里面稍后处理 ^39980417-32-2810-2933

    • ⏱ 2022-07-21 09:15:42
  • 📌 应该通过带默认值的关键字参数来扩展函数的行为,因为这不会影响原有的函数调用代码。 ^39980417-32-7660-7700

    • ⏱ 2022-07-21 09:19:56
  • 📌 可选的关键字参数总是应该通过参数名来传递,而不应按位置传递。 ^39980417-32-7719-7749

    • ⏱ 2022-07-21 09:20:04

第24条 用None和docstring来描述默认值会变的参数

  • 📌 参数的默认值只会在系统加载这个模块的时候,计算一遍,而不会在每次执行时都重新计算 ^39980417-33-936-976

    • ⏱ 2022-07-21 09:22:00
  • 📌 [插图]这样的写法与前面datetime.now的例子有着同样的问题。系统只会计算一次default参数(在加载这个模块的时候),所以每次调用这个函数时,给调用者返回的都是一开始分配的那个字典 ^39980417-33-2181-2553

    • ⏱ 2022-07-21 09:28:32

第25条 用只能以关键字指定和只能按位置传入的参数来设计清晰的参数列表

  • 📌 在函数的参数列表之中,/符号左侧的参数是只能按位置指定的参数,*符号右侧的参数则是只能按关键字形式指定的参数。 ^39980417-34-6349-6449

    • ⏱ 2022-07-21 09:39:03
  • 📌 这两个符号之间的参数,既可以按位置提供,又可以用关键字形式指定 ^39980417-34-6494-6525

    • ⏱ 2022-07-21 09:42:07

第26条 用functools.wraps定义函数修饰器

  • 📌 在实现这个修饰器时,用*args与**kwargs表示受修饰的原函数func所收到的参数 ^39980417-35-716-895

    • ⏱ 2022-07-21 09:50:44
  • 📌 functools内置模块之中的 ^39980417-35-4221-4282

    • ⏱ 2022-07-21 09:59:45
  • 📌 wraps本身也是个修饰器,它可以帮助你编写自己的修饰器。把它运用到wrapper函数上面,它就会将重要的元数据(metadata)全都从内部函数复制到外部函数。 ^39980417-35-4340-4473

    • ⏱ 2022-07-21 10:00:00

第27条 用列表推导取代map与filter

  • 📌 [插图] ^39980417-37-2741-2742

    • ⏱ 2022-07-21 16:17:45
  • 📌 字典与集合也有相应的推导机制,分别叫作字典推导(dictionary comprehension)与集合推导(set comprehension)。 ^39980417-37-2569-2650

    • ⏱ 2022-07-21 16:18:11

第31条 谨慎地迭代函数所收到的参数

  • 📌 要想让自定义的容器类型可以迭代,只需要把__iter__方法实现为生成器即可。 ^39980417-41-9383-9467

    • ⏱ 2022-07-21 17:02:54
  • 📌 可以把值传给iter函数,检测它返回的是不是那个值本身。如果是,就说明这是个普通的迭代器,而不是一个可以迭代的容器。另外,也可以用内置的isinstance函数判断该值是不是collections.abc.Iterator类的实例。 ^39980417-41-9486-9737

    • ⏱ 2022-07-21 17:03:06

第34条 不要用send给生成器注入数据

  • 📌 如果不通过for循环或内置的next函数推进生成器,而是改用send方法,那么调用方法时传入的参数就会成为上一条yield表达式的值,生成器拿到这个值后,会继续运行到下一条yield表达式那里。可是,刚开始推进生成器的时候,它是从头执行的,而不是从某一条yield表达式那里继续的,所以,首次调用send方法时,只能传None,要是传入其他值,程序运行时就会抛出异常。 ^39980417-44-2201-2745

    • ⏱ 2022-07-21 17:48:13
  • 📌 通过迭代器向组合起来的生成器输入数据,要比采用send方法的那种方案好,所以尽量避免使用send方法。 ^39980417-44-9514-9655

    • ⏱ 2022-07-21 18:02:42

第36条 考虑用itertools拼装迭代器与生成器

  • 📌 islice可以在不拷贝数据的前提下,按照下标切割源迭代器。 ^39980417-46-3543-3580
    • ⏱ 2022-07-21 18:23:59

第38条 让简单的接口接受函数,而不是类的实例

  • 📌 某个类如果定义了__call__特殊方法,那么它的实例就可以像普通的Python函数那样调用。 ^39980417-49-7184-7276

    • ⏱ 2022-07-22 21:52:20
  • 📌 如果想用函数来维护状态,那么可以考虑定义一个带有__call__方法的新类,而不要用有状态的闭包去实现。 ^39980417-49-7295-7392

    • ⏱ 2022-07-22 21:52:31

第39条 通过@classmethod多态来构造同一体系中的各类对象

  • 📌 如果想在超类中用通用的代码构造子类实例,那么可以考虑定义@classmethod方法,并在里面用cls(…)的形式构造具体的子类对象。 ^39980417-50-10069-10228
    • ⏱ 2022-07-22 23:40:00

第40条 通过super初始化超类

  • 📌 super能够确保菱形继承体系中的共同超类只初始化一次 ^39980417-51-4990-5024

    • ⏱ 2022-07-22 23:48:57
  • 📌 method resolution order,MRO ^39980417-51-4923-4950

    • ⏱ 2022-07-22 23:51:30
  • 📌 后按照与刚才相反的顺序,依次执行PlusNineCorrect、TimesSevenCorrect与GoodWay的初始化逻辑 ^39980417-51-6962-7160

    • ⏱ 2022-07-23 00:02:04
  • 📌 只有一种情况需要明确给super指定参数,这就是:我们想从子类里面访问超类对某项功能所做的实现方案,而那种方案可能已经被子类覆盖掉了 ^39980417-51-9678-9789

    • ⏱ 2022-07-23 00:03:03

第41条 考虑用mix-in类来表示可组合的功能

  • 📌 超类最好能写成不带实例属性与__init__方法的mix-in类,以避免由多重继承所引发的一些问题。 ^39980417-52-6876-6971
    • ⏱ 2022-07-23 00:19:48

第42条 优先考虑用public属性表示应受保护的数据,不要用private属性表示

  • 📌 如果属性名以两个下划线开头,那么即为private字段。 ^39980417-53-999-1027

    • ⏱ 2022-07-23 00:21:07
  • 📌 Python类的属性只有两种访问级别,也就是public与private。 ^39980417-53-411-448

    • ⏱ 2022-07-23 00:22:50
  • 📌 以单下划线开头的字段(例如_protected_field),习惯上叫作受保护的(protected)字段,表示这个类以外的用户在使用这种字段时必须慎重。 ^39980417-53-4171-4293

    • ⏱ 2022-07-23 00:32:18
  • 📌 这种防止其他类访问private属性的功能,其实仅仅是通过变换属性名称而实现的。当Python编译器看到MyChildObject.get_private_field这样的方法想要访问__private_field属性时,它会把下划线和类名加在这个属性名称的前面,所以代码实际上访问的是_MyChildObject__private_field。在上面的例子中,__private_field是在MyParentObject的__init__里面定义的,所以,它变换之后的真实名称是_MyParentObject__private_field。子类不能通过__private_field来访问这个属性,因为这样写实际上是在访问不存的_MyChildObject__private_field,而不是_MyParentObject__private_field。了解名称变换规则后,我们就可以从任何一个类里面访问private属性。无论是子类还是外部的类,都可以不经许可就访问到这些属性。 ^39980417-53-2265-3171

    • ⏱ 2022-07-23 00:42:03

第43条 自定义的容器类型应该从collections.abc继承

  • 📌 为了让BinaryNode类能像序列那样使用,我们可以定义__getitem__方法(一般叫作dunder getitem,其中的dunder为double underscore(双下划线)的简称)。 ^39980417-54-2966-3156

    • ⏱ 2022-07-23 00:47:41
  • 📌 为了方便大家定制容器,Python内置的collections.abc模块定义了一系列抽象基类(abstract base class),把每种容器类型应该提供的所有常用方法都写了出来。我们只需要从这样的抽象基类里面继承就好。 ^39980417-54-5463-5621

    • ⏱ 2022-07-23 01:10:04

第44条 用纯属性与修饰器取代旧式的setter与getter方法

  • 📌 将来如果想在设置属性时,实现特别的功能,那么可以先通过@property修饰器来封装获取属性的那个方法,并在封装出来的修饰器上面通过setter属性来封装设置属性的那个方法 ^39980417-56-2051-2227
    • ⏱ 2022-07-23 01:19:35

第46条 用描述符来改写需要复用的@property方法

  • 📌 描述符协议(descriptor protocol)规定了程序应该如何处理属性访问操作。 ^39980417-58-2172-2216

    • ⏱ 2022-07-23 03:00:45
  • 📌 如果要给Exam实例的writing_grade属性赋值:[插图]那么Python会把这次赋值操作转译为:[插图]获取这个属性时也一样:[插图]Python会把它相应地转译为:[插图]这样的转译效果是由object的__getattribute__方法促成的(参见第47条)。简单地说,就是当Exam实例里面没有名为writing_grade的属性时,Python会转而在类的层面查找,查询Exam类里面有没有这样一个属性。如果有,而且还是个实现了__get__与__set__方法的对象,那么系统就认定你想通过描述符协议定义这个属性的访问行为。 ^39980417-58-3121-4771

    • ⏱ 2022-07-23 08:04:38

第47条 针对惰性属性使用__getattr__、getattribute__及__setattr

  • 📌 只要给实例中的属性赋值(不论是直接赋值,还是通过内置的setattr函数赋值),系统就触发__setattr__方法。 ^39980417-59-6581-6730

    • ⏱ 2022-07-23 08:38:08
  • 📌 __getattr__只会在属性缺失时触发,而__getattribute__则在每次访问属性时都要触发。 ^39980417-59-9836-9941

    • ⏱ 2022-07-23 08:41:53
  • 📌 在实现__getattribute__与__setattr__的过程中,如果要使用本对象的普通属性,那么应该通过super()(也就是object类)来使用,而不要直接使用,以避免无限递归。 ^39980417-59-9960-10235

    • ⏱ 2022-07-23 08:42:12

第48条 用__init_subclass__验证子类写得是否正确

  • 📌 元类应该从type之中继承。 ^39980417-60-816-875

    • ⏱ 2022-07-23 08:48:58
  • 📌 每个类只能定义一个元类 ^39980417-60-4346-4357

    • ⏱ 2022-07-23 09:06:34
  • 📌 如果某个类是根据元类所定义的,那么当系统把该类的class语句体全部处理完之后,就会将这个类的写法告诉元类的__new__方法。 ^39980417-60-10182-10336

    • ⏱ 2022-07-23 09:14:22
  • 📌 __init_subclass__能够用来检查子类定义得是否合理,如果不合理,那么可以提前报错,让程序无法创建出这种子类的对象。 ^39980417-60-10461-10532

    • ⏱ 2022-07-23 09:15:20

第49条 用__init_subclass__记录现有的子类

  • 📌 先考虑通过__init_subclass__实现自动注册,而不要用标准的元类机制来实现,因为__init_subclass__更清晰,更便于初学者理解。 ^39980417-61-6917-7083
    • ⏱ 2022-07-23 10:55:01

第50条 用__set_name__给类属性加注解

  • 📌 元类可以当作class语句的挂钩,只要class语句体定义完毕,元类就会看到它的写法并尽快做出应对 ^39980417-62-3144-3290

    • ⏱ 2022-07-23 11:03:57
  • 📌 你可以给描述符定义__set_name__方法,让系统把使用这个描述符做属性的那个类以及它在类里的属性名通过方法的参数告诉你。 ^39980417-62-7039-7147

    • ⏱ 2022-07-23 11:48:13
  • 📌 [插图] ^39980417-62-6211-6212

    • ⏱ 2022-07-23 11:50:08
  • 📌 用描述符直接操纵每个实例的属性字典,要比把所有实例的属性都放到一份字典里更好,因为后者要求我们必须使用weakref内置模块之中的特殊字典来记录每个实例的属性值以防止内存泄漏。 ^39980417-62-7166-7299

    • ⏱ 2022-07-23 11:52:22

第51条 优先考虑通过类修饰器来提供可组合的扩充功能,不要使用元类

  • 📌 用类修饰器(class decorator) ^39980417-63-4237-4259
    • ⏱ 2022-07-23 12:03:10

第52条 用subprocess管理子进程

  • 📌 请注意,前面那批进程(也就是上游进程)的stdout实例必须谨慎地处理,用它把相应的哈希进程(也就是下游进程)启动之后,就应该及时关闭(close)并将其设为None。 ^39980417-65-4858-5077
    • ⏱ 2022-07-22 04:23:35

第53条 可以用线程执行阻塞式I/O,但不要用它做并行计算

  • 📌 尽管Python也支持多线程,但这些线程受GIL约束,所以每次或许只能有一条线程向前推进,而无法实现多头并进。 ^39980417-66-1048-1103

    • ⏱ 2022-07-22 10:03:41
  • 📌 GIL实际上就是一种互斥锁(mutual-exclusion lock,mutex),用来防止CPython的状态在抢占式的多线程环境(preemptive multithreading)之中受到干扰 ^39980417-66-716-816

    • ⏱ 2022-07-22 10:04:34
  • 📌 么解释器的状态(例如为垃圾回收工作而设立的引用计数等) ^39980417-66-867-894

    • ⏱ 2022-07-22 10:05:35
  • 📌 用多线程处理阻塞式I/O ^39980417-66-5516-5528

    • ⏱ 2022-07-22 10:17:30

第59条 如果必须用线程做并发,那就考虑通过ThreadPoolExecutor实现

  • 📌 [插图] ^39980417-72-1349-1350

    • ⏱ 2022-07-22 12:13:35
  • 📌 利用ThreadPoolExecutor,我们只需要稍微调整一下代码,就能够并行地执行简单的I/O操作,这种方案省去了每次fan-out(分派)任务时启动线程的那些开销。 ^39980417-72-3595-3725

    • ⏱ 2022-07-22 12:14:03

第60条 用协程实现高并发的I/O

  • 📌 协程与线程的区别在于,它不会把这个函数从头到尾执行完,而是每遇到一个await表达式,就暂停一次,下次继续执行的时候,它会先等待await所针对的那项awaitable操作有了结果(那项操作是用async函数表示的),然后再推进到下一个await表达式那里(这跟生成器函数的运作方式有点像,那种函数也是一遇到yield就暂停)。 ^39980417-73-989-1378

    • ⏱ 2022-07-22 12:13:17
  • 📌 通过事件循环(event loop)打造的 ^39980417-73-1555-1576

    • ⏱ 2022-07-22 12:15:36
  • 📌 调用step_cell的时候,系统并不会立刻执行这个函数,而是会返回一个协程实例 ^39980417-73-3493-3578

    • ⏱ 2022-07-22 12:25:01
  • 📌 这样就可以实现任务fan-out(分派)模式 ^39980417-73-3791-3813

    • ⏱ 2022-07-22 12:25:10
  • 📌 内置的asyncio模块提供了gather函数,可以用来实现fan-in(归集)模式。 ^39980417-73-3835-3968

    • ⏱ 2022-07-22 12:25:44
  • 📌 把simulate(grid)这个协程交给asyncio.run去运行 ^39980417-73-4479-4604

    • ⏱ 2022-07-22 12:27:15

第64条 考虑用concurrent.futures实现真正的并行计算

  • 📌 Python内置的multiprocessing模块提供了多进程机制,这种机制很容易通过内置的concurrent.futures模块来使用 ^39980417-77-1589-1749

    • ⏱ 2022-07-22 18:43:43
  • 📌 把ThreadPoolExecutor改成concurrent.futures模块里的ProcessPoolExecutor ^39980417-77-3241-3438

    • ⏱ 2022-07-22 18:47:12

第66条 考虑用contextlib和with语句来改写可复用的try/finally代码

  • 📌 用@contextmanager来修饰下面这个辅助函数 ^39980417-80-3162-3234

    • ⏱ 2022-07-23 14:34:53
  • 📌 系统开始执行with语句时,会先把@contextmanager所修饰的debug_logging辅助函数推进到yield表达式所在的地方(参见第30条),然后开始执行with结构的主体部分。 ^39980417-80-3736-4057

    • ⏱ 2022-07-23 14:35:39
  • 📌 [插图] ^39980417-80-7361-7362

    • ⏱ 2022-07-23 14:43:27
  • 📌 如果你想让自己的函数也支持with…as…结构,那么只需要像刚才那样,用@context-manager修饰它,并记得在yield语句中返回一个值以赋给as右侧的那个变量。 ^39980417-80-6600-6870

    • ⏱ 2022-07-23 14:43:45
  • 📌 Python内置的contextlib模块提供了contextmanager修饰器,让我们可以很方便地修饰某个函数,从而制作出相对应的情境管理器,使得这个函数能够运用在with语句里面。 ^39980417-80-10390-10618

    • ⏱ 2022-07-23 14:44:48
  • 📌 [插图] ^39980417-80-3546-3547

    • ⏱ 2022-07-23 14:45:29
  • 📌 情境管理器通过yield语句所产生的值,可以由with语句之中位于as右侧的那个变量所接收,这样的话,我们就可以通过该变量与当前情境相交互了。 ^39980417-80-10637-10843

    • ⏱ 2022-07-23 14:47:32

第67条 用datetime模块处理本地时间,不要用time模块

  • 📌 把Python内置的datetime模块与开发者社群提供的pytz模块结合起来,可以在不同时区之间可靠地转换。 ^39980417-81-7463-7608
    • ⏱ 2022-07-23 15:04:39

第68条 用copyreg实现可靠的pickle操作

  • 📌 把内置的copyreg模块与pickle模块搭配起来使用,可以让新版的程序兼容旧版的序列化数据。 ^39980417-82-15204-15342
    • ⏱ 2022-07-23 15:12:24

第69条 在需要准确计算的场合,用decimal表示相应的数值

  • 📌 如果你想要的是准确答案,那么应该使用str字符串来构造Decimal ^39980417-83-3036-3160
    • ⏱ 2022-07-23 15:51:45

第71条 优先考虑用deque实现生产者-消费者队列

  • 📌 跟list不同,内置collections模块之中的deque类,无论是通过append添加元素,还是通过popleft获取元素,所花的时间都只跟队列长度呈线性关系,而非平方关系,这使得它非常适合于FIFT队列。 ^39980417-85-9268-9599
    • ⏱ 2022-07-23 16:24:17

第72条 考虑用bisect搜索已排序的序列

  • 📌 bisect_left函数,能够迅速地对任何一个有序的序列执行二分搜索 ^39980417-86-1407-1456
    • ⏱ 2022-07-23 16:23:20

第73条 学会使用heapq制作优先级队列

  • 📌 改用heappush函数来添加这个元素 ^39980417-87-5836-5900

    • ⏱ 2022-07-23 16:33:26
  • 📌 要使用heapq模块,我们必须让元素所在的类型支持自然排序,这可以通过对类套用@functools.total_ordering修饰器并定义__lt__方法来实现。 ^39980417-87-12867-13084

    • ⏱ 2022-07-23 16:42:22

第74条 考虑用memoryview与bytearray来实现无须拷贝的bytes操作

  • 📌 bytes有个限制,就是只能读取不能修改,我们不能单独更新其中某个位置上的字节。 ^39980417-88-6097-6144

    • ⏱ 2022-07-23 16:53:00
  • 📌 而bytearray则相当于可修改的bytes,它允许我们修改任意位置上面的内容。 ^39980417-88-6392-6561

    • ⏱ 2022-07-23 16:54:15
  • 📌 可以通过memoryview来修改它所封装的bytearray ^39980417-88-7288-7409

    • ⏱ 2022-07-23 16:55:54
  • 📌 memoryview最大的优点,是能够在不复制底层数据的前提下,制作出另一个memoryview。 ^39980417-88-3003-3104

    • ⏱ 2022-07-23 17:00:50

第75条 通过repr字符串输出调试信息

  • 📌 把repr返回的值传给内置的eval函数,应该会得到一个跟原来相同的Python对象 ^39980417-90-3191-3323

    • ⏱ 2022-07-23 17:04:41
  • 📌 如果这个类不受你控制,那么可以用另一种办法,也就是把对象的实例字典传给print函数,这个字典指实例的__dict__属性。 ^39980417-90-6191-6343

    • ⏱ 2022-07-23 17:09:22
  • 📌 如果这个类受你控制,那么就自己定义__repr__特殊方法,并返回一个包含Python表达式的字符串,让这个字符串能够用来重建该对象。 ^39980417-90-5444-5556

    • ⏱ 2022-07-23 17:10:02

第78条 用Mock来模拟受测代码所依赖的复杂函数

  • 📌 fake是原对象的简化版,mock是原对象的模拟版 ^39980417-93-15555-15580

    • ⏱ 2022-07-23 17:27:38
  • 📌 fake跟mock不同,fake具备DatabaseConnection类的绝大多数行为,只不过是用比较简单的办法来实现这些行为的 ^39980417-93-1399-1509

    • ⏱ 2022-07-23 17:28:08
  • 📌 我们通过Mock类来创建mock以仿制get_animals函数。把mock应该返回的数据(也就是expected所表示的数据)赋给return_value属性,这样的话,如果把mock当作get_animals来用,那它就会像真正的get_animals一样返回这份数据。 ^39980417-93-2140-2720

    • ⏱ 2022-07-23 17:37:50
  • 📌 用unittest.mock.patch系列的函数来注入mock逻辑 ^39980417-93-10367-10491

    • ⏱ 2022-07-23 17:56:55
  • 📌 都是DEFAULT,表示需要系统针对这三个名称分别创建标准的Mock实例 ^39980417-93-14346-14472

    • ⏱ 2022-07-23 18:04:29

第80条 考虑用pdb做交互调试

  • 📌 用来触发调试器的指令就是Python内置的breakpoint函数。 ^39980417-95-862-986

    • ⏱ 2022-07-23 18:33:50
  • 📌 where:打印出当前的执行调用栈 ^39980417-95-2328-2352

    • ⏱ 2022-07-23 18:35:32
  • 📌 up:把观察点沿着执行调用栈上移一层,回到当前函数调用者处 ^39980417-95-2525-2561

    • ⏱ 2022-07-23 18:35:46
  • 📌 down:把观察点沿着执行调用栈下移一层 ^39980417-95-2645-2672

    • ⏱ 2022-07-23 18:35:57
  • 📌 pdb模块还能够在程序出现错误的时候检查该程序的状态,这可以通过python -m pdb -c continue 命令实现,也可以先在普通的Python解释器界面运行受测程序,等到出现问题,再用import pdb; pdb.pm()切换至调试界面。 ^39980417-95-6657-6900

    • ⏱ 2022-07-23 18:45:56

第83条 用虚拟环境隔离项目,并重建依赖关系

  • 📌 [插图] ^39980417-99-5380-5381

    • ⏱ 2022-07-23 20:33:51
  • 📌 用版本控制系统(revision control system)与他人协作时,这样一份requirements.txt文件是很有用处的 ^39980417-99-11552-11664

    • ⏱ 2022-07-23 20:38:57
  • 📌 python3 -m venv命令可以创建虚拟环境,source bin/activate与deactivate命令分别可以启用与禁用该环境。 ^39980417-99-12546-12714

    • ⏱ 2022-07-23 20:40:05
  • 📌 python3 -m pip freeze > requirements.txt命令可以把当前环境所依赖的软件包保存到文件之中,之后可以通过python3 -m pip install -r requirements.txt在另一套环境里面重新安装这些包。 ^39980417-99-12771-12954

    • ⏱ 2022-07-23 20:40:19

第85条 用包来安排模块,以提供稳固的API

  • 📌 把名为__init__.py的空白文件放在某个目录中,即可令该目录成为一个包。 ^39980417-101-605-689

    • ⏱ 2022-07-23 20:55:33
  • 📌 Python允许我们通过__all__这个特殊的属性,决定模块或包里面有哪些内容应该当作API公布给外界。 ^39980417-101-4928-5064

    • ⏱ 2022-07-23 21:03:07
  • 📌 如果执行from foo import *这样的语句,那么只有__all__所列出名称的属性才会引入进来。若是foo里面没有__all__,那么就只会引入public属性(也就是只会引入不以下划线开头的那些属性。 ^39980417-101-5113-5399

    • ⏱ 2022-07-23 21:03:48

第87条 为自编的模块定义根异常,让调用者能够专门处理与此API有关的异常

  • 📌 API用户在处理完API所属模块有可能抛出的具体异常后,可以写一个针对模块根异常的except块,如果程序进入这个块,那就说明他使用API的方式可能有问题,例如可能忘记处理某种本来应该处理的具体异常。 ^39980417-103-6280-6425

    • ⏱ 2022-07-23 21:26:03
  • 📌 API用户还可以再写一个except块以捕获整个Python体系之中的根异常,如果程序进入了那个块,那说明所调用的API可能实现得有问题。 ^39980417-103-6444-6558

    • ⏱ 2022-07-23 21:26:13
  • 📌 在模块的根异常下,可以设立几个门类,让具体的异常不要直接继承总的根异常,而是继承各自门类中那个分根异常,这样的话,使用这个模块的开发者,就可以只关注这几个门类,即便你修改了某个门类之下的具体异常,也不会影响到他们已经写好的那些代码。 ^39980417-103-6577-6693

    • ⏱ 2022-07-23 21:27:03

第88条 用适当的方式打破循环依赖关系

  • 📌 我们可以把本模块里面,需要用到其他模块的那种操作放在configure函数中 ^39980417-104-6483-6566

    • ⏱ 2022-07-23 21:57:48
  • 📌 把import语句从模块级别下移到函数或方法里面,这样就可以解除循环依赖关系了。 ^39980417-104-8386-8478

    • ⏱ 2022-07-23 22:03:07

第89条 重构时考虑通过warnings提醒开发者API已经发生变化

  • 📌 在命令行界面执行Python解释器的时候,可以开启-W error选项,从而将警告视为错误。这在执行自动测试的过程中特别有用,因为这样可以及时发现受测程序所依赖的API是否已经推出了新的版本。 ^39980417-105-8974-9115
    • ⏱ 2022-07-23 22:22:45

第90条 考虑通过typing做静态分析,以消除bug

  • 📌 通过from future import annotations来引入类型注解功能 ^39980417-106-9417-9507

    • ⏱ 2022-07-23 22:45:19
  • 📌 会让Python系统在运行程序的时候,完全忽略类型注解里面提到的值,于是就解决了提前引用的问题 ^39980417-106-9557-9604

    • ⏱ 2022-07-23 22:45:45
  • 📌 我们通常应该先把代码本身写出来,然后编写测试,最后才考虑在必要的地方添加类型信息。 ^39980417-106-10012-10053

    • ⏱ 2022-07-23 22:46:16
  • 📌 类型提示信息最能发挥作用的地方,是在项目与项目衔接处。 ^39980417-106-10070-10097

    • ⏱ 2022-07-23 22:46:37
  • 📌 如果有些代码比较复杂,或者特别容易出错,那么即便不属于API,也仍然值得添加类型提示信息。 ^39980417-106-10243-10288

    • ⏱ 2022-07-23 22:47:36

读书笔记

本书评论