12 继承的优缺点

 — 

本章将讨论继承和子类化,重点是说明对 Python 而言尤为重要的两个细节:

  • 子类化内置类型的缺点
  • 多重继承的方法和解析顺序

我们将通过两个重要的 Python 项目探讨多重继承,这两个项目是 GUI 工具包 Tkinter 和 Web 框架 Django

我们将首先分析子类化内置类型的问题,然后讨论多重继承,通过案例讨论类层次结构方面好的做法和不好的

子类化内置类型很麻烦

在 Python 2.2 之前内置类型(如 list 和 dict)不能子类化,之后可以了,但是有个重要事项:内置类型(使用 C 语言编写)不会调用用户定义的类覆盖的特殊方法

至于内置类型的子类覆盖的方法会不会隐式调用,CPython 没有官方规定,基本上,内置类型的方法不会调用子类覆盖的方法。例如,dict 的子类覆盖 __getitem__() 方法不会被内置类型的 get() 方法调用,下面说明了这个问题:

内置类型的 dict 的 __init____update__ 方法会忽略我们覆盖的 __setitem__ 方法

Category: fluent_python Tags:

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

 — 

本章讨论的话题是接口,从鸭子类型代表特征动态协议,到使接口更明确,能验证是否符合规定的抽象基类(Abstract Base Class,ABC)

在 Python 中 上章所说的鸭子类型是接口的常规方式,新只是是抽象基类和类型检查。Python 语言诞生 15 年之后,Python 2.6 才引入抽象基类。

本章先说明 Python 社区以往对接口的不严谨理解:部分实现接口通常被认为是可接受的。我们通过几个示例强调鸭子类型的动态本性,从而澄清这一点

接着,通过 Alex Martelili 写的一篇短文,对抽象基类作介绍,还为 Python 编程下一个新趋势下定义。本章余下的内容专门讲解抽象基类。首先,本章说明抽象基类的常见用途:实现接口时作为超类使用。

然后,说明抽象基类如何检查具体子类是否符合接口定义,以及如何使用注册机制声明一个类实现了某个接口,而不进行子类化操作。最后,说明如何让抽象基类自动 ”识别“ 任何符合接口的类 -- 不进行子类化或注册

我们将实现一个新抽象基类,看看它的运作方式。但是,作者和 Alex Martelli 都不建议你自己编写抽象基类,因为容易过度设计

抽象基类与描述符和元类一样,是用于构建框架的工具。因此,只有少数 Python 开发者编写的抽象基类不会对用户施加不必要的限制,让他们做无用功

下面从 Python 风格探索接口

Python 文化中的接口和协议

在引入抽象基类之前,Python 已经非常成功了,即使现在也很少有代码使用抽象基类。在第一章就讨论了鸭子类型和协议,在上一章,我们将协议定义为非正式的接口,是让 Python 这种动态类型语言实现多态的方式。

接口在动态类型语言是怎么运作的呢?首先,基本的事实是,Python 语言没有 interface 关键字,而且除了抽象基类,每个类都有接口:类实现或继承的公开属性(方法或数据的属性),包括特殊方法,如 __getitem____add__

按照定义,受保护的属性和私有属性不在接口中:即便有“受保护”属性也只是采用命名约定实现的(单个前导下划线)私有属性也可以轻松的访问(第 9 章),原因也是如此,不要违背这些约定

另一方面,不要觉得把公开数据属性放入对象接口中不妥,因为如果需要,总能实现读值方法和设值方法,把数据属性变成特性,使用 obj.attr 语法的客户代码不会受到影响。Vector2d 类就是这么做的

下面的例子 x,y 是公开属性

Category: fluent_python Tags:

10 序列的修改,散列和切片

 — 

本章以第 9 章定义的二维向量 Vector2d 类为基础,向前迈出一大步,定义表示多维向量的 Vector 类。这个类的行为与 Python 标准中的不可变扁平序列一样。Vector 实例中的元素是浮点数,本章结束后 Vector2d 类将支持以下功能

  • 基本的序列协议 -- __len____getitem__
  • 正确表述拥有很多元素的实例
  • 适当的切片支持,用于生成新的 Vector 实例
  • 综合各个元素的值计算散列值
  • 自定义的格式语言扩展

此外,我们还将通过 __getattr__ 方法实现属性的动态存取,以此取代 Vector2d 使用的只读属性 -- 不过,序列类型通常不会这么做

