打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
一名神级程序员花了半个月:把这本书的全部重点都整理好了!超长

起步

这本书有21个章节, 整理也是根据这些章节过来.

第一章: python数据模型

第二章: 序列构成的数组

这部分主要是介绍序列, 着重介绍数组和元组的一些高级用法.

序列按照容纳数据的类型可以分为:

  • 容器序列 : list、tuple 和 collections.deque 这些序列能存放不同类型的数据

  • 扁平序列 : str、bytes、bytearray、memoryview 和 array.array,这类序列只能容纳一种类型.

如果按照是否能被修改可以分为:

  • 可变序列 : list、bytearray、array.array、collections.deque 和 memoryview

  • 不可变序列 : tuple、str 和 bytes

列表推导

列表推导是构建列表的快捷方式, 可读性更好且效率更高.

例如, 把一个字符串变成unicode的码位列表的例子, 一般:

第三章: 字典和集合

defaultdict:处理找不到的键的一个选择

当某个键不在映射里, 我们也希望也能得到一个默认值. 这就是 defaultdict , 它是 dict 的子类, 并实现了 __missing__ 方法.

不可变映射类型

说到不可变, 第一想到的肯定是元组, 但是对于字典来说, 要将key和value的对应关系变成不可变, types 模块的 MappingPrype 可以做到:

第四章: 文本和字节序列

本章讨论了文本字符串和字节序列, 以及一些编码上的转换. 本章讨论的 str指的是python3下的.

字符问题

如果字符序列和预期不符, 在进行解码或编码时容易抛出 Unicode*Error的异常. 造成这种错误是因为目标编码中没有定义某个字符(没有定义某个码位对应的字符), 这里说说解决这类问题的方式.

unicode文本排序

对于字符串来说, 比较的码位. 所以在非 ascii 字符时, 得到的结果可能会不尽人意.

第五章: 一等函数

高阶函数

高阶函数就是接受函数作为参数, 或者把函数作为返回结果的函数. 如 map , filter , reduce等.

第六章: 使用一等函数实现设计模式

虽然设计模式与语言无关, 但这并不意味着每一个模式都能在每一个语言中使用. Gamma 等人合著的 《设计模式:可复用面向对象软件的基础》 一书中有 23 个模式, 其中有 16
个在动态语言中'不见了, 或者简化了'.

这里不举例设计模式, 因为书里的模式不常用.

第七章: 函数装饰器和闭包

函数装饰器用于在源码中“标记”函数,以某种方式增强函数的行为。这是一项强大的功 能,但是若想掌握,必须理解闭包。

修饰器和闭包经常在一起讨论, 因为修饰器就是闭包的一种形式. 闭包还是回调式异步编程和函数式编程风格的基础.

装饰器基础知识

装饰器是可调用的对象, 其参数是另一个函数(被装饰的函数). 装饰器可能会处理被 装饰的函数, 然后把它返回, 或者将其替换成另一个函数或可调用对象.

闭包

闭包其实挺好理解的, 当匿名函数出现的时候, 才使得这部分难以掌握. 简单简短的解释闭包就是:

名字空间与函数捆绑后的结果被称为一个闭包(closure).

如果两个变量都是指向同一个对象, 我们通常会说变量是另一个变量的 别名 .

在==和is之间选择运算符 == 是用来判断两个对象值是否相等(注意是对象值). 而 is 则是用于判断两个变量是否指向同一个对象, 或者说判断变量是不是两一个的别名, is 并不关心对象的值. 从使用上, == 使用比较多, 而 is 的执行速度比较快.

函数的参数做引用时

python中的函数参数都是采用共享传参. 共享传参指函数的各个形式参数获得实参中各个引用的副本. 也就是说, 函数内部的形参 是实参的别名.

这种方案就是当传入参数是可变对象时, 在函数内对参数的修改也就是对外部可变对象进行修改. 但这种参数试图重新赋值为一个新的对象时则无效, 因为这只是相当于把参数作为另一个东西的引用, 原有的对象并不变. 也就是说, 在函数内, 参数是不能把一个对象替换成另一个对象的.

不要使用可变类型作为参数的默认值

参数默认值是个很棒的特性. 对于开发者来说, 应该避免使用可变对象作为参数默认值. 因为如果参数默认值是可变对象, 而且修改了它的内容, 那么后续的函数调用上都会收到影响.

del和垃圾回收

