本章将讨论继承和子类化,重点是说明对 Python 而言尤为重要的两个细节:
我们将通过两个重要的 Python 项目探讨多重继承,这两个项目是 GUI 工具包 Tkinter 和 Web 框架 Django
我们将首先分析子类化内置类型的问题,然后讨论多重继承,通过案例讨论类层次结构方面好的做法和不好的
在 Python 2.2 之前内置类型(如 list 和 dict)不能子类化,之后可以了,但是有个重要事项:内置类型(使用 C 语言编写)不会调用用户定义的类覆盖的特殊方法
至于内置类型的子类覆盖的方法会不会隐式调用,CPython 没有官方规定,基本上,内置类型的方法不会调用子类覆盖的方法。例如,dict 的子类覆盖 __getitem__() 方法不会被内置类型的 get() 方法调用,下面说明了这个问题:
内置类型的 dict 的 __init__ 和 __update__ 方法会忽略我们覆盖的 __setitem__ 方法
本章讨论的话题是接口,从鸭子类型代表特征动态协议,到使接口更明确,能验证是否符合规定的抽象基类(Abstract Base Class,ABC)
在 Python 中 上章所说的鸭子类型是接口的常规方式,新只是是抽象基类和类型检查。Python 语言诞生 15 年之后,Python 2.6 才引入抽象基类。
本章先说明 Python 社区以往对接口的不严谨理解:部分实现接口通常被认为是可接受的。我们通过几个示例强调鸭子类型的动态本性,从而澄清这一点
接着,通过 Alex Martelili 写的一篇短文,对抽象基类作介绍,还为 Python 编程下一个新趋势下定义。本章余下的内容专门讲解抽象基类。首先,本章说明抽象基类的常见用途:实现接口时作为超类使用。
然后,说明抽象基类如何检查具体子类是否符合接口定义,以及如何使用注册机制声明一个类实现了某个接口,而不进行子类化操作。最后,说明如何让抽象基类自动 ”识别“ 任何符合接口的类 -- 不进行子类化或注册
我们将实现一个新抽象基类,看看它的运作方式。但是,作者和 Alex Martelli 都不建议你自己编写抽象基类,因为容易过度设计
抽象基类与描述符和元类一样,是用于构建框架的工具。因此,只有少数 Python 开发者编写的抽象基类不会对用户施加不必要的限制,让他们做无用功
下面从 Python 风格探索接口
在引入抽象基类之前,Python 已经非常成功了,即使现在也很少有代码使用抽象基类。在第一章就讨论了鸭子类型和协议,在上一章,我们将协议定义为非正式的接口,是让 Python 这种动态类型语言实现多态的方式。
接口在动态类型语言是怎么运作的呢?首先,基本的事实是,Python 语言没有 interface 关键字,而且除了抽象基类,每个类都有接口:类实现或继承的公开属性(方法或数据的属性),包括特殊方法,如 __getitem__ 或 __add__
按照定义,受保护的属性和私有属性不在接口中:即便有“受保护”属性也只是采用命名约定实现的(单个前导下划线)私有属性也可以轻松的访问(第 9 章),原因也是如此,不要违背这些约定
另一方面,不要觉得把公开数据属性放入对象接口中不妥,因为如果需要,总能实现读值方法和设值方法,把数据属性变成特性,使用 obj.attr 语法的客户代码不会受到影响。Vector2d 类就是这么做的
下面的例子 x,y 是公开属性
本章以第 9 章定义的二维向量 Vector2d 类为基础,向前迈出一大步,定义表示多维向量的 Vector 类。这个类的行为与 Python 标准中的不可变扁平序列一样。Vector 实例中的元素是浮点数,本章结束后 Vector2d 类将支持以下功能
__len__ 和 __getitem__此外,我们还将通过 __getattr__ 方法实现属性的动态存取,以此取代 Vector2d 使用的只读属性 -- 不过,序列类型通常不会这么做
在大量代码之间,我们将穿插讨论一个概念:把协议当做正式借口。我们将说明协议和鸭子类型之间的关系,以及对自定义类型的影响
Vector 类要尽量与上一章的 Vector2d 类兼容。为了编写 Vector(3, 4),Vector(3, 4, 5) 这样的代码,我们可以让 __init__ 方法接受任意个参数(通过 *args);但是,序列类型的构造方法最好接受可迭代的对象为参数,因为所有内置的序列类型都是这样做的。下面是我们的第一版 Vector 代码
得益于 Python 数据模型,自定义类型行为可以像内置类型那样自然。实现如此自然的行为,靠的不是继承,而是鸭子类型,我们只需要按照预定行为实现对象所需方法即可
这一章我们定义自己的类,而且让类的行为跟真正的 Python 对象一样,这一章延续第一章,说明如何实现在很多 Python 类型中常见的特殊方法。
本章包含以下话题:
__slots__ 节省内存我们将开发一个二维欧几里得向量模型,这个过程中覆盖上面所有话题。这个过程中我们会讨论两个概念
每门面向对象的语言至少都有一种获取对象的字符串表示形式的标准方式。Python 提供了两种方式
repr(): 便于开发者理解的方式返回对象的字符串表示形式
str(): 便于用户理解的方式返回对象的字符串表示形式
为了给对象提供其他的表现形式,还会用到两个特殊的方法, __bytes__ 和 __format__。__bytes__ 方法与 __str__方法类似:bytes() 函数调用它获取对象的字节序列表示形式。而 __format__ 方法会被内置的 format() 和 str.format() 调用。使用特殊的格式代码显示对象的字符串表示形式。
注意:Python3 中 __repr__, __str__, __format__ 方法都必须返回 Unicode 字符串(str)类型。只有 __bytes__ 方法应该返回字节序列(bytes 类型)
为了说明用于生成对象表示形式的众多方法,我们将使用一个 Vector2d 类,与第一章的类似。这几节会不断完善这个类,我们期望这个类行为如下所示:
本章先用一个比喻说变量不是盒子,而是标注,然后讨论对象标示,值和别名的概念。随后会揭露元组的一个神奇的特性,元组是不可变的,其中的值可以改变。之后引申到浅复制和深复制。接下来是引用和函数参数。然后最后一节讨论垃圾回收,del 命令以及如何使弱引用 “记住” 对象,而无需对象本身存在
本章的内容是许多 Python 程序中不易察觉的 bug 关键
人们经常使用变量是盒子这样的比喻,但是这不易理解面向对象语言中的引用式变量。最好把它们理解为附加在对象上的标注:
装饰器用于在源码中 “标记” 函数,以某种方式增强函数行为。这是一项强大的功能,但是如果想掌握,必须理解闭包
nonlocal 是新出现的关键字,在 Python 3.0 中引入。作为 Python 程序员,如果严格遵守基于类的面向对象编程方式,即使不知道这个关键字也没事,但是如果想自己实现函数装饰器,那就必须了解闭包的方方面面,因此也就需要知道 nonlocal
这一章中我们主要讨论的话题如下:、
掌握这些知识,可以进一步探讨装饰器:
下面我们先介绍基础知识:
假如有个 decorate 装饰器
@decorate
def target():
print('running target()')
上面的写法与下面效果一样:
def target():
print('running target()')
target = decorate(target)
Norvig 建议在有一等函数的语言重新审视 “策略” “命令” “模板方法” 和 “访问者” 模式。通常,我们我们可以把这些模式中涉及的某些类的实例替换成简单的函数,从而减少样板代码,本章将用函数对象重构 “策略” 模式
策略模式就是定义一些列算法,把它们意义封装起来,并且使它们可以相互替换。本模式使得算法可以独立于它的客户而变化
电商领域有个功能可以明显使用 “策略” 模式,即根据客户的属性或订单中的商品计算折扣,例如有个网店有如下折扣规则
简单起见,我们规定一个订单只能享有一种折扣。策略涉及如下几个概念:
上下文:
策略:
具体策略:
下面例子,实例化订单之前,系统会以某种方式选择一种促销折扣策略,然后传给 Order 构造方法。具体怎么选择策略,不在这个模式职责范围内:
这篇文章需要看 ipynb 文件,这个 html 没有转换全,而且还有很多格式不对
在 Python 中,函数是一等对象。编程语言理论家把 “一等对象” 定义为满足下面条件的程序实体:
Python 中,整数、字符串和字典都是一等对象。人们经常将 ”把函数视作一等对象“ 简称为 ”一等函数“。这样说并不完美,似乎表明函数是一等对象中的特殊群体。在 Python 中,所有的函数都是一等对象
下面的例子表明 Python 函数是对象,创建了一个函数,然后调用它,读取它的 __doc__ 属性,并确定函数对象本身是 function 类的实例
本章将讨论以下话题:
(有一些东西觉得用不到,就没有记,到时候用到可以对照目录看书)
字符串是个简单的概念,一个字符序列,问题出现在 “字符” 的定义上。在 2015 年 “字符” 的最佳定义是 Unicode 字符,因此,从 Python 3 的 str 对象获得的元素是 Unicode 字符,这相当于从 Python 2 中的 unicode 对象中获取的元素,而不是从 Python 2 中的 str 对象获取原始字节序列。
把码位转成字节序列的过程叫编码,把字节序列转换成码位的过程是解码。下面展示了这一区分:
我们在这章讨论字典和集合,因为它们背后都是哈希表,下面是本章的大纲
如果一个对象有一个哈希值,而且在生命周期中不被改变(它需要实现一个 __hash__() 方法),而且可以与其它对象比较(需要实现 __eq__() 方法),就是可散列化的。原子不可变数据类型(str, bytes 和数值类型)都是可散列类型,fronenset 也是可散列类型,因为根据其定义,frozenset 只能容纳可散列类型,元祖的话,只有当一个元组的所有元素都是可散列的,元组才是可散列的。
© kaka 2016
Powered by Pelican