在大量代码之间,我们将穿插讨论一个概念:把协议当做正式借口。我们将说明协议和鸭子类型之间的关系,以及对自定义类型的影响

Vector 第一版:与 Vector2d 类兼容

Vector 类要尽量与上一章的 Vector2d 类兼容。为了编写 Vector(3, 4),Vector(3, 4, 5) 这样的代码,我们可以让 __init__ 方法接受任意个参数(通过 *args);但是,序列类型的构造方法最好接受可迭代的对象为参数,因为所有内置的序列类型都是这样做的。下面是我们的第一版 Vector 代码

Category: fluent_python Tags:

9 符合 Python 风格的对象

 — 

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

这一章我们定义自己的类,而且让类的行为跟真正的 Python 对象一样,这一章延续第一章,说明如何实现在很多 Python 类型中常见的特殊方法。

本章包含以下话题:

  • 支持用于生成对象其他表示形式的内置函数(如 repr(), bytes() 等等)
  • 使用一个类方法实现备选构造方法
  • 扩展内置的 format() 函数和 str.format() 方法使用的格式微语言
  • 实现只读属性
  • 把对象变成可散列的,以便在集合中及作为 dict 的键使用
  • 利用 __slots__ 节省内存

我们将开发一个二维欧几里得向量模型,这个过程中覆盖上面所有话题。这个过程中我们会讨论两个概念

  • 如何以及何时利用 @classmethod 和 @staticmethod 装饰器
  • Python 的私有属性和受保护属性的用法,约定和局限

对象表示形式

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

repr(): 便于开发者理解的方式返回对象的字符串表示形式

str(): 便于用户理解的方式返回对象的字符串表示形式

为了给对象提供其他的表现形式,还会用到两个特殊的方法, __bytes____format____bytes__ 方法与 __str__方法类似:bytes() 函数调用它获取对象的字节序列表示形式。而 __format__ 方法会被内置的 format() 和 str.format() 调用。使用特殊的格式代码显示对象的字符串表示形式。

注意:Python3 中 __repr__, __str__, __format__ 方法都必须返回 Unicode 字符串(str)类型。只有 __bytes__ 方法应该返回字节序列(bytes 类型)

再谈向量类

为了说明用于生成对象表示形式的众多方法,我们将使用一个 Vector2d 类,与第一章的类似。这几节会不断完善这个类,我们期望这个类行为如下所示:

Category: fluent_python Tags:

8 对象引用,可变性和垃圾回收

 — 

本章先用一个比喻说变量不是盒子,而是标注,然后讨论对象标示,值和别名的概念。随后会揭露元组的一个神奇的特性,元组是不可变的,其中的值可以改变。之后引申到浅复制和深复制。接下来是引用和函数参数。然后最后一节讨论垃圾回收,del 命令以及如何使弱引用 “记住” 对象,而无需对象本身存在

本章的内容是许多 Python 程序中不易察觉的 bug 关键

变量不是盒子

人们经常使用变量是盒子这样的比喻,但是这不易理解面向对象语言中的引用式变量。最好把它们理解为附加在对象上的标注:

Category: fluent_python Tags:

7 装饰器

 — 

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

nonlocal 是新出现的关键字,在 Python 3.0 中引入。作为 Python 程序员,如果严格遵守基于类的面向对象编程方式,即使不知道这个关键字也没事,但是如果想自己实现函数装饰器,那就必须了解闭包的方方面面,因此也就需要知道 nonlocal

这一章中我们主要讨论的话题如下:、

  • Python 如何计算装饰器语法
  • Python 如何判断变量是不是局部的
  • 闭包存在的原因和工作原理
  • nonlocal 能解决什么问题

掌握这些知识,可以进一步探讨装饰器:

  • 实现行为良好的装饰器
  • 标准库中有用的装饰器
  • 实现一个参数化装饰器

下面我们先介绍基础知识:

基础知识

假如有个 decorate 装饰器

@decorate
def target():
    print('running target()')

上面的写法与下面效果一样:

def target():
    print('running target()')

target = decorate(target)
Category: fluent_python Tags:

6 Python “策略”模式和“命令”模式

 — 

Norvig 建议在有一等函数的语言重新审视 “策略” “命令” “模板方法” 和 “访问者” 模式。通常,我们我们可以把这些模式中涉及的某些类的实例替换成简单的函数,从而减少样板代码,本章将用函数对象重构 “策略” 模式

