流畅的Python(Fluent Python)

PART I. Prologue

Chapter 1. The Python Data Model

1.1 A Pythonic Card Deck

介绍了特殊方法,也叫做魔术方法

import collections

Card = collections.namedtuple('Card', ['rank', 'suit'])

class FrenchDeck:
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()

    def __init__(self):
        self._cards = [Card(rank, suit) for suit in self.suits
                                        for rank in self.ranks]

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, position):
        return self._cards[position]
#初始化这个纸牌(初始化一张纸牌的操作)
beer_card = Card('7','diamonds')

#初始化法国纸牌组
deck = FrenchDeck()
可以直接调用len(deck),deck[0],choice(deck),for card in deck, Card('Q','hearts') in deck, sorted(deck)等操作,因为实现了特殊方法__len__, __getitem__

1.2 How Special Methods Are Used

特殊方法的存在是为了被Python解释器调用,你自己并不需要调用它们。也就是没有my_object.__len__()这种写法。自己实现的类用len(my_object),他会自己去调用__len__方法。Python内置的类型,那么 CPython 会抄个近路,__len__ 实际 上会直接返回 PyVarObject 里的 ob_size 属性。PyVarObject 是表示 内存中长度可变的内置对象的 C 语言结构体。

for i in x: 这个语句, 背后其实用的是 iter(x),而这个函数的背后则是 x.__iter__() 方 法。

from math import hypot

class Vector:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __repr__(self):  #直接在print(Vector)输出
        return 'Vector(%r, %r)' % (self.x, self.y)

    def __abs__(self):
        return hypot(self.x, self.y)

    def __bool__(self):
        return bool(abs(self))

    def __add__(self, other):  #调用+的时候使用
        x = self.x + other.x
        y = self.y + other.y
        return Vector(x, y)

    def __mul__(self, scalar):  #调用*的时候使用,注意顺序
        return Vector(self.x * scalar, self.y * scalar)

#__repr__ 和 __str__ 的区别在于,后者是在 str() 函数被使用,或是在用 print 函数打印一个对象的#时候才被调用的,并且它返回的字符串对终端用户更友好。如果你只想实现这两个特殊方法中的一个,#__repr__ 是更好的选择,因为如果一个对象没有 __str__ 函数,而 Python 又需要调用它的时候,解释#器会用 __repr__ 作为替代。

#默认情况下,我们自己定义的类的实例总被认为是真的,除非这个类对__bool__ 或者 __len__ 函数有自
#己的实现。bool(x) 的背后是调用x.__bool__() 的结果;如果不存在 __bool__ 方法,那么 bool(x)
#会尝试调用 x.__len__()。若返回 0,则 bool 会返回 False;否则返回True。

1.3 Overview of Special Methods

Python大概有83个特殊方法,其中47个用于实现算术运算、位运算和比较操作。(其中包括反向运算符和增量赋值运算符a*=b)

1.4 Why len Is Not a Method

因为当len(x)中x是一个内置类型的实例,那么CPython就会直接从一个C结构体里读取对象的长度,不会调用任何方法,非常高效。

1.5 Chapter Summary

通过实现特殊方法,自定义数据类型可以表现得跟内置类型一样,从而 让我们写出更具表达力的代码——或者说,更具 Python 风格的代码。

PART II. Data Structures

Chapter 2. An Array of Sequences

2.1 Overview of Built-In Sequences

Python标准库用C实现了丰富的序列类型:

容器序列(可存放不同类型的数据):list, tuple, collections.deque

扁平序列(只能容纳一种类型):str, bytes, bytearray, memoryview, array.array

容器序列存放的是它们所包含的任意类型的对象的引用,扁平序列里存放的是值而不是引用(扁平序列是一段连续的内存空间)

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

不可变序列:tuple, str, bytes

2.2 List Comprehensions and Generator Expressions

列表推导是构建列表的快捷方式,而生成器表达式则可以用来创建其它任何类型的序列。

# 把一个字符串变成Unicode码位的列表
symbols = '$¢£¥€¤'
codes = []
for symbol in symbols:
    codes.append(ord(symbol))

# 或者
symbols = '$¢£¥€¤'
codes = [ord(symbol) for symbol in symbols]

