第1章 Python数据模型

第2章 序列构成的数组

对切片 slice 赋值改善可读性

num = slice(0,3)

[1, 2, 3, 'a', 'b', 'c'][num]

第3章 字典和集合

第4章 文本和字节序列

bytes -> str     解码(decode)输入的字符序列
100% str         只处理文本
str   -> bytes   编码(encode)输出的文本

第5章 一等函数

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

第7章 函数装饰器和闭包

导入时 运行时

Python不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量

第8章 对象引用、可变性和垃圾回收

变量是标注,而不是盒子

元组的不可变性其实是指tuple数据结构的物理内容(即保存的引用)不可变,与引用的对象无关

浅复制:复制了最外层容器,副本中的元素是源容器中元素的引用 深复制:副本不共享内部对象的引用

# 默认参数是函数对象的一个属性,如果它是
In [1]: def acc(a=[]):
   ...:    a.append(1)
   ...:
   ...: acc()
   ...: acc()
   ...: acc.__defaults__
   ...:
Out[1]: ([1, 1],)

del语句删除名称,而不是对象

每个对象会统计有多少引用指向自己,当引用计数归零时,对象立即就被销毁。Cpython会在对象上调用__del__方法(如果定义了),然后释放分配给对象的内存。

弱引用不会增加对象的引用数量。引用的目标对象称为所指对象(referent)。因此我们说,弱引用不会妨碍所指对象被当作垃圾回收。

第9章 符合Python风格的对象

# 设置x为只读属性
class C:

    def __init__(self, x):
        self.__x = float(x)

    @property
    def x(self):
        return self.__x

创建可散列的类型,需要正确地实现__hash____eq__方法

实例的私有变量(private)以双下划线开头,只能内部访问,如__name, 一般内部访问为self.__name在外部可用_类名__name访问, 通过dir(实例)可以查看到

Python在各个实例中名为__dict__的字典里存储实例属性,为了使底层的散列表提升访问速度,字典会消耗大量内存,如果要处理数百万个属性不多的实例,通过__solts__类属性,让解释器在元组中存储实例属性,从而节省内存。__solts__是一个类属性,它的值为一个由字符串构成的可迭代对象,其中各个元素表示各个实例属性。

类属性__weakref__可以让对象支持弱引用(用户自定义的类中默认有__weakref__属性),如果类中定义了__solts__属性,为了让实例可以作为弱引用的目标,要把__weakref__加入__solts__中。

解释器会忽略继承来的__solts__

Python有个很独特的特性:类属性可用于为实例属性提供默认值。但当显式的设置实例属性后,实例属性会覆盖类属性

第10章 序列的修改、散列和切片

Python的序列协议只需要__len____getitem__两个方法。

seq[a:b:c]的工作原理:创建slice(a, b, c)对象,交给__getitem__方法处理。

属性查找失败后,解释器会调用__getattr__方法。 getattr(self, name)

多数时候,如果实现了__getattr__方法,那么也要实现__setattr__方法,以防对象的行为不一致。

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

接口是对象公开方法的子集,让对象在系统中扮演特定的角色。(比如实现特定方法的对象,可视为“文件类对象”、“可迭代对象”等。golang的interface更能表示这种思想。)

猴子补丁:在运行时修改类或模块,而不改动源码。

"""猴子补丁示例"""
class C:

    def __init__(self, values):
        self.values = values

## 在控制台运行
>> c = C([1,2,3])
>> def set_value(c, position, value):
..     c.values[position] = value
>> C.__setitem__ = set_value
>> c[0] = 4
>> c.values
[4, 2, 3]

鸭子类型:对象的类型无关紧要,只要实现了特定的协议,就可进行相应的操作。(比如实现了__iter__方法,就可视为实现了“迭代器协议”,就可用for _ in _进行迭代操作)

抽象基类中的具体方法只能依赖抽象基类定义的接口(即只能使用抽象基类中的其他具体方法、抽象方法或特性)

子类必须实现抽象方法