策略模式就是定义一些列算法,把它们意义封装起来,并且使它们可以相互替换。本模式使得算法可以独立于它的客户而变化

电商领域有个功能可以明显使用 “策略” 模式,即根据客户的属性或订单中的商品计算折扣,例如有个网店有如下折扣规则

  • 有 1000 或以上积分的顾客,每个订单享 %5 折扣。
  • 同一订单中,单个商品数量达到 20 或以上,享 %10 折扣
  • 订单中的不同商品达到 10 个或以上,享 %7 折扣

简单起见,我们规定一个订单只能享有一种折扣。策略涉及如下几个概念:

上下文:

  • 把一些计算委托给实现不同算法的可互换组件,它提供服务。在这个电商例子中,上下文是 Order,它会根据不同的算法计算促销折扣

策略:

  • 实现不同算法组件的具体接口。在这个例子中,名为 Promotion 的抽象基类扮演这个角色

具体策略:

  • “策略” 的具体子类。fidelityPromo, BulkPromo, LargeOrderPromo 是这里的三个具体策略

下面例子,实例化订单之前,系统会以某种方式选择一种促销折扣策略,然后传给 Order 构造方法。具体怎么选择策略,不在这个模式职责范围内:

Category: fluent_python Tags:

5 一等函数

 — 

这篇文章需要看 ipynb 文件,这个 html 没有转换全,而且还有很多格式不对

在 Python 中,函数是一等对象。编程语言理论家把 “一等对象” 定义为满足下面条件的程序实体:

  • 在运行时创建
  • 能赋值给变量或者数据结构中的元素
  • 能作为参数传给函数
  • 能作为函数返回结果

Python 中,整数、字符串和字典都是一等对象。人们经常将 ”把函数视作一等对象“ 简称为 ”一等函数“。这样说并不完美,似乎表明函数是一等对象中的特殊群体。在 Python 中,所有的函数都是一等对象

把函数视作对象

下面的例子表明 Python 函数是对象,创建了一个函数,然后调用它,读取它的 __doc__ 属性,并确定函数对象本身是 function 类的实例

Category: fluent_python Tags:

4 文本和字节序列

 — 

本章将讨论以下话题:

  • 字符、码位和字节表述
  • bytes、bytearray 和 memoryview 等二进制序列的独特特性
  • 全部 Unicode 和陈旧字符集的编解码器
  • 避免和处理编码错误
  • 处理文本文件的最佳实践
  • 默认编码陷阱和标准 I/O 问题
  • 规范化 Unicode 文本,进行安全的比较
  • 规范化、大小写折叠和暴力移除音调符号的实用函数
  • 实用 locale 模块和 PyUCA 库正确地排序 Unicode 文本
  • Unicode 数据库中的字符元数据
  • 能处理字符串和字节序列的双模式 API

(有一些东西觉得用不到,就没有记,到时候用到可以对照目录看书)

字符问题

字符串是个简单的概念,一个字符序列,问题出现在 “字符” 的定义上。在 2015 年 “字符” 的最佳定义是 Unicode 字符,因此,从 Python 3 的 str 对象获得的元素是 Unicode 字符,这相当于从 Python 2 中的 unicode 对象中获取的元素,而不是从 Python 2 中的 str 对象获取原始字节序列。

把码位转成字节序列的过程叫编码,把字节序列转换成码位的过程是解码。下面展示了这一区分:


3-1 字典和集合常用操作

 — 

我们在这章讨论字典和集合,因为它们背后都是哈希表,下面是本章的大纲

  • 常用字典方法
  • 特别处理遗失的键
  • 在标准库中,dict 的变化
  • set 与 frozenset 形态
  • 哈希表的工作原理
  • 哈希表的影响(键形态限制,无法预知的排序等等)

什么是可散列化

如果一个对象有一个哈希值,而且在生命周期中不被改变(它需要实现一个 __hash__() 方法),而且可以与其它对象比较(需要实现 __eq__() 方法),就是可散列化的。原子不可变数据类型(str, bytes 和数值类型)都是可散列类型,fronenset 也是可散列类型,因为根据其定义,frozenset 只能容纳可散列类型,元祖的话,只有当一个元组的所有元素都是可散列的,元组才是可散列的。

Category: fluent_python Tags:

© kaka 2016

Powered by Pelican