Python 会忽略代码里 []、{} 和 () 中的换行,因此如果你的代码里 有多行的列表、列表推导、生成器表达式、字典这一类的,可以省 略不太好看的续行符 \。

列表推导同filter和map的比较

beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))

ASCII:'0' : 48, 'A' : 65, 'a' : 97

使用列表推导计算笛卡尔积:

colors = ['black', 'white']
sizes = ['S', 'M', 'L']
tshirts = [(color, size) for size in sizes
                         for color in colors]

列表推导的作用只有一个:生成列表。如果想生成其他类型的序列,生成器表达式就派上了用场。

虽然也可以用列表推导来初始化元组、数组或其他序列类型,但是生成 器表达式是更好的选择。这是因为生成器表达式背后遵守了迭代器协 议,可以逐个地产出元素,而不是先建立一个完整的列表,然后再把这 个列表传递到某个构造函数里。前面那种方式显然能够节省内存。

生成器表达式的语法跟列表推导差不多,只不过把方括号换成圆括号而 已。

# 用生成器表达式建立元组和数组

 symbols = '$¢£¥€¤'
 tuple(ord(symbol) for symbol in symbols)

 import array
 array.array('I', (ord(symbol) for symbol in symbols))


# 使用生成器表达式计算笛卡尔积
 colors = ['black', 'white']
 sizes = ['S', 'M', 'L']
 for tshirt in ('%s %s' % (c, s) for c in colors for s in sizes):
     print(tshirt)

2.3 Tuples Are Not Just Immutable Lists

2.3.1 Tuples as Records

2.3.2 Tuple Unpacking

用*运算符将一个可迭代对象拆开作为函数的参数:

>>> t = (20, 8)
>>> divmod(*t)

# 用*来处理剩下的元素
# 在 Python 中,函数用 *args 来获取不确定数量的参数算是一种经典写法了
>>> a, b, *rest = range(5)
>>> a, b, rest
(0, 1, [2, 3, 4])
>>> a, b, *rest = range(3)
>>> a, b, rest
(0, 1, [2])
>>> a, b, *rest = range(2)
>>> a, b, rest
(0, 1, [])

# 在平行赋值中,* 前缀只能用在一个变量名前面,但是这个变量可以出
# 现在赋值表达式的任意位置:
>>> a, *body, c, d = range(5)
>>> a, body, c, d
(0, [1, 2], 3, 4)
>>> *head, b, c, d = range(5)
>>> head, b, c, d
([0, 1], 2, 3, 4)

2.3.3 Nested Tuple Unpacking

2.3.4 NamedTuples

collections.namedtuple 是一个工厂函数,它可以用来构建一个带 字段名的元组和一个有名字的类(这个在acwing算法学习里写过了)

2.3.5 Tuples as Immutable Lists

2.4 Slice Objects

2.4.1 为什么切片和区间会忽略最后一个元素(几个小优点)

2.4.2 对对象进行切片

>>> s = 'bicycle'
>>> s[::3]
'bye'
>>> s[::-1]
'elcycib'
>>> s[::-2]
'eccb'

>>> a = '123456789'
>>> slice1 = slice(0,3)
>>> slice2 = slice(4,5)
>>> a[slice1]
>>> a[slice2]

2.4.3 Multidimensinal Slicing and Ellipsis

[] 运算符里还可以使用以逗号分开的多个索引或者是切片,外部库 NumPy 里就用到了这个特性,二维的 numpy.ndarray 就可以用 a[i, j] 这种形式来获取,抑或是用 a[m:n, k:l] 的方式来得到二维切片。要正确处理这种 [] 运算符的话,对 象的特殊方法 __getitem__ 和 __setitem__ 需要以元组的形式来接收 a[i, j] 中的索引。也就是说,如果要得到 a[i, j] 的值,Python 会 调用 a.__getitem__((i, j))。

省略(ellipsis)的正确书写方法是三个英语句号(...),省略在 Python 解析器眼 里是一个符号,而实际上它是 Ellipsis 对象的别名,而 Ellipsis 对象,又是 ellipsis 类的单一实例。 它可以当作切片规范的一部分,也可以用在函数的参数清单中,如果 x 是四维数组,那 么 x[i, ...] 就是 x[i, :, :, :] 的缩写。