白鹅类型:借助白鹅类型,可以使用抽象基类声明接口,而且类可以子类化抽象基类或使用抽象基类注册。即使不继承,也有办法把一个类注册为抽象的虚拟子类。这样做时,我们保证注册的类忠实的实现了抽象基类定义的接口。(即使不注册,抽象基类也能把一个类识别为虚拟子类)

魔法方法__subclasshook__可以让抽象基类识别没有注册为子类的类。

类的继承关系在一个特殊的类属性中指定————__mro__,即Method Resolution Order。这个属性按顺序列出类及其超类,Python会按照这个顺序搜索方法。

  • 强类型和弱类型:

      如果一门语言很少隐式转换类型,说明他是强类型语言;如果经常这么做,说明它是弱类型语言。Java、C++和Python是强类型语言。PHP、JavaScript和Perl是弱类型语言。
    
  • 静态类型和动态类型

      在编译时检查类型的语言是静态类型语言,在运行时检查类型的语言是动态类型语言。静态类型需要声明类型(有些现代语言使用类型推导避免部分类型声明)。
    

Python是动态强类型语言

第12章 继承的优缺点

直接子类化内置类型(如dict、list或str)容易出错,因为内置类型的方法通常会忽略用户覆盖的方法。不要子类化内置类型,用户自定义的类应该继承collections模块中的类,例如UserDict、UserList和UserString。

# 超类中的方法可以直接调用,此时要把实例作为显式参数传入。
class C:
    def ping(self):
        print('ping', self)

class D(C):
    pass

d = D()
C.ping(d)

类都有一个名为__mro__的属性,它的值是一个元组,按照Method Resolution Order列出各个超类,从当前类一直向上,直到object类。

若想把方法调用委托给超类,推荐使用内置的super().method()/super(SonName, self).method()。然而,有时可能需绕过方法解析顺序,直接调用某个超类的方法,此时可以使用Parent.method(self)

方法解析顺序不仅考虑继承图,还考虑子类声明中列出超类的顺序。

方法解析顺序使用C3算法计算

第11章 正确重载运算符

实现一元运算符和中缀运算符的特殊方法一定不能修改操作数。使用这些运算符的表达式期待的结果是新对象。

a+b,先检查a__add__方法且返回值不是NotImplemented,调用a.__add__(b);否则检查b__radd__方法且返回值不是NotImplemented,调用b.__radd__(a);否则抛出TypeError

NotImplemented是特殊的单例值,returnNotImplementedError是与异常,raise

__eq__的后备机制是返回id(a) == id(b)__ne__的后备机制是返回not (a == b)

如果一个类没有实现就地运算符,增量赋值运算符就只是语法糖:a += b的作用与a = a + b完全一样。对不可变类型来说,这是预期的行为(创建了新实例)。

增量赋值特殊方法(?就地运算)返回self

一般来说,如果中缀运算符的正向方法(如__mul__)只处理与self属于同一类型的操作数,那就无需实现对应的反向方法(如__rmul__),因为按照定义,反向向方法是为了处理类型不同的操作数。

第14章 可迭代的对象、迭代器和生成器

Python解释器需要迭代对象x时,会自动调用iter(x)

内置的iter函数有以下作用:

  1. 检查对象是否实现了__iter__方法,如果实现了就调用它,获取一个迭代器。
  2. 如果没有实现__iter__方法,但是实现了__getitem__方法,Python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素。
  3. 如果尝试失败,Python抛出TypeError异常,通常会提示”C object is not iterable”。

任何Python序列可迭代的原因是,它们都实现了__getitem__方法,标准的序列也都实现了__iter__方法。

从Python 3.4开始,检查对象x能否迭代,最准确的办法是:调用iter(x)函数,如果不可迭代,再处理TypeError异常。

可迭代的对象(Iterable):对象实现了能返回迭代器__iter__方法,或对象实现了__getitem__方法,而且其参数是从0开始的索引。

迭代器(Iterator):实现了__next____iter__方法。

Python从可迭代的对象中获取迭代器。

s = 'ABC'
for char in s:
    print(char)

# 上述代码相当于

s = 'ABC'
it = iter(s)
while True:
    try:
        print(next(it))
    except StopIteration:
        del it
        break