在python中, 当一个对象失去了最后一个引用时, 会当做垃圾, 然后被回收掉. 虽然python提供了 del 语句用来删除变量. 但实际上只是删除了变量和对象之间的引用, 并不一定能让对象进行回收, 因为这个对象可能还存在其他引用.

在CPython中, 垃圾回收主要用的是引用计数的算法. 每个对象都会统计有多少引用指向自己. 当引用计数归零时, 意味着这个对象没有在使用, 对象就会被立即销毁.

符合Python风格的对象

得益于 Python 数据模型,自定义类型的行为可以像内置类型那样自然。实现如此自然的 行为,靠的不是继承,而是鸭子类型(duck typing):我们只需按照预定行为实现对象所 需的方法即可。

对象表示形式

每门面向对象的语言至少都有一种获取对象的字符串表示形式的标准方式。Python 提供了 两种方式。

Python中的把使用一个下划线前缀标记的属性称为'受保护的'属性

使用slots类属性节省空间

第十一章: 接口:从协议到抽象基类

这些协议定义为非正式的接口, 是让编程语言实现多态的方式. 在python中, 没有 interface 关键字, 而且除了抽象基类, 每个类都有接口: 所有类都可以自行实现 __getitem____add__ .

有写规定则是程序员在开发过程中慢慢总结出来的, 如受保护的属性命名采用单个前导下划线, 还有一些编码规范之类的.

协议是接口, 但不是正式的, 这些规定并不是强制性的, 一个类可能只实现部分接口, 这是允许的.

既然有非正式的协议, 那么有没有正式的协议呢? 有, 抽象基类就是一种强制性的协议.

抽象基类要求其子类需要实现定义的某个接口, 且抽象基类不能实例化.

Python文化中的接口和协议

引入抽象基类之前, python就已经非常成功了, 即使现在也很少使用抽象基类. 通过鸭子类型和协议, 我们把协议定义为非正式接口, 是让python实现多态的方式.

另一边面, 不要觉得把公开数据属性放入对象的接口中不妥, 如果需要, 总能实现读值和设值方法, 把数据属性变成特性. 对象公开方法的自己, 让对象在系统中扮演特定的角色. 因此, 接口是实现特定角色的方法集合.

序列协议是python最基础的协议之一, 即便对象只实现那个协议最基本的一部分, 解释器也会负责地处理.

水禽和抽象基类

鸭子类型在很多情况下十分有用, 但是随着发展, 通常由了更好的方式.

近代, 属和种基本是根据表型系统学分类的, 鸭科属于水禽, 而水禽还包括鹅, 鸿雁等. 水禽是对某一类表现一致进行的分类, 他们有一些统一'描述'部分.

因此, 根据分类的演化, 需要有个水禽类型, 只要 cls 是抽象基类, 即 cls 的元类是 abc.ABCMeta , 就可以使用 isinstance(obj, cls) 来进行判断.

与具类相比, 抽象基类有很多理论上的优点, 被注册的类必须满足抽象基类对方法和签名的要求, 更重要的是满足底层语义契约.

标准库中的抽象基类

大多数的标准库的抽象基类在 collections.abc 模块中定义. 少部分在 numbersio 包中有一些抽象基类. 标准库中有两个 abc 模块, 这里只讨论 collections.abc .

这个模块中定义了 16 个抽象基类.

Iterable、Container 和 Sized各个集合应该继承这三个抽象基类,或者至少实现兼容的协议。 Iterable 通过 __iter__ 方法支持迭代,Container 通过 __contains__ 方法支持 in 运算符,Sized 通过 __len__ 方法支持 len() 函数。

Sequence、Mapping 和 Set这三个是主要的不可变集合类型,而且各自都有可变的子类。

MappingView在 Python3 中,映射方法 .items().keys().values() 返回的对象分别是 ItemsView、KeysView 和 ValuesView 的实例。前两个类还从 Set 类继承了丰富的接 口。

Callable 和 Hashable这两个抽象基类与集合没有太大的关系,只不过因为 collections.abc是标准库中 定义抽象基类的第一个模块,而它们又太重要了,因此才把它们放到 collections.abc 模块中。我从未见过 CallableHashable 的子类。这两个抽象基类的主要作用是为内 置函数 isinstance 提供支持,以一种安全的方式判断对象能不能调用或散列。

Iterator注意它是 Iterable 的子类。