2.4.4 Assigning to Slices

>>> l = list(range(10))
>>> del l[5:7]
>>> l[3::2] = [11, 22]
>>> l[2:5] = 100 ➊
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable
>>> l[2:5] = [100]
# 如果赋值的对象是一个切片,那么赋值语句的右侧必须是个可迭代
# 对象。即便只有单独一个值,也要把它转换成可迭代的序列。

2.5 Augmented Assignment with Sequences

Python 会新建一个包含同样类型数据的序列来作为拼接的结果(如列表的+),+ 和 * 都遵循这个规律,不修改原有的操作对象,而是构建一个全新的序列。

追加同一个行对象(row)3 次到游戏板(board)。

>>> board = []
>>> for i in range(3):
... row=['_'] * 3 # ➊
... board.append(row)

2.6 A += Assignment Puzzler

增量赋值运算符 += 和 *= 的表现取决于它们的第一个操作对象,+= 背后的特殊方法是 __iadd__ (用于“就地加法”)。但是如果一个类 没有实现这个方法的话,Python 会退一步调用 __add__ 。

如果 a 实现了 __iadd__ 方法,就会调用这个方法。同时对可变序列 (例如 list、bytearray 和 array.array)来说,a 会就地改动,就 像调用了 a.extend(b) 一样。但是如果 a 没有实现 __iadd__ 的话,a += b 这个表达式的效果就变得跟 a = a + b 一样了:首先计算 a + b,得到一个新的对象,然后赋值给 a。也就是说,在这个表达式中, 变量名会不会被关联到新的对象,完全取决于这个类型有没有实现 __iadd__ 这个方法。

对不可变序列进行重复拼接操作的话,效率会很低,因为每次都有一个 新对象,而解释器需要把原来对象中的元素先复制到新的对象里,然后 再追加新的元素。 str 是一个例外,因为对字符串做 += 实在是太普遍了,所以 CPython 对它做了优化。为 str 初始化内存的时候,程序会为它留出额外的可扩展空间,因此进行增量操作的时候,并不会涉 及复制原有字符串到新位置这类操作。

1 不要把可变对象放在元组里面。

2 增量赋值不是一个原子操作。我们刚才也看到了,它虽然抛出了异常,但还是完成了操作。

3 查看Python 的字节码并不难,而且它对我们了解代码背后的运行机 制很有帮助。

2.7 list.sort and the sorted Built-In Function

如果一个函数 或者方法对对象进行的是就地改动,那它就应该返回 None。

与 list.sort 相反的是内置函数 sorted,它会新建一个列表作为返回 值。这个方法可以接受任何形式的可迭代对象作为参数,甚至包括不可变序列或生成器。而不管 sorted 接受的是怎样的参数,它最后都会返回一个列表。(都有两个参数,一个reverse和key,一个只有一个参数的函数,这个函数会被用在序列里的每一个元素 上,所产生的结果将是排序算法依赖的对比关键字。可选参数 key 还可以在内置函数 min() 和 max() 中起作用。 另外,还有些标准库里的函数也接受这个参数,像 itertools.groupby() 和 heapq.nlargest() 等。

>>> sorted(fruits, key=len)

['grape', 'apple', 'banana', 'raspberry'])

2.8 Managing Ordered Sequences with bisect

bisect 模块包含两个主要函数,bisect 和 insort,两个函数都利用二分查找算法来在有序序列中查找或插入元素。

2.8.1 Searching with bisect

你可以先用 bisect(haystack, needle) 查找位置 index,再 用 haystack.insert(index, needle) 来插入新值。但你也可用 insort 来一步到位,并且后者的速度更快一些。(插入之后还是有序序列)

 把函数对象作为一个参数传递后进行调用。

bisect_left 返回的插入 位置是原序列中跟被插入元素相等的元素的位置,也就是新元素会被放 置于它相等的元素的前面,而 bisect_right 返回的则是跟它相等的元 素之后的位置。

bisect 可以用来建立一个用数字作为索引的查询表格,比如说把分数和成绩对应起来。

