第10章 字典(dictionaries)
字符串、列表和元组都使用整数作为索引,而字典可以使用任何不可变类型(immutable type)作为索引。
一种创建字典的方式是,新建一个空的字典,然后向其中添加元素:
1
2
3
4
5
|
>>> eng2sp = {} >>> eng2sp[’one’] = ’uno’ >>> eng2sp[’two’] = ’dos’ >>> print eng2sp {’one’: ’uno’, ’two’: ’dos’} |
字典中的元素以用逗号隔开的列表方式存在,每一项包含一个索引和一个值,两者之间用冒号隔开。
字典中索引被称为键(keys),字典中的元素被称为键值对。
另外一种创建字典的方式如:
1
|
>>> eng2sp = {’one’: ’uno’, ’two’: ’dos’, ’three’: ’tres’} |
当我们打印刚创建的字典的时候结果为:
1
2
|
>>> print eng2sp {’one’: ’uno’, ’three’: ’tres’, ’two’: ’dos’} |
键值对的顺序与输入的不同,但是我们不用太担心这个问题,因为我们使用字典主要是为了查找键所对应的值。
1
2
|
>>> print eng2sp[’two’] ’dos’ |
10.1 字典运算符
del语句将删除字典中的键值对。
1
2
3
4
5
6
|
>>> inventory = {’apples’: 430 , ’bananas’: 312 , ’oranges’: 525 , ’pears’: 217 } >>> print inventory {’oranges’: 525 , ’apples’: 430 , ’pears’: 217 , ’bananas’: 312 } >>> del inventory[’pears’] >>> print inventory {’oranges’: 525 , ’apples’: 430 , ’bananas’: 312 } |
可以用赋值语句来修改键值对的值
1
2
3
|
>>> inventory[’pears’] = 0 >>> print inventory {’oranges’: 525 , ’apples’: 430 , ’pears’: 0 , ’bananas’: 312 } |
len函数可以返回字典键值对的数量
1
2
|
>>> len (inventory) 4 |
10.2 字典方法
方法(method)与函数类似,获取一个实际参数,并且返回一个值,但是方法与函数的语法不同。
key方法将返回一个字典的所有键:
1
2
|
>>> eng2sp.keys() [’one’, ’three’, ’two’] |
方法的调用(invocation),上面我们称,我们调用了对象eng2sp的key。
value方法将返回字典的值
1
2
|
>>> eng2sp.values() [’uno’, ’tres’, ’dos’] |
item方法将以一个由元组构成的列表的方式显示一个字典的键值对
1
2
|
>>> eng2sp.items() [(’one’,’uno’), (’three’, ’tres’), (’two’, ’dos’)] |
如果方法取用一个参数的话,那么将使用与函数调用相似的语法,has_key方法将判断字典中是否含有键,返回布尔值。
1
2
3
4
|
>>> eng2sp.has_key(’one’) True >>> eng2sp.has_key(’deux’) False |
如果忽略对象而直接调用方法,将报错:
1
2
|
>>> has_key(’one’) NameError: has_key |
10.3 别名使用和拷贝(Aliasing and copying)
因为字典是可变的,因而要注意别名使用。
如果你希望修改一个字典中的键值对并且保留一个原来的字典的拷贝,可以使用copy方法
1
2
3
4
5
6
7
8
9
|
>>> opposites = {’up’: ’down’, ’right’: ’wrong’, ’true’: ’false’} >>> alias = opposites >>> copy = opposites.copy() >>> alias[’right’] = ’left’ >>> opposites[’right’] ’left’ >>> copy[’right’] = ’privilege’ >>> opposites[’right’] ’left’ |
修改alias的话,opposites也将改变,但是修改copy的话,opposites不变。
10.4 系数矩阵(Sparse matrices)
对于用列表表示的矩阵:
其中含有许多0,用字典的方式表示这个矩阵则简单的多:
1
|
matrix = {( 0 , 3 ): 1 , ( 2 , 1 ): 2 , ( 4 , 3 ): 3 } |
我们只需要用3个键值对来表示那些矩阵内的非0元素。使用元租作为键,使用整数作为键对值。
如果要存取矩阵内的元素,我们使用[]运算符。
1
2
|
matrix[ 0 , 3 ] 1 |
如果我们要访问矩阵内一个为0的元素时会出现错误:
1
2
|
>>> matrix[ 1 , 3 ] KeyError: ( 1 , 3 ) |
解决这个错误的方法是使用get方法:
1
2
3
4
|
>>> matrix.get(( 0 , 3 ), 0 ) 1 >>> matrix.get(( 1 , 3 ), 0 ) 0 |
get方法的第一个实际参数是键,第二个实际参数是当索引不在字典中时get方法会返回的值。
10.5 提示(Hints)
使用5.7中的fibonacci函数,当提供的实际参数越大的时候,函数调用的时间越长。
使用一个调用图来看原因:
调用图是一系列函数框架构成的图,这些框架被用线指向其所调用的函数框架。
fibonacci(4)调用fibonacci(3)和fibonacci(2)
fibonacci(3)调用fibonacci(2)和fibonacci(1)
fibonacci(2)调用fibonacci(1)和fibonacci(0)
随着给定的fibonacci(1)和fibonacci(0)被调用的次数非常多。
一个更好的解决方法是将每次计算的结果保存进一个字典里。将之前计算过的结果保存下来留给以后利用,这种方法被称为提示(hint)。
下面是使用提示方法来实现斐波那契函数
1
2
3
4
5
6
7
8
|
previous = { 0 : 1 , 1 : 1 } def fibonacci(n): if previous.has_key(n): return previous[n] else : newValue = fibonacci(n - 1 ) + fibonacci(n - 2 ) previous[n] = newValue return newValue |
字典previous将记录下每一个已经计算过了的斐波那契数。每次调用这个函数的时候,将检查字典中是否已经保存有要计算的斐波那契数,如果已经有了的话将直接返回该值,而不需要再进行任何重复调用;如果字典中没有的话,函数将在字典中已经保存的斐波那契数的基础上进型调用计算,并将每一个计算出的数保存进字典中。
如果我们使用新编写的函数计算fibonacci(50)的话:
1
2
|
>>> fibonacci( 50 ) 20365011074L |
实际的结果应该是20,365,011,074,上面结果末尾的L表示该结果对于Python的整数类型来说太大了,Python自动将其转化成了长整数(long integer)。
10.6 长整数(Long integers)
创建长整数类型变量的方法有:
1
2
|
>>> type ( 1L ) < type ’ long ’> |
以及
1
2
3
4
5
6
|
>>> long ( 1 ) 1L >>> long ( 3.9 ) 3L >>> long (’ 57 ’) 57L |
对于整数适用的所有数学运算符以及一般代码均同样适用于长整数。
10.7 计算字母(Counting letters)
字典提供一个简易有效的方法来生成一个直方图:
1
2
3
4
5
6
|
>>> letterCounts = {} >>> for letter in "Mississippi" : ... letterCounts[letter] = letterCounts.get (letter, 0 ) + 1 ... >>> letterCounts {’M’: 1 , ’s’: 4 , ’p’: 2 , ’i’: 4 } |
我们创建一个空的字典,然后对于Mississippi内的每一个字母,计算机将检查字典内记录的该字母出现的次数并且将其增加。
我们可以使用sort和item方法来使其按字母表顺序排列:
1
2
3
4
|
>>> letterItems = letterCounts.items() >>> letterItems.sort() >>> print letterItems [(’M’, 1 ), (’i’, 4 ), (’p’, 2 ), (’s’, 4 )] |
sort是我们在学习列表的时候见过的方法,append,extend以及reverse等对列表适用的方法也适用于字典。
第11章 文件和异常
要打开一个文件,你需要制定文件名,以及你希望读还是写。
打开一个文件将创建一个新的文件对象,下例中f指的便是新的文件对象。
1
2
3
|
>>> f = open ( "test.dat" , "w" ) >>> print f < open file ’test.dat’, mode ’w’ at fe820> |
open函数使用两个实际参数,第一个是参数是文件名,第二个是模式。模式”w”意味着我们打开文件是为了编写。
如果没有名叫test.dat的文件,将会创建该文件。如果存在该文件,其将被现在正被编写的文件所替代。
我们对文件使用write方法来将数据写入文件中:
1
2
|
>>> f.write( "Now is the time" ) >>> f.write( "to close the file" ) |
将文档close将告诉系统我们已经完成了编写,使得文档可以被读取:
1
|
>>> f.close() |
这次我们使用模式”r”打开文档:
1
|
>>> f = open ( "test.dat" , "r" ) |
使用’r'模式打开文档,如果要打开的文档不存在,将报错
1
2
|
>>> f = open ( "test.cat" , "r" ) IOError: [Errno 2 ] No such file or directory: ’test.cat’ |
如果不使用实际参数,read方法将读取文档的所有内容:
1
2
3
|
>>> text = f.read() >>> print text Now is the timeto close the file |
read方法,使用整数作为实际参数, 将读文档内指定个数个字符:
1
2
3
|
>>> f = open ( "test.dat" , "r" ) >>> print f.read( 5 ) Now i |
如果文档中的剩余的字符数不到参数所要求的数量,剩下字符将全部显示,如果已经到了文档的末尾将返回一个空白的字符串:
1
2
3
4
|
>>> print f.read( 1000006 ) s the timeto close the file >>> print f.read() >>> |
下面这个函数将拷贝一个文档,每次读取最多50个字符进行编写。
1
2
3
4
5
6
7
8
9
10
11
|
def copyFile(oldFile, newFile): f1 = open (oldFile, "r" ) f2 = open (newFile, "w" ) while True : text = f1.read( 50 ) if text = = "": break f2.write(text) f1.close() f2.close() return |
这里出现了新的语句,break语句,执行break语句的时候将立刻退出循环,从循环后面的第一行代码开始执行。
11.2 文本文件
下面我们建立一个含有三行不同文本并且行与行之间用换行号(new-line)字符隔开的文本文件:
1
2
3
|
>>> f = open ( "test.dat" , "w" ) >>> f.write( "line onenline twonline threen" ) >>> f.close() |
readline方法将读取文档中包含换行号在内的一行文本。
1
2
3
4
|
>>> f = open ( "test.dat" , "r" ) >>> print f.readline() line one >>> |
readlines方法将以字符串构成的列表的方式读取文本文档内的剩余内容:
1
2
|
>>> print f.readlines() [’line two 012 ’, ’line three 012 ’] |
这里换行号以换码顺序(escape sequence)方式显示。
到文档结尾处时,readline方法将返回空字符串,readlines方法将返回一个空的列表:
1
2
3
|
>>> print f.readline() >>> print f.readlines() [] |
下面是一个行处理程序,
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def filterFile(oldFile, newFile): f1 = open (oldFile, "r" ) f2 = open (newFile, "w" ) while True : text = f1.readline() if text = = "": break if text[ 0 ] = = ’ #’: continue f2.write(text) f1.close() f2.close() return |
该程序以r模式打开要处理的文档,以w模式打开/新建处理后将保存成为的文档。一行一行地读取第一个文档,将其中不以#开头的每一行内容保存至后一个文档中去。
11.2 写变量
write方法的实际参数需要是字符串类型,我们需要将其他类型先转变称为字符串,才可以使用write方法将其写入文档。
最简单的方法是使用str函数
1
2
|
>>> x = 52 >>> f.write ( str (x)) |
另一种方式是使用格式运算符(format operator)%,对整数而言%是百分比号,但是在第一个运算项为字符串时%则变成格式运算符。
第一个运算项是格式字符串(format string)第二个运算项是一个表达式元组,表达式的结果将按照格式字符串转化为指定格式。
以格式序列%d(format sequence)为例:
1
2
3
|
>>> cars = 52 >>> "%d" % cars ’ 52 ’ |
格式序列可以出现在格式字符串的任何位置,我们可以使用这种方式将一个值写入一个句子中:
1
2
3
|
>>> cars = 52 >>> "In July we sold %d cars." % cars ’In July we sold 52 cars.’ |
格式序列”%f”为浮点数,而”%s”为字符串:
1
2
|
>>> "In %d days we made %f million %s." % ( 34 , 6.1 ,’dollars’) ’In 34 days we made 6.100000 million dollars.’ |
按照系统默认,浮点数将显示小数点后六位。
表达式元组的数量和格式序列的数量需要一致:
1
2
3
4
|
>>> "%d %d %d" % ( 1 , 2 ) TypeError: not enough arguments for format string >>> "%d" % ’dollars’ TypeError: illegal argument type for built - in operation |
我们可以在格式序列中精确指定数字的位数,格式序列中的数字位数是最小位数,不足的话将以空格补上:
1
2
3
4
|
>>> "%6d" % 62 ’ 62 ’ >>> "%12f" % 6.1 ’ 6.100000 ’ |
如果该值为负数,那么将在末尾补上空格。
1
2
|
>>> "%-6d" % 62 ’ 62 ’ |
对于浮点数,可以指定总位数以及小数位的位数:
1
2
|
>>> "%12.2f" % 6.1 ’ 6.10 ’ |
下面的函数将以指定格式打印一个字典内的内容:
1
2
3
4
5
|
def report (wages) : students = wages.keys() students.sort() for student in students : print "%-20s %12.2f" % (student, wages[student]) |
我们建立一个小型字典来测试这个函数的功能:
1
2
3
4
5
|
>>> wages = {’mary’: 6.23 , ’joe’: 5.45 , ’joshua’: 4.25 } >>> report (wages) joe 5.45 joshua 4.25 mary 6.23 |
我们可以控制值的宽度来保证表格的格式统一。
11.3 目录
如果你要打开别处的某个文件,你需要指定其路径(path),即文件所在的目录:
1
2
3
|
>>> f = open ( "/usr/share/dict/words" , "r" ) >>> print f.readline() Aarhus |
11.4 序列化(pickling)
因为Python中将内容写进一个文档需要使用字符串格式,因此读取的时候读取出来的类型也是字符串。
1
2
3
4
|
>>> f.write ( str ( 12.3 )) >>> f.write ( str ([ 1 , 2 , 3 ])) >>> f.readline() ’ 12.3 [ 1 , 2 , 3 ]’ |
不仅如此,你甚至无法知道哪里是一个值的结尾,哪里是一个值的开头。
pickling得名的原因是因为它保存数据的结构。pickle是一个模块,你需要加载该模块才可以使用。
1
2
|
>>> import pickle >>> f = open ( "test.pck" , "w" ) |
要保存数据结构,你可以使用dump方法,然后正常方式关闭文档。
1
2
3
|
>>> pickle.dump( 12.3 , f) >>> pickle.dump([ 1 , 2 , 3 ], f) >>> f.close() |
然后我们可以读取这个被dump过了的文档:
1
2
3
4
5
6
7
8
9
10
11
|
>>> f = open ( "test.pck" , "r" ) >>> x = pickle.load(f) >>> x 12.3 >>> type (x) < type ’ float ’> >>> y = pickle.load(f) >>> y [ 1 , 2 , 3 ] >>> type (y) < type ’ list ’> |
每次我们使用load方法,我们都获取文档中的一个值,完整地包含其原有类型。
11.5 异常
每一次出现运行时,将产生一个异常,通常情况下程序会停止并输出一条错误提示信息。
1
2
|
>>> print 55 / 0 ZeroDivisionError: integer division or modulo |
尝试访问一个不存在的列表:
1
2
3
|
>>> a = [] >>> print a[ 5 ] IndexError: list index out of range |
尝试访问字典中不存在的键
1
2
3
|
>>> b = {} >>> print b[’what’] KeyError: what |
尝试打开不存在的文档
1
2
|
>>> f = open ( "Idontexist" , "r" ) IOError: [Errno 2 ] No such file or directory: ’Idontexist’ |
我们可以使用try语句和except语句来方式防止程序因异常而停止。
1
2
3
4
5
|
filename = raw_input (’Enter a file name: ’) try : f = open (filename, "r" ) except IOError: print ’There is no file named’, filename |
我们利用try语句和except语句实现对一个文档是否存在的检验,并这个功能封装进一个函数中:
1
2
3
4
5
6
7
|
def exists(filename): try : f = open (filename) f.close() return True except IOError: return False |
你可以使用多个except语句来针对多种可能的异常
当你的程序发现了错误的情况时,你可以使用raise语句,使其产生一个异常
1
2
3
4
5
|
def inputNumber () : x = input (’Pick a number: ’) if x = = 17 : raise ValueError, ’ 17 is a bad number’ return x |
raise语句使用两个实际参数,第一个是错误类型,第二个是指定的异常提示信息。
其他几种错误类型有:TypeError, KeyError 以及NotImplementedError.
1
2
3
|
>>> inputNumber () Pick a number: 17 ValueError: 17 is a bad number |
第12章 类型和对象
12.1 用户定义的复合类型
点(point),就像2元坐标系中的点,其坐标用(x,y)表示。
如果要在Python中表示一个点,快捷的方法是使用列表和元组。另外一种方法是使用一个新的用户定义的复合类型,也叫一个class。
类型定义:
1
2
|
class Point: pass |
使用这种自定义类型的东西叫实例(instances)或者对象(object)。
建立实例叫实例化(instantiation)
实例化一个对象,我们需要使用Point函数:
1
|
blank = Point() |
像Point这样可以创建新对象的函数被称为构造器(constructor)
12.2 属性(attributes)
使用点号向一个实例中添加数据:
1
2
|
>>> blank.x = 3.0 >>> blank.y = 4.0 |
选取一个实例中的一个东西,与从一个模块中选取一个变量的语法类似,这里被选取的东西叫做属性(attributes)。
表示了以上赋值结果的状态图:
通过同样的语法可以对一个属性进行读取:
1
2
3
4
5
|
>>> print blank.y 4.0 >>> x = blank.x >>> print x 3.0 |
表达式blank.x的含义是,找到blank指向的对象,然后从中获得x的值。这样可以区分变量a和属性a,不至于混淆。
将点号用于表达式是复合语法规则的:
1
2
|
print ’(’ + str (blank.x) + ’, ’ + str (blank.y) + ’)’ distanceSquared = blank.x * blank.x + blank.y * blank.y |
第一行的输出结果是(3.0,4.0)
第二行的计算结果是将25赋值给distanceSquared
打印blank的结果:
1
2
|
>>> print blank <__main__.Point instance at 80f8e70 > |
80f8e70是该对象的独有的标识符,使用16进制数。
12.3 作为实际参数的实例
1
2
|
def printPoint(p): print ’(’ + str (p.x) + ’, ’ + str (p.y) + ’)’ |
printPoint(blank)的输出结果必然是(3.0,4.0)
12.4 同一性
当你说两个点是相同的时候,可能有两种含义,一是这两个点的坐标相同,但是这两个点不是同一个点,二是这两个点实际上是同一个点。
1
2
3
4
5
6
7
8
|
>>> p1 = Point() >>> p1.x = 3 >>> p1.y = 4 >>> p2 = Point() >>> p2.x = 3 >>> p2.y = 4 >>> p1 is p2 False |
表示p1和p2是两个坐标相同的点,但是两个点不是同一个点。
1
2
3
|
>>> p2 = p1 >>> p1 is p2 True |
如果我们将p1赋值给p2那么两个点就是同一个对象的别名了。
这种等于是一种浅等于(shallow equality),因为它只比较了参引,没有对实际内容进行比较。
编写一个函数来进行深等于(deep equality):
1
2
|
def samePoint(p1, p2) : return (p1.x = = p2.x) and (p1.y = = p2.y) |
如果我们建立了两个含有同样数据的对象,我们可以使用samePoint来看它们是否表示的是同样的点。
1
2
3
4
5
6
7
8
|
>>> p1 = Point() >>> p1.x = 3 >>> p1.y = 4 >>> p2 = Point() >>> p2.x = 3 >>> p2.y = 4 >>> samePoint(p1, p2) True |
如果两个变量同时指向同一个对象,那么必然同时是浅等于和深等于的关系。
12.5 矩形(Rectangles)
定义新类型以及其实例:
1
2
3
4
5
|
class Rectangle: pass box = Rectangle() box.width = 100.0 box.height = 200.0 |
我们使用一个二元坐标来指定矩形的左上方的定点。
1
2
3
|
box.corner = Point() box.corner.x = 0.0 box.corner.y = 0.0 |
我们可以实现对象的嵌套。
用状态图表示为:
12.6 作为返回结果的实例(Instances as return values)
编写函数findCenter,该函数取用一个矩形作为实际参数,返回该矩形的中心点:
1
2
3
4
5
|
def findCenter(box): p = Point() p.x = box.corner.x + box.width / 2.0 p.y = box.corner.y - box.height / 2.0 return p |
将上例的box作为参数带入。
1
2
3
|
>>> center = findCenter(box) >>> printPoint(center) ( 50.0 , - 100.0 ) |
12.7 对象的可变性
对上面矩形的长宽进行修改:
1
2
|
box.width = box.width + 50 box.height = box.height + 100 |
将这个代码封装进一个方法中去:
1
2
3
|
def growRect(box, dwidth, dheight) : box.width = box.width + dwidth box.height = box.height + dheight |
使用情况:
1
2
3
4
5
6
7
|
>>> bob = Rectangle() >>> bob.width = 100.0 >>> bob.height = 200.0 >>> bob.corner = Point() >>> bob.corner.x = 0.0 >>> bob.corner.y = 0.0 >>> growRect(bob, 50 , 100 ) |
12.8 拷贝
别名使用会使得程序变得难懂,而且容易出错,一个变量的改变可能会出乎意图地改变别的量。
copy模块中的copy函数可以用来复制任何对象
1
2
3
4
5
6
7
8
9
|
>>> import copy >>> p1 = Point() >>> p1.x = 3 >>> p1.y = 4 >>> p2 = copy.copy(p1) >>> p1 = = p2 False >>> samePoint(p1, p2) True |
p1和p2指向的变量含有相同的数据,但是p1和p2不是指向同一个变量。
拷贝一个简单的就像点这样的对象,对象不含有任何对象的嵌套,叫做钱拷贝(shallow copying)。
对于矩形那样的类型,拷贝一个矩形,则新旧矩形都指向同一个点对象。
用状态图表示为:
这时候对一个矩形使用moveRect将影响另一个矩形。
copy模块中含有一个深拷贝函数deepcopy:
1
|
>>> b2 = copy.deepcopy(b1) |
下面的growRect将在不改变原有矩形的前提下返回一个增加了长宽的新矩形。
1
2
3
4
5
6
|
def growRect(box, dwidth, dheight) : import copy newBox = copy.deepcopy(box) newBox.width = newBox.width + dwidth newBox.height = newBox.height + dheight return newBox |
How to Think Like a Computer Scientist
Learning with Python
by Allen Downey, Jeff Elkner and Chris Meyers.
PDF Version : Link