Iterable有个__iter__方法,每次都实例化一个新的Iterator;而Iterator要实现__next__方法,返回单个元素,此外还要实现__iter__方法,返回Iterator本身。

Iterable[__iter__]
    |
    |构建
    v
Iterator[__next__, __iter__]

Iterator的__iter__方法返回self,以便在应该使用Iterable对象的地方使用迭代器。

检查对象x是否为Iterator最好的方法是调用isinstance(x, abc.Iterator)

生成器函数定义体内的return语句会触发生成器对象抛出StopIteration异常。

第15章 上下文管理器和else块

EAFP(easier to ask for forgiveness than permission):先假定(try)存在有效的键或属性,如果假定不成立,那么捕获异常(except)。

LBYL(look before you leap):在调用函数或查找属性或键之前显式测试(if)前提条件。

与函数和模块不同,with块没有定义新的作用域。

__enter__(self)__exit__(self, exc_type, exc_value, traceback)

在使用@contextmanager装饰的生成器中,yield语句的作用是把函数的定义体分成两个部分:yield语句前面的所有代码在with块开始时(即解释器调用__enter__方法时)执行,yield语句后面的代码在with语句结束时(即调用__exit__方法时)执行。

第16章 协程

从根本上把yield视为流程控制的方式,这样就好理解协程了。关键的一点是,协程在yield关键字所在的位置暂停执行

预激(prime):最先调用next(coro)函数这一步,让协程向前执行到第一个yield表达式,准备好作为活跃的协程使用。

# 预激协程的装饰器
from functools import wraps

def coroutine(func):
    @wraps(func)
    def primer(*args, **kwargs):
        gen = func(*args, **kwargs)
        next(gen)
        return gen
    return primer

使用yield from句法调用协程时,会自动预激,因此与上例的@coroutine装饰器不兼容。asyncio.coroutine装饰器(async)不会预激协程,因此兼容yield from句法(await)。

协程中未处理的异常会向上冒泡,传给next函数或send方法的调用方(即触发协程的对象)。

协程发生并抛出异常后,协程会终止。试图重新激活协程会抛出StopIteration异常。

# 致使生成器在暂停的yield表达式处抛出异常。
generator.throw(exc_type[, exc_value[, traceback]])

# 致使生成器在暂停的yield表达式处抛出GeneratorExit异常。如果生成器没有处理这个异常,或抛出StopIteration异常,调用方不会报错。
generator.close()

生成器中的return result语句会抛出StopIteration(result)异常,这样调用方可以从异常的value属性中获取result。(yield from能自动处理)

# 从`StopIteration`的`value`中获取协程用`return`返回的值。
try:
    coro.send(None)
except StopIteration as exc:
    result = exc.value

yield from结构会在内部自动捕获StopIteration异常,还会把value属性的值变成yield from表达式的值。

  • 子生成器(用yield from激活)
  • 委派生成器(在定义体中使用yield from)
  • 客户代码(使用send, next, throw, close驱动整个过程)
    客户代码          委派生成器            子生成器
    +------+         +----------+          +--------+
    |      |--send-->|          |--------->|        |
    |      |<--------|          |<-yield---|        |
    |      |--throw->|          |--------->|        |
    |      |--close->|          |--------->|        |
    +------+         +----------+          +--------+

RESULT = yield from EXPR

# 相当于如下伪码:

_i = iter(EXPR)
try:
    _y = next(_i)
except StopIteration as _e:
    _r = _e.value
else:
    while True:
        _s = yield _y
        try:
            _y = _i.send(_s)
        except StopIteration as _e:
            _r = _e.value
            break
RESULT = _r

事件驱动框架的运作方式:在单个线程中使用一个主循环驱动协程执行并发活动。使用协程做面向事件编程时,协程会不断把控制权让步给主循环,激活并向前运行其他协程,从而执行各个并发活动。

协作式多任务:协程显式自主地把控制权让步给中央调度程序。

抢占式多任务(多线程):调度程序可以在任何时刻暂停线程(即使在执行一个语句的过程中),把控制权让给其他线程。