>>> def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
... i = bisect.bisect(breakpoints, score)
... return grades[i]
...
>>> [grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
['F', 'A', 'C', 'C', 'B', 'A', 'A']

2.8.2 Inserting with bisect.insort

insort(seq, item) 把变量 item 插入到序列 seq 中,并能保持 seq 的升序顺序。

目前所提到的内容都不仅仅是对列表或者元组有效,还可以应用于几乎所有的序列类型上。

如果你只需要处 理数字列表的话,数组可能是个更好的选择。下面就来讨论一些可以替换列表的数据结构。

2.9 When a List Is Not the Answer

要存放 1000 万个浮点数的话,数组(array)的效率要高得多,因为数组在背后存的并不是float 对象,而是数字的机器翻译,也就是字节表述。这一点就跟 C 语言中的数组一样。再比如说,如 果需要频繁对序列做先进先出的操作,deque(双端队列)的速度应该会更快。

2.9.1 Arrays

如果我们需要一个只包含数字的列表,那么array.array 比 list 更 高效。数组支持所有跟可变序列有关的操作,包括.pop、.insert 和 .extend。另外,数组还提供从文件读取和存入文件的更快的方法,如 .frombytes 和 .tofile。

创建数组需要一个类型码,这个类 型码用来表示在底层的 C 语言应该存放怎样的数据类型。

一个浮点型数组的创建、存入文件和从文件读取的过程

>>> from array import array ➊
>>> from random import random
>>> floats = array('d', (random() for i in range(10**7))) ➋
>>> floats[-1] ➌
0.07802343889111107
>>> fp = open('floats.bin', 'wb')
>>> floats.tofile(fp) ➍
>>> fp.close()
>>> floats2 = array('d') ➎
>>> fp = open('floats.bin', 'rb')
>>> floats2.fromfile(fp, 10**7) ➏
>>> fp.close()
>>> floats2[-1] ➐
0.07802343889111107
>>> floats2 == floats ➑
True

 用 array.fromfile 从一个二进制文件里读出 1000 万个双精度浮点数只需要 0.1 秒,这比从文本文件里读取的速度要快 60 倍,因为后者会使用内置的float 方法把每一行文字转换成浮点数。

使用 array.tofile 写入到二进制文件,比以每行一个浮点数的 方式把所有数字写入到文本文件要快 7 倍。

另外一个快速序列化数字类型的方法是使用 pickle,pickle.dump 处理浮点数组的速度几乎跟 array.tofile 一 样快。不过前者可以处理几乎所有的内置数字类型,包含复数、嵌 套集合,甚至用户自定义的类。前提是这些类没有什么特别复杂的实现。

要给数组排序的话,得用 sorted 函数新建一个数组

a = array.array(a.typecode, sorted(a))

2.9.2 Memory Views

memoryview 是一个内置类,它能让用户在不复制内容的情况下操作同一个数组的不同切片。memoryview是泛化和去数学化的 NumPy 数组。它让你在不需要复制内容的前提下,在数据结构之间共享内存。

memoryview.cast 的概念跟数组模块类似,能用不同的方式读写同一 块内存数据,而且内容字节不会随意移动。这听上去又跟 C 语言中类型 转换的概念差不多。memoryview.cast 会把同一块内存里的内容打包成一个全新的 memoryview 对象给你。

通过改变数组中的一个字节来更新数组里某个元素的值

>>> numbers = array.array('h', [-2, -1, 0, 1, 2])
>>> memv = memoryview(numbers) ➊
>>> len(memv)
5
>>> memv[0] ➋
-2
>>> memv_oct = memv.cast('B') ➌
>>> memv_oct.tolist() ➍
[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
>>> memv_oct[5] = 4 ➎
>>> numbers
array('h', [-2, -1, 1024, 1, 2]) ➏

2.9.3 NumPy and SciPy

凭借着 NumPy 和 SciPy 提供的高阶数组和矩阵操作,Python 成为科学计算应用的主流语言。

SciPy 是基于 NumPy 的另一个库,它提供了很多跟科学计算有关的算法,专为线性代数、数值积分和统计学而设计。

>>> import numpy ➊
>>> a = numpy.arange(12) ➋
>>> a
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> type(a)
<class 'numpy.ndarray'>
>>> a.shape ➌
(12,)
>>> a.shape = 3, 4 ➍
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> a[2] ➎
array([ 8, 9, 10, 11])
>>> a[2, 1] ➏
9
>>> a[:, 1] ➐
array([1, 5, 9])
>>> a.transpose() ➑
array([[ 0, 4, 8],
       [ 1, 5, 9],
       [ 2, 6, 10],
       [ 3, 7, 11]])
>>> import numpy
>>> floats = numpy.loadtxt('floats-10M-lines.txt') ➊
>>> floats[-3:] ➋
array([ 3016362.69195522, 535281.10514262, 4566560.44373946])
>>> floats *= .5 ➌
>>> floats[-3:]
array([ 1508181.34597761, 267640.55257131, 2283280.22186973])
>>> from time import perf_counter as pc ➍
>>> t0 = pc(); floats /= 3; pc() - t0 ➎
0.03690556302899495
>>> numpy.save('floats-10M', floats) ➏
>>> floats2 = numpy.load('floats-10M.npy', 'r+') ➐
>>> floats2 *= 6
>>> floats2[-3:] ➑
memmap([3016362.69195522, 535281.10514262, 4566560.44373946])

❺ 把每个元素都除以 3,可以看到处理 1000 万个浮点数所需的时间还不足 40 毫秒。

Pandas和Blaze数据分析库就以它们为基础,提供了高效 的且能存储非数值类数据的数组类型,和读写常见数据文件格式(例如 csv、xls、SQL转储和 HDF5)的功能。

2.9.4 Deques and Other Queues

collections.deque 类(双向队列)是一个线程安全、可以快速从两 端添加或者删除元素的数据类型。而且如果想要有一种数据类型来存 放“最近用到的几个元素”,deque 也是一个很好的选择。这是因为在新 建一个双向队列的时候,你可以指定这个队列的大小,如果这个队列满 员了,还可以从反向端删除过期的元素,然后在尾端添加新的元素。

>>> from collections import deque
>>> dq = deque(range(10), maxlen=10) ➊
>>> dq
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
>>> dq.rotate(3) ➋
>>> dq
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
>>> dq.extendleft([10, 20, 30, 40]) ➎
>>> dq
deque([40, 30, 20, 10, 7, 8, 9, 0, 1, 2], maxlen=10)

双向队列中间删除元素的操作会慢一些,因为它只对在头尾的操作进行了优化。append 和 popleft 都是原子操作,也就说是 deque 可以在多线程程序 中安全地当作先进先出的栈使用,而使用者不需要担心资源锁的问题。

# a_list.pop(p) 这个操作只能用于列表,双向队列的这个方法不接收参数。

queue

提供了同步(线程安全)类 Queue、LifoQueue 和 PriorityQueue,不同的线程可以利用这些数据类型来交换信息。这三 个类的构造方法都有一个可选参数 maxsize,它接收正整数作为输入 值,用来限定队列的大小。但是在满员的时候,这些类不会扔掉旧的元 素来腾出位置。相反,如果队列满了,它就会被锁住,直到另外的线程 移除了某个元素而腾出了位置。这一特性让这些类很适合用来控制活跃 线程的数量。

multiprocessing

这个包实现了自己的 Queue,它跟 queue.Queue 类似,是设计给 进程间通信用的。同时还有一个专门的 multiprocessing.JoinableQueue 类型,可以让任务管理变得更方便。

asyncio

里面有 Queue、LifoQueue、PriorityQueue 和 JoinableQueue,这些类受 到 queue 和 multiprocessing 模块的影响,但是为异步编程里的任务 管理提供了专门的便利。

CHAPTER 3

Dictionaries and Sets

3.1 Generic Mapping Types

collections.abc 模块中有 Mapping 和 MutableMapping 这两个抽象 基类,它们的作用是为 dict 和其他类似的类型定义形式接口。

# 用 isinstance 而不是 type 来检查某个参数是否为 dict 类型,
# 因为这个参数有可能不是 dict,而是一个比较另类的映射类型

>>> my_dict = {}
>>> isinstance(my_dict, abc.Mapping)
True

标准库里的所有映射类型都是利用 dict 来实现的,因此它们有个共同的限制,即只有可散列的数据类型才能用作这些映射里的键。(如果一个对象是可散列的,那么在这个对象的生命周期中,它 的散列值是不变的,而且这个对象需要实现 __hash__() 方 法。另外可散列对象还要有 __qe__() 方法,这样才能跟其他键做比较。如果两个可散列对象是相等的,那么它们的散列值一定是一样的)

原子不可变数据类型(str、bytes 和数值类型)都是可散列类 型,frozenset 也是可散列的,因为根据其定义,frozenset 里 只能容纳可散列类型。元组的话,只有当一个元组包含的所有元素 都是可散列类型的情况下,它才是可散列的。

一般来讲用户自定义的类型的对象都是可散列的,散列值就是它们 的 id() 函数的返回值,所以所有这些对象在比较的时候都是不相 等的。如果一个对象实现了 __eq__ 方法,并且在方法中用到了这 个对象的内部状态的话,那么只有当所有这些内部状态都是不可变 的情况下,这个对象才是可散列的。

>>> a = dict(one=1, two=2, three=3)
>>> b = {'one': 1, 'two': 2, 'three': 3}
>>> c = dict(zip(['one', 'two', 'three'], [1, 2, 3]))
>>> d = dict([('two', 2), ('one', 1), ('three', 3)])
>>> e = dict({'three': 3, 'one': 1, 'two': 2})
>>> a == b == c == d == e
True

3.2 dict Comprehensions

字典推导 (dictcomp)可以从任何以键值对作为元素的可迭代对象中构建出字 典。

>>> DIAL_CODES = [ ➊
... (86, 'China'),
... (91, 'India'),
... (1, 'United States'),
... (62, 'Indonesia'),
... (55, 'Brazil'),
... (92, 'Pakistan'),
... (880, 'Bangladesh'),
... (234, 'Nigeria'),
... (7, 'Russia'),
... (81, 'Japan'),
... ]
>>> country_code = {country: code for code, country in DIAL_CODES} ➋
>>> country_code
{'China': 86, 'India': 91, 'Bangladesh': 880, 'United States': 1,
'Pakistan': 92, 'Japan': 81, 'Russia': 7, 'Brazil': 55, 'Nigeria':
234, 'Indonesia': 62}
>>> {code: country.upper() for country, code in country_code.items() ➌
... if code < 66}
{1: 'UNITED STATES', 55: 'BRAZIL', 62: 'INDONESIA', 7: 'RUSSIA'}

3.3 Overview of Common Mapping Methods

# OrderedDict.popitem() 会移除字典里最先插入的元素(先进先出);同时这个方法还有一 个可选的 last 参数,若为真,则会移除最后插入的元素(后进先出)。

你既可以用一个映射对象来新建一个映射对象,也可以用包含 (key, value) 元素的可迭代对象来初始化一个 映射对象。

# 整个代码的功能是统计文本中哪些单词在哪些地方出现过
# 如a.txt文档,第一行为 I am a student 第二行为 you are a student too.
# 则结果(按大写字母顺序排序) I [(1,1)] ...   student [(1,8),(2,11)]

# \w指匹配[A-Za-z0-9_],+匹配一个至多个
# .finditer返回的是一个可迭代对象,然后用group得到被匹配的所有组
# enumerate,为可迭代对象编号

import sys
import re
WORD_RE = re.compile(r'\w+')
index = {}
with open(sys.argv[1], encoding='utf-8') as fp:
    for line_no, line in enumerate(fp, 1):
        for match in WORD_RE.finditer(line):
            word = match.group()
            if word=='better':
                print()
            column_no = match.start()+1
            location = (line_no, column_no)

            # 这其实是一种很不好的实现,这样写只是为了证明论点
            # occurrences = index.get(word, [])
            # occurrences.append(location)
            # index[word] = occurrences

            # setdefault可以完成两个操作 1.得到 key=word的value,如果不存在就
            # 返回[] 2.向里面插入值
            index.setdefault(word, []).append(location)

# 以字母顺序打印出结果
for word in sorted(index, key=str.upper):
    print(word, index[word])

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值