第十二章: 继承的优缺点

很多人觉得多重继承得不偿失, 那些不支持多继承的编程语言好像也没什么损失.

子类化内置类型很麻烦

python2.2 以前, 内置类型(如list, dict)是不能子类化的. 它们是不能被其他类所继承的, 原因是内置类型是C语言实现的, 不会调用用户定义的类覆盖的方法.

至于内置类型的子类覆盖的方法会不会隐式调用, CPython 官方也没有制定规则. 基本上, 内置类型的方法不会调用子类覆盖的方法. 例如, dict 的子类覆盖的 __getitem__ 方法不会覆盖内置类型的 get() 方法调用.

多重继承和方法解析顺序

任何实现多重继承的语言都要处理潜在的命名冲突,这种冲突由不相关的祖先类实现同名 方法引起。这种冲突称为“菱形问题”,如图.

Python 会按照特定的顺序遍历继承 图。这个顺序叫方法解析顺序(Method Resolution Order,MRO)。类都有一个名为 mro 的属性,它的值是一个元组,按照方法解析顺序列出各个超类,从当前类一直 向上,直到 object 类。

第十三章: 正确重载运算符

在python中, 大多数的运算符是可以重载的, 如 == 对应了 __eq__ , + 对应 __add__ .

某些运算符不能重载, 如 is, and, or, and .

第十四章: 可迭代的对象、迭代器和生成器

迭代是数据处理的基石. 扫描内存中放不下的数据集时, 我们要找到一种惰性获取数据的方式, 即按需一次获取一个数据. 这就是 迭代器模式 .

python中有 yield 关键字, 用于构建 生成器(generator) , 其作用用于迭代器一样.

所有的生成器都是迭代器, 因为生成器完全实现了迭代器的接口.

检查对象 x 是否迭代, 最准确的方法是调用 iter(x) , 如果不可迭代, 则抛出 TypeError 异常. 这个方法比 isinstance(x, abc.Iterable) 更准确, 因为它还考虑到遗留的 __getitem__ 方法.

可迭代的对象与迭代器的对比

我们需要对可迭代的对象进行一下定义:

使用 iter 内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的 iter 方法,那么对象就是可迭代的。序列都可以迭代;实现了 getitem 方 法,而且其参数是从零开始的索引,这种对象也可以迭代。

我们要明确可迭代对象和迭代器之间的关系: 从可迭代的对象中获取迭代器.

标准的迭代器接口有两个方法:

  • __next__ : 返回下一个可用的元素, 如果没有元素了, 抛出 StopIteration 异常.

  • __iter__ : 返回 self , 以便咋应该使用可迭代对象的地方使用迭代器.

典型的迭代器

为了清楚地说明可迭代对象与迭代器之间的重要区别, 我们将两者分开, 写成两个类:

这个例子主要是为了区分可迭代对象和迭代器, 这种情况工作量一般比较大, 程序员也不愿这样写.

生成器表达式

生成器表达式可以理解为列表推导的惰性版本: 不会迫切地构建列表, 而是返回一个生成器, 按需惰性生成元素. 也就是, 如果列表推导是产出列表的工厂, 那么生成器表达式就是产出生成器的工厂.

标准库中的生成器函数

用于映射的生成器函数

模块函数说明
itertoolsaccumulate(it, [func])产出累积的总和;如果提供了 func,那么把前两个元素传给它,然后把计算结果和下一个元素传给它,以此类推,最后产出结果
(内置)enumerate(iterable, start=0)产出由两个元素组成的元组,结构是 (index, item),其中 index 从 start 开始计数,item 则从 iterable 中获取
(内置)map(func, it1, [it2, ..., itN])把 it 中的各个元素传给func,产出结果;如果传入 N 个可迭代的对象,那么 func 必须能接受 N 个参数,而且要并行处理各个可迭代的对象

合并多个可迭代对象的生成器函数

模块函数说明
itertoolschain(it1, ..., itN)先产出 it1 中的所有元素,然后产出 it2 中的所有元素,以此类推,无缝连接在一起
itertoolschain.from_iterable(it)产出 it 生成的各个可迭代对象中的元素,一个接一个,无缝连接在一起;it 应该产出可迭代的元素,例如可迭代的对象列表
(内置)zip(it1, ..., itN)并行从输入的各个可迭代对象中获取元素,产出由 N 个元素组成的元组,只要有一个可迭代的对象到头了,就默默地停止