第17章 使用期物(future)处理并发

期物(future):期物指一种对象,表示异步执行的操作。

defer, promise

GIL: Global Interpreter Lock

CPython解释器本身就不是线程安全的,因此有全局解释器锁(GIL),一次只允许使用一个线程执行Python字节码。

标准库中所有执行阻塞型I/O操作的函数,在等待操作系统返回结果时都会释放GIL。

第18章 使用asyncio包处理并发

asyncio包使用事件循环驱动的协程实现并发。

调度程序任何时候都能中断线程,需要使用锁来保护程序中的重要部分,防止多步操作在执行的过程中中断,防止数据处于无效状态;而协程只会在yield处中断,任意时刻只有一个协程在运行,具有自同步的能力。

能够安全地取消协程的原因:按照定义协程只能在暂停yield处取消,因此可以处理CancelledError异常,执行清理操作。

在asyncio包中,yield from的作用是把控制权还给事件循环。

在一个单线程程序中使用主循环依次激活队列里的协程,各个协程向前执行几步,然后把控制权让给主循环,主循环在激活队列里的下一个协程。

使用asyncio包时,我们编写的异步代码中包含由asyncio本身驱动的协程(即委派生成器),而生成器最终把职责委托给asyncio包或第三方库(如aiohttp)中的协程。

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

  • 在单独的线程中运行各个阻塞型操作
  • 把每个阻塞型操作转换成非阻塞的异步调用
    • 回调
    • 协程

第19章 动态属性和特性

在Python中,数据的属性和处理数据的方法统称属性(attribute)。其实,方法只是可调用的属性。

仅当无法使用常规的方式获取属性(即在实例、类或超类中找不到指定的属性),解释器才会调用特殊的__getattr__方法。

用于构建实例的是特殊方法__new__:这是个类方法(使用特殊方式处理,因此不必使用@classmethod装饰器),必须返回一个实例。返回的实例会作为第一个参数(即self)传给__init__方法。因此调用__init__方法时要传入实例,而且禁止返回任何值,所以__init__方法其实是“初始化方法”。真正的构造方法是__new__

__new__方法也可以返回其他类的实例,此时,解释器不会调用__init__方法。

# 构建对象的伪代码
def object_maker(the_class, some_arg):
    new_object = the_class.__new__(some_arg)
    if isinstance(new_object, the_class):
        the_class.__init__(new_object, some_arg)
    return new_object

# 下述两个语句的作用基本等效
x = Foo('bar')
x = object_maker(Foo, 'bar')

对象的__dict__属性中存储着对象的属性——前提是类中没有声明__slots__属性。因此,更新实例的__dict__属性,把值设为一个映射,能快速地在那个实例中创建一堆属性。

第20章 属性描述符

descr.get(self, obj, type=None) -> value

descr.set(self, obj, value) -> None

descr.delete(self, obj) -> None

https://docs.python.org/3/howto/descriptor.html

第21章 类元编程

所有类都是 type 的实例,但是元类还是 type 的子类,因此可以作为制造类的工厂

其中三个属性在本书中已经见过多次: mroclassname 。此外,还有以下 属性。 cls.bases 由类的基类组成的元组。 cls.qualname Python 3.3 新引入的属性,其值是类或函数的限定名称,即从模块的全局作用域到类的 点分路径。例如,在示例 21-6 中,内部类 ClassTwo 的 qualname 属性,其值是字符 串 ‘ClassOne.ClassTwo’ ,而 name 属性的值是 ‘ClassTwo’ 。这个属性的规范是“PEP 3155 — Qualified name for classes and functions”(https://www.python.org/dev/peps/pep-3155/)。 cls.subclasses() 这个方法返回一个列表,包含类的直接子类。这个方法的实现使用弱引用,防止在超类 和子类(子类在 bases 属性中储存指向超类的强引用)之间出现循环引用。这个方 法返回的列表中是内存里现存的子类。 cls.mro() 构建类时,如果需要获取储存在类属性 mro 中的超类元组,解释器会调用这个方 法。元类可以覆盖这个方法,定制要构建的类解析方法的顺序。