新的句法:yield from

如果生成器函数需要产出另一个生成器生成的值, 传统的方式是嵌套的 for 循环, 例如, 我们要自己实现 chain生成器:

可迭代的归约函数

有些函数接受可迭代对象, 但仅返回单个结果, 这类函数叫规约函数.

模块函数说明
(内置)sum(it, start=0)it 中所有元素的总和,如果提供可选的 start,会把它加上(计算浮点数的加法时,可以使用 math.fsum 函数提高精度)
(内置)all(it)it 中的所有元素都为真值时返回 True,否则返回 False;all([]) 返回 True
(内置)any(it)只要 it 中有元素为真值就返回 True,否则返回 False;any([]) 返回 False
(内置)max(it, [key=,] [default=])返回 it 中值最大的元素;*key 是排序函数,与 sorted 函数中的一样;如果可迭代的对象为空,返回 default
functoolsreduce(func, it, [initial])把前两个元素传给 func,然后把计算结果和第三个元素传给 func,以此类推,返回最后的结果;如果提供了 initial,把它当作第一个元素传入

第十五章: 上下文管理器和 else 块

本章讨论的是其他语言不常见的流程控制特性, 正因如此, python新手往往忽视或没有充分使用这些特性. 下面讨论的特性有:

  • with 语句和上下文管理器

  • for while try 语句的 else 子句

上下文管理器和with块

使用@contextmanager

第十六章: 协程

预激协程的装饰器

协程内部如果不能处理这个异常, 就会导致协程终止.

第十七章: 使用期物处理并发

第十八章: 使用 asyncio 包处理并发

并发是指一次处理多件事。 并行是指一次做多件事。 二者不同,但是有联系。 一个关于结构,一个关于执行。 并发用于制定方案,用来解决可能(但未必)并行的问题。—— Rob Pike Go 语言的创造者之一

并行是指两个或者多个事件在同一时刻发生, 而并发是指两个或多个事件在同一时间间隔发生. 真正运行并行需要多个核心, 现在笔记本一般有 4 个 CPU 核心, 但是通常就有超过 100 个进程同时运行. 因此, 实际上大多数进程都是并发处理的, 而不是并行处理. 计算机始终运行着 100 多个进程, 确保每个进程都有机会取得发展, 不过 CPU 本身同时做的事情不会超过四件.

从期物、任务和协程中产出

避免阻塞型调用

有两种方法能避免阻塞型调用中止整个应用程序的进程:

  • 在单独的线程中运行各个阻塞型操作

  • 把每个阻塞型操作转换成非阻塞的异步调用使用

多线程是可以的, 但是会消耗比较大的内存. 为了降低内存的消耗, 通常使用回调来实现异步调用. 这是一种底层概念, 类似所有并发机制中最古老最原始的那种--硬件中断. 使用回调时, 我们不等待响应, 而是注册一个函数, 在发生某件事时调用. 这样, 所有的调用都是非阻塞的.

异步应用程序底层的事件循环能依靠基础设置的中断, 线程, 轮询和后台进程等待等, 确保多个并发请求能取得进展并最终完成, 这样才能使用回调. 事件循环获得响应后, 会回过头来调用我们指定的回调. 如果做法正确, 事件循环和应用代码公共的主线程绝不会阻塞.

把生成器当做协程使用是异步编程的另一种方式. 对事件循环来说, 调用回调与在暂停的协程上调用 .send()效果差不多.

覆盖型与非覆盖型描述符对比

python存取属性的方式是不对等的. 通过实例读取属性时, 通常返回的是实例中定义的属性, 但是, 如果实例中没有指定的属性, 那么会从获取类属性. 而实例中属性赋值时, 通常会在实例中创建属性, 根本不影响类.

覆盖型描述符

我们要做一个在运行时创建类的, 类工厂函数:

元类基础知识

元类是制造类的工厂, 不过不是函数, 本身也是类. 元类是用于构建类的类 .

谢谢大家阅读,谢谢“栖迟於一丘”的分享!超牛逼!原文链接


本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
@所有人,您有一份Python入门基础学习宝典,请注意查收
Python入门教程:内置函数—Map、Reduce、Filter
第九章:魔法方法、属性和迭代器
适合Java开发者学习的Python入门教程
Python 工匠:容器的门道
编程语言Python代码阅读(第8篇):列表元素逻辑判断
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服