例:>>>print(r’c:\Windows’)
c:\Windows
print(r’c:\Windows.filrs \frond\bar’)
c:\Windows.filrs \frond\bar
用列表来表示:所有元素都放在方括号内,并用逗号隔开
一个大仓库,你可以随时往里边添加和删除任何东西。
封闭的列表,一旦定义,就不可改变(不能添加、删除或修改)。
适用于所有序列的操作:索引,切片,相加,相乘,和成员资格检查。
greeting[0]
‘h’
1.字符串就是有字符组成的序列,索引0指向第一个元素。(python中没有专门表示字符的类型,因此一个字符就是只包含一个元素的字符串)
2.负数索引:从右开始往左数,因此-1是最后一个元素的位置。
numbers[7:10]
[8, 9, 10]
1.第一个索引指定的元素包含在切片里,但第二个索引指定的元素不包含在切片里。
numbers[-3,-1]
[8, 9]
2.从列表末尾开始数,可以用负数索引
numbers[-3,0]
[]
3.如果第一个索引指定的元素位于第二个索引指定的元素后面,结果就为空序列。
numbers[-3,]
[8, 9, 10]
4.如果切片结束于序列末尾,可省略第二个索引。
numbers[:,3]
[1,2,3]
5.如果切片开始于序列开头,可省略第一个索引。
numbers[:]
[1,2,3,4,5,6,7,8,9,10]
6.复制整个序列
numbers[0:10:2] (2:步长,意味着从起点到终点每隔一个元素提取一个元素)
[1,3,5,7,9]
7.* 特殊的两种
像这两种情况:步长为正数时,它从起点移到终点,而步长为负数时,它从终点移到起点
number[5::-2]
[6,4,2]
number[:5:-2]
[10,8]
[1,2,3]+[4,5,6]
[1, 2, 3, 4, 5, 6]
‘hello’+‘worid’
‘helloworid’
[1,2,3]+‘hello’
Traceback (most recent call last):
File “
[1,2,3]+‘hello’
TypeError: can only concatenate list (not “str”) to list
‘pyhthon’*5
‘pyhthonpyhthonpyhthonpyhthonpyhthon’
permissions = ‘rw’
‘w’ in permissions
True
‘x’ in permissions
False
list() 使用字符串来创造列表
list(‘hello’)
[‘h’, ‘e’, ‘l’, ‘l’, ‘o’]
x = [1,1,1]
x[1] = 2 -------给特定位置的元素赋值
x
[1, 2, 1]
names = [‘hello’,‘tian’,‘jian’,‘zhen’]
del names[2] ------删除特定位置的元素
names
[‘hello’, ‘tian’, ‘zhen’]
name = list (‘perl’)
name
[‘p’, ‘e’, ‘r’, ‘l’]
name[2:]=list(‘ar’)
name
[‘p’, ‘e’, ‘a’, ‘r’]
st = [1,2,3,4]
lst.append(4)
lst
[1, 2, 3, 4, 4]
st = [1,2,3,4]
lst.clear()
lst
[]
b = a.copy()
b[1] = 4
a
[1, 2, 3]
b
[1, 4, 3]
[‘to’,‘be’,‘or’,‘not’,‘to’,‘be’].count(‘to’)
2
x = [[1,2],1,1,[2,1,[1,2]]]
x.count(1)
2
x.count([1,2])
1
a = [1,2,3]
b = [4,5,6]
a.extend(b)
a
[1, 2, 3, 4, 5, 6]
knights = [‘we’,‘are’,‘the’,‘knights’,‘who’,‘say’,‘ni’]
knights.index(‘who’)
4
numbers = [1,2,3,4,5,6,7]
numbers.insert(3,‘four’)
numbers
[1, 2, 3, ‘four’, 4, 5, 6, 7]
pop:从列表中删除一个元素(末尾为最后一个元素),并返回这个元素。----是唯一既修改列表有返回非None的值得列表方法。----pop可实现一种常见的数据结构–栈(先进后出)
x = [1,2,3]
x.pop()
3
x
[1, 2]
x.pop(0)
1
x
[2]
x = [‘to’,‘be’,‘or’,‘not’,‘to’,‘be’]
x.remove(‘be’)
x
[‘to’, ‘or’, ‘not’, ‘to’, ‘be’]
x = [1,2,3]
x.reverse()
x
[3, 2, 1]
reversed;按相反的顺序迭代序列,不返回列表,而是返回一个迭代器
x = [1,2,3]
list(reversed(x))
[3, 2, 1]
x = [4,6,2,1,7,9]
x.sort()
x
[1, 2, 4, 6, 7, 9]
x = [‘aardvark’,‘balone’,‘acme’,‘add’,‘aerate’]
x.sort(key=len)
x
[‘add’, ‘acme’, ‘balone’, ‘aerate’, ‘aardvark’]
x = [4,6,2,1,7,9]
x.sort(reverse=True)
x
[9, 7, 6, 4, 2, 1]
tuple([1,2,3])
(1, 2, 3)
tuple(‘abc’)
(‘a’, ‘b’, ‘c’)
tuple((1,2,3))
(1, 2, 3)
-----------------------------字符串是不可变的
format = "hello,%s.%senough for ya?"
values = ('world','hot')
format % values
'hello,world.hotenough for ya?'
%s称为转换说明符,指出了药将值插入什么地方。s意味着将值视为字符串进行格式设置
字段名:索引或标识符,指出要设置哪个值得格式并使用结果来替换改字段
转换标志:跟在叹号后面的单个字符,当前支持的字符包括r(repr),s(str),a(ascii)
格式说明符;跟在冒号后面的表达式。
在最简单的情况下,只需要向foemat提供要设置其格式的未命名参数,并在格式字符串中使用未命名字段。
“{foo}{}{bar}{}”.format(1,2,bar=4,foo=3)
‘3142’
message="The number is {num}".format(num=42)
print(message )
The number is 42-----结果
b :将整数表示为二进制数
c :将整数解读为Unicode码点
d :将整数视为十进制数进行处理,这是整数默认使用的说明符
e :使用科学表示法来表示小数(用e来表示指数)
E :与e相同,但使用E来表示指数
f :将小数表示为定点数
F :与f相同,但对于特殊值(nan和inf),使用大写表示
g :自动在定点表示法和科学表示法之间做出选择。这是默认用于小数的说明符,但在默认情况下至少有1位小数
G :与g相同,但使用大写来表示指数和特殊值
n :与g相同,但插入随区域而异的数字分隔符
o :将整数表示为八进制数
s :保持字符串的格式不变,这是默认用于字符串的说明符
x :将整数表示为十六进制数并使用小写字母
X :与x相同,但使用大写字母
% :将数表示为百分比值(乘以100,按说明符f设置格式,再在后面加上%)
设置浮点数的格式时,默认在小数点后面显示6位小数
数和字符串的对齐方式不同,如下:
b="{num:10}".format(num=3)
print(b)
3
c="{name:10}".format(name="Bob")
print(c)
Bob
精度
d="pi day is {pi:.2f}".format(pi=pi)
print(d)
在指定的宽度和精度的数前面,可添加一个标志,这个标志可以是0,+,-或空格,其中零表示使用零来填充数字
要指定左对齐,右对齐和居中可分别使用<,>,和^
print('{0:<10.2f}\n{0:^10.2f}\n{0:>10.2f}'format)
3.14
3.14
3.14
说明符等号
print('{0:10.2f}\n{1:10.2f}'.format(pi,-pi))
3.14
-3.14
print('{0:10.2f}\n{1:=10.2f}'.format(pi,-pi))
3.14
- 3.14
模块string未死
虽然字符串方法完全盖住了模块string的风头,但这个模块包含一些字符串没有的常量
和函数。下面就是模块string中几个很有用的常量①。
string.digits:包含数字0~9的字符串。
string.ascii_letters:包含所有ASCII字母(大写和小写)的字符串。
string.ascii_lowercase:包含所有小写ASCII字母的字符串。
string.printable:包含所有可打印的ASCII字符的字符串。
string.punctuation:包含所有ASCII标点字符的字符串。
string.ascii_uppercase:包含所有大写ASCII字母的字符串。
虽然说的是ASCII字符,但值实际上是未解码的Unicode字符串。
A="The Middle by Jimmy Eat World".center(39)
print(A)
The Middle by Jimmy Eat World
B="The Middle by Jimmy Eat World".center(39,"*")
print(B)
*****The Middle by Jimmy Eat World*****
C='With a moo-moo here,and a moo-moo there'.find('moo')
print(C)
7
B='***SPAM * for * everyone!!!***'.strip('*!')
print(B)
SPAM * for * everyone
然而,使用translate前必须创建一个转换表。这个转换表指出了不同Unicode码点之间的转
换关系。要创建转换表,可对字符串类型str调用方法maketrans,这个方法接受两个参数:两个
长度相同的字符串,它们指定要将第一个字符串中的每个字符都替换为第二个字符串中的相应字
符
字典旨在让你能够轻松的找到特定的单词(键),此次获悉其定义(值),是python中唯一的内置映射类型
字典的用途:1.表示棋盘的状态,其中每个键都是由坐标组成的元组
2.存储文件修改时间,其中的键为文件名
3.数字电话/地址簿
字典以类似下面的方式表示:
phonebook = {
'Alice':'2341','Beth':'9102','Cecil':'3258'}
items = [('name','Gumby'),('age',42)]
d = dict(items)
print(d)
{
'name': 'Gumby', 'age': 42}
len(d)返回字典d包含的项(键值对)数。
d[k]返回与键k相关联的值。
d[k] = v将值v关联到键k。
del d[k]删除键为k的项。
k in d检查字典d是否包含键为k的项
键的类型:字典中的键可以是整数,但并非必须是整数。字典中的键可以是任何不可变的类型,如浮点数(实数)、字符串或元组。
自动添加:即便是字典中原本没有的键,也可以给它赋值,这将在字典中创建一个新项。然而,如果不使用append或其他类似的方法,就不能给列表中没有的元素赋值。
成员资格:表达式k in d(其中d是一个字典)查找的是键而不是值,而表达式v in l(其中l是一个列表)查找的是值而不是索引。
x = []
x[42] = 'Foobar' 赋给一个空列表中索引为42的元素
print(x)
IndexError: list assignment index out of range
x = {
}
x[42] = 'Foobar' 赋给一个空字典的键42
print(x)
{
42: 'Foobar'}
可在字典中包含各种信息,这样只需在格式字符串中提取所需的信息即可。为此,必须使用format_map来指出你将通过一个映射来提供所需的信息。
phonebook = {
'Beth':'9102','Alice':'2341','Cecil':'3258'}
A = "Cecil's phone number is {Cecil}.".format_map(phonebook)
print(A)
Cecil's phone number is 3258.
d = {
}
d['name'] = 'Gumby'
d['age'] = 42
print(d)
returned_value = d.clear()
print(d)
print(returned_value)
{
'name': 'Gumby', 'age': 42}
{
}
None
x = {
} ------将一个空字典赋给x来“清空”它
y = x ------这对y没有任何影响
x['key'] = 'value'
print(y) -----它依然指向原来的字典
x = {
}
print(x)
print(y)
{
'key': 'value'}
{
}
{
'key': 'value'}
第二种情况
x = {
}
y = x
x['key'] = 'value'
print(y)
x.clear() ------删除原来字典额所有元素,要用clear
print(y) ------因此y也是空的
{
'key': 'value'}
{
}
copy()当替换副本中的值时,原件不受影响。然而,如果修改副本中的值(就地修改而不是替换),原件也将发生变化,因为原件指向的也是被修改的值为避免这种问题,一种办法是执行深复制,即同时复制值及其包含的所有值,
copy():浅复制。deepcopy():深复制
from copy import deepcopy
d = {
}
d['names'] = ['Alfred','Bertrand']
c = d.copy()
dc = deepcopy(d)
d['names'].append('Clive')
print(c)
print(dc)
{
'names': ['Alfred', 'Bertrand', 'Clive']}
{
'names': ['Alfred', 'Bertrand']}
A = {
}.fromkeys(['name','age']) ------最前面的空的花括号的意思是:首先创建一个空字典。
print(A)
{
'name': None, 'age': None}
A = dict.fromkeys(['name','age'],'(unknown)') ---可以不先创建一个空字典,直接对dict调用方法,
print(A) -----dict是所有字典所属的类型
------如果不想使用默认值,可提供特定的值
{
'name': '(unknown)', 'age': '(unknown)'}
d = {
}
print(d.get('name'))
None
d = {
}
d['name'] = 'Gumby'
d.setdefault('name','N/A')
print(d)
{
'name': 'Gumby'}
d = {
}
d[1] = 1
d[2] = 2
d[3] = 3
d[4] = 4
print(d.values())
dict_values([1, 2, 3, 4])
映射:映射让你能够使用任何不可变的对象(最常用的是字符串和元组)来标识其元素。python中只有一种映射类型,那就是字典。
将字符串格式设置功能用于字典:要对字典执行字符串格式设置操作,不能使用format和命名参数,而必须使用format_map
字典方法:字典有很多方法,这些方法的调用方式与列表和字符串的方法相同
print("I","wish","to","register","a","complaint",sep="_")---可自定义分隔符
I_wish_to_register_a_complaint
values = 1,2,3
print(values)
x,y,z= values
print(x)
(1, 2, 3)
1
*假设要从字典中随便获取一个键-值对,可使用方法popitem
scoundrel = {
'name':'robin','girlfriend':'marion'}
key,value = scoundrel.popitem()
print(key)
print(value)
girlfriend
marion
*可使用星号运算符来收集多余的值。这样无需确保值和变量的个数相同。
a,b,*rest = [1,2,3,4]
print(rest)
[3, 4]
x = y =somefunction()与y =somefunction() x = y 等价
x = 2
x += 1
x *=2
print(x)
6
fnord = 'foo'
fnord += 'bar'
fnord *= 2
print(fnord)
foobarfoobar
注意 也可使用制表符来缩进代码块。Python将制表符解释为移到下一个制表位(相邻制表位
相距8个空格),但标准(也是更佳的)做法是只使用空格(而不使用制表符)来缩进,
且每级缩进4个空格。
在同一个代码块中,各行代码的缩进量必须相同。
在Python中,使用冒号(:)指出接下来是一个代码块,并将该代码块中的每行代码都缩进相同的程度。发现缩进量与之前相同时,你就知道当前代码块到此结束了。
布尔值True和False属于类型bool
print(bool('I think , therefore I am'))
print(bool(42))
print(bool(''))
print(bool(0))
True
True
False
False
注意: 虽然[]和"“都为假(即bool([]) == bool(”") == False),但它们并不相等(即[] != “”)。
对其他各种为假的对象来说,情况亦如此(一个更显而易见的例子是() != False)。
表 达 式 描 述
x == y x 等于y
x < y x小于y
x > y x大于y
x >= y x大于或等于y
x <= y x小于或等于y
x != y x不等于y
x is y x和y是同一个对象
x is not y x和y是不同的对象
x in y x是容器(如序列)y的成员
x not in y x不是容器(如序列)y的成员
如果知道必须满足特定条件,程序才能正确地运行,可在程序中添加assert语句充当检查点,
for number in range(1,101):
print(number)
1......100-------包含起始位置,不包含结束位置
范围类似于切片。它们包含起始位置(这里为0),但不包含结束位置(这里为10)。在很多
情况下,你都希望范围的起始位置为0。实际上,如果只提供了一个位置,将把这个位置视为结
束位置,并假定起始位置为0。
d = {
'x':1,'y':2,'z':3}
for key in d:-----要遍历字典的所有关键字,可像遍历序列那样使用普通的for语句。
print(key,'corresponds to',d[key])
x corresponds to 1
y corresponds to 2
z corresponds to 3
也可使用keys等字典方法来获取所有的键。如果只对值感兴趣,可使用d.values。你可能还
记得,d.items以元组的方式返回键值对。for循环的优点之一是,可在其中使用序列解包。
字典元素的排列顺序是不确定的。换而言之,迭代字典的键或值时,一定会处理所有的
键或值,但不知道处理的顺序。如果顺序很重要,可将键或值存储在一个列表中并对列
表排序,再进行迭代。要让映射记住其项的插入顺序,可使用模块collections中的
OrderedDict类。
names = ['anne','beth','george','damon']
ages = [12,45,32,102]
for i in range(len(names)):
print(names[i],'is',ages[i],'years old')
anne is 12 years old
beth is 45 years old
george is 32 years old
damon is 102 years old
i是用作循环索引的变量的标准名称。一个很有用的并行迭代工具是内置函数zip,它将两个
序列“缝合”起来,并返回一个由元组组成的序列。返回值是一个适合迭代的对象,要查看其内
容,可使用list将其转换为列表。
函数zip可用于“缝合”任意数量的序列。需要指出的是,当序列的长度不同时,函数zip将
在最短的序列用完后停止“缝合”。
strings = ['xxs','sss','xxx',]
for index,string in enumerate(strings):
if 'xxx'in string:
strings[index] = '[censored]'
print(strings)
--------你需要在迭代对象序列的同时获取当前对象的索引。例如,你可能想替换一
个字符串列表中所有包含子串'xxx'的字符串。
['xxs', 'sss', '[censored]']
这个函数让你能够迭代索引值对,其中的索引是自动提供的。
反转和排序后的版本。
print(sorted([4,3,6,8,3]))
[3, 3, 4, 6, 8]
print(list(reversed('hello,world!')))
['!', 'd', 'l', 'r', 'o', 'w', ',', 'o', 'l', 'l', 'e', 'h']
sorted返回一个列表,而reversed像zip那样返回一个更神秘的可迭代对象
不能对它执行索引或切片操作,也不能直接对它调用列表的方法。要执行这些操作,可先使用list对返回的对象进行转换。
from math import sqrt
for n in range(99,0,-1):
root = sqrt(n)
if root == int(root):
print(n)
break
81
*注意到我向range传递了第三个参数——步长,即序列中相邻数的差。通过将步长设置为负数,可让range向下迭代,
语句continue没有break用得多。它结束当前迭代,并跳到下一次迭代开头。这基本上意味着跳过循环体中余下的语句,但不结束循环。这
while True:
word = input('please enter a word:')
if not word:break
print('the word was',word)
please enter a word:enter
the word was enter
please enter a word:
while True导致循环永不结束,但你将条件放在了循环体内的一条if语句中,而这条if语句
将在条件满足时调用break。这说明并非只能像常规while循环那样在循环开头结束循环,而是可
在循环体的任何地方结束循环。if/break行将整个循环分成两部分:第一部分负责设置(如果使
用常规while循环,将重复这部分),第二部分在循环条件为真时使用第一部分初始化的数据。
如何判断循环是提前结束还是正常结束的呢?
1.可在循环开始前定义一个布尔变量并将其设置为False,再在跳出循环时将其设置为True。这样就可在循环后面使用一条if语句来判断循环是否是提前结束的。
2.一种更简单的办法是在循环中添加一条else子句
from math import sqrt
for n in range(99,81,-1):
root = sqrt(n)
if root == int(root):
print(n)
break
else:
print("didn't find it!")
didn't find it!
[x * x for x in range(10)]-----------这个列表由range(10)内每个值的平方组成
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[x*x for x in range(10) if x % 3 == 0] ---想打印那些能被3整除的平
方值,
[0, 9, 36, 81]
girls = ['alice','bernice','clarice']
boys = ['chris','arnold','bob']
letterGirls = {
}
for girl in girls:
letterGirls.setdefault(girl[0],[]).append(girl)
print([b+'+'+g for b in boys for g in letterGirls[b[0]]])
--------首字母相同的男孩和女孩配对。
['chris+clarice', 'arnold+alice', 'bob+bernice']
这个程序创建一个名为letterGirls的字典,其中每项的键都是一个字母,而值为以这个字母打头的女孩名字组成的列表。创建这个字典后,列表推导遍历所有的男孩,并查找名字首字母与当前男孩相同的所有女孩。这样,这个列表推导就无需尝试所有的男孩和女孩组合并检查他们的名字首字母是否相同了。
squeares = {
i:"{} squared is {}".format(i,i**2) for i in range(10)}
print(squeares[8])
8 squared is 64
在列表推导中,for前面只有一个表达式,而在字典推导中,for前面有两个用冒号分隔的表达式。这两个表达式分别为键及其对应的值。
1.exec:将字符串作为代码执行
exec("print('hello,world!')")
hello,world!
2.eval:是一个类似于exec的内置函数eval是一个类似于exec的内置函数。exec执行一系列Python语句,而eval计算用字符串表示的Python表达式的值,并返回结果(exec什么都不返回,因为它本身是条语句)。
打印语句:你可使用print语句来打印多个用逗号分隔的值。如果print语句以逗号结尾,
后续print语句将在当前行接着打印。
导入语句:有时候,你不喜欢要导入的函数的名称——可能是因为你已将这个名称用作
他用。在这种情况下,可使用import … as …语句在本地重命名函数。
赋值语句:通过使用奇妙的序列解包和链式赋值,可同时给多个变量赋值;而通过使用
增强赋值,可就地修改变量。
代码块:代码块用于通过缩进将语句编组。代码块可用于条件语句和循环中,还可用于
函数和类定义中(这将在本书后面介绍)。
条件语句:条件语句根据条件(布尔表达式)决定是否执行后续代码块。通过使用if/elif/
else,可将多个条件语句组合起来。条件语句的一个变种是条件表达式,如a if b else c。
断言:断言断定某件事(一个布尔表达式)为真,可包含说明为何必须如此的字符串。
如果指定的表达式为假,断言将导致程序停止执行(或引发第8章将介绍的异常)。最好
尽早将错误揪出来,免得它潜藏在程序中,直到带来麻烦。
循环:你可针对序列中的每个元素(如特定范围内的每个数)执行代码块,也可在条件
为真时反复执行代码块。要跳过代码块中余下的代码,直接进入下一次迭代,可使用
continue语句;要跳出循环,可使用break语句。另外,你还可在循环末尾添加一个else
子句,它将在没有执行循环中的任何break语句时执行。
推导:推导并不是语句,而是表达式。它们看起来很像循环,因此我将它们放在循环中
讨论。通过列表推导,可从既有列表创建出新列表,这是通过对列表元素调用函数、剔
除不想要的函数等实现的。推导功能强大,但在很多情况下,使用普通循环和条件语句
也可完成任务,且代码的可读性可能更高。使用类似于列表推导的表达式可创建出字典。
pass、del、exec和eval:pass语句什么都不做,但适合用作占位符。del语句用于删除变
量或数据结构的成员,但不能用于删除值。函数exec用于将字符串作为Python程序执行。
函数eval计算用字符串表示的表达式并返回结果。
函 数 描 述
chr(n) 返回一个字符串,其中只包含一个字符,这个字符对应于传入的顺序值n(0 ≤n < 256)
eval(source[,globals[,locals]]) 计算并返回字符串表示的表达式的结果
exec(source[, globals[, locals]]) 将字符串作为语句执行
enumerate(seq) 生成可迭代的索引值对
ord© 接受一个只包含一个字符的字符串,并返回这个字符的顺序值(一个整数)
range([start,] stop[, step]) 创建一个由整数组成的列表
reversed(seq) 按相反的顺序返回seq中的值,以便用于迭代
sorted(seq[,cmp][,key][,reverse]) 返回一个列表,其中包含seq中的所有值且这些值是经过排序的
xrange([start,] stop[, step]) 创建一个用于迭代的xrange对象
zip(seq1, seq2,…) 创建一个适合用于并行迭代的新序列
def square(x):-----给函数编写文档
'Calculates the sruare of the number x.'
return x*x
square.__doc__ -----访问文档字符串
'Calculates the square of the number x.
__doc__是函数的一个属性。属性将在第7章详细介绍。属性名中的双下划线表示这是一个特殊的属性。
所以的函数都有返回值,如果你没有告诉他们该返回什么,将返回None。
字符串(以及数和元组)是不可变的
第一种
storage = {
}
storage['first'] = {
}
storage['middle'] = {
}
storage['last'] = {
}
me = 'Magnus Lie Hetland'
storage['first']['Magnus'] = [me]-----storage是一个字典
storage['middle']['Lie'] = [me]
storage['last']['Hetland'] = [me]
print(storage['middle']['Lie'])
my_sister = 'Anne Lie Hetland'---添加的妹妹的信息
storage['first'].setdefault('Anne',[]).append(my_sister)
storage['middle'].setdefault('Lie',[]).append(my_sister)
storage['last'].setdefault('Hetland',[]).append(my_sister)
print(storage['first']['Anne'])
print(storage['middle']['Lie'])
['Magnus Lie Hetland']
['Anne Lie Hetland']
['Magnus Lie Hetland', 'Anne Lie Hetland']
第二种
def init(data):
data['first'] = {
}
data['middle'] = {
}
data['last'] = {
}
storage = {
}
init(storage)
'middle':{
},'last':{
},'first':{
}
在字典中,键的排列顺序是不固定的,因此打印字典时,每次的顺序可能不同
def lookup(data, label, name):
return data[label].get(name)
def store(data, full_name): - 将参数data和full_name提供给这个函数。这些参数被设置为从外部获得的值。
names = full_name.split() --通过拆分full_name创建一个名为names的列表。
if len(names) == 2: names.insert(1, '') --如果names的长度为2(只有名字和姓),就将中间名设置为空字符串。
labels = 'first', 'middle', 'last' --将'first'、'middle'和'last'存储在元组labels中(也可使用列表,这里使用元组只是为
了省略方括号)。
for label, name in zip(labels, names): -- 使用函数zip将标签和对应的名字合并,以便对每个标签名字对执行如下操作:
people = lookup(data, label, name) --获取属于该标签和名字的列表;
, 将full_name附加到该列表末尾或插入一个新列表。
if people:
people.append(full_name)
else:
data[label][name] = [full_name]
>>> MyNames = {
}
>>> init(MyNames)
>>> store(MyNames, 'Magnus Lie Hetland')
>>> lookup(MyNames, 'middle', 'Lie')
['Magnus Lie Hetland']
def inc(x):return x + 1
foo = 10
foo = inc(foo)
print(foo)
11
store(patient='Mr. Brainsample', hour=10, minute=20, day=13, month=5)
像这样使用名称指定的参数称为关键字参数,主要优点是有助于澄清各个参数的作用。
def print_params3(*params):#定义
print(params)
print_params3('Testing')#调用
('Testing',)
赋值时带星号的变量收集多余的值。它收集的是列表而不是元组中多余的值
def print_params3(**params):#定义
print(params)
print_params3(x=1,y=2,z=3)#调用
{
'x': 1, 'y': 2, 'z': 3}
def add(x,y):----定义函数
return x + y
params = (1,2)
print(add(*params))----调用,参数为*params
3
分配参数是通过在调用函数(而不是定义函数)时使用运算符*实现的。
def hello_3(x='Hello',y='world'):#定义函数
print('%s,%s!' %(x,y))
params={
'x':'Sir Robin','y':'Well met'}
hello_3(**params)#调用,参数为**params
Sir Robin,Well met!
这种做法也可用于参数列表的一部分,条件是这部分位于参数列表末尾,通过使用运算符**可将字典中的值分配给关键字参数。
def with_stars(**kwds):
print(kwds['name'],'is',kwds['age'],'years old')
def without_stars(kwds):
print(kwds['name'],'is',kwds['age'],'years old')
args = {
'name':'Mr.Gumby','age':42}
print(with_stars(**args))
print(without_stars(args))
Mr.Gumby is 42 years old
Mr.Gumby is 42 years old
根据上面的程序我们可以看出,只有在定义函数(允许可变数量的参数)或调用函数时(拆分字典或序列)使用,星号才能发挥作用。
def story(**kwds): #定义
return 'Once upon a time,there was a '\
'{job} called {name}.'.format_map(kwds)
def power(x,y,*others):
if others:#如果有others
print('Recevived redundant parameters:',others)#则输出others和pow(x,y)
return pow(x,y) #若没有others,则输出pow(x,y)
def interval(start,stop=None,step=1):
'Imitates range() for step > 0'
if stop is None:#如果没有给参数stop指定值
start,stop = 0,start#就调整参数start和stop的值
result = []
i = start#从start开始往上数
while i < stop:#数到stop位置
result.append(i)#将当前数的数附加到result末尾
i += step#增加到当前数和step(>0)之和
return result
print(story(job='king',name='Gumby'))
print(story(name = 'Sir Robin',job = 'brave knight'))
params = {
'job':'language','name':'Python'}
print(story(**params))
del params['job']
print(story(job= 'stroke of genius',**params))
print(power(2,3))
print(power(3,2))
print(power(y=3,x=2))
params = (5,) * 2
print(power(*params))
print(power(3, 3, 'Hello, world'))
print(interval(10))
print(interval(1, 5))
print(interval(3, 12, 4))
print(power(*interval(3, 7)))
Once upon a time,there was a king called Gumby.
Once upon a time,there was a brave knight called Sir Robin.
Once upon a time,there was a language called Python.
Once upon a time,there was a stroke of genius called Python.
8
9
8
3125
Recevived redundant parameters: ('Hello, world',)
27
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4]
[3, 7, 11]
Recevived redundant parameters: (5, 6)
81
x = 1
scope = vars()
print(scope['x'])
scope['x'] += 1
print(x)
1
2
def foo():x = 42---在这里,函数foo修改(重新关联)了变量x
x = 1
foo()--调用foo时创建了一个新的命名空间,供foo中的代码块使用。赋值语句x = 42是在这个内部作用域(局部命名空间)中执行的,不影响外部(全局)作用域内的x。
print(x)
1----所以值不变
在函数内使用的变量称为局部变量(与之相对的是全局变量)。参数类似于局部变量,因此参数与全局变量同名不会有任何问题。
def combine(parameter): print(parameter + external)
external = 'berry'
print(combine('Shrub'))
Shrubberry
但如果要在函数中访问全局变量呢?如果只是想读取这种变量的值(不重新关联它),通常不会有任何问题
遮盖”的问题:
读取全局变量的值通常不会有问题,但还是存在出现问题的可能性。如果有一个局部变量或参数与你要访问的全局变量同名,就无法直接访问全局变量,因为它被局部变量遮 **住****了。如果需要,可使用函数globals来访问全局变量。这个函数类似于vars,返回一个包含全局变量的字典。(locals返回一个包含局部变量的字典。)如下:在上一个示例中,如果有一个名为parameter的全局变量,就无法在函数combine中访问它,因为有一个与之同名的参数。然而,必要时可使用globals()[‘parameter’]来访问它。
def combine(parameter):
print(parameter + globals()['parameter'])
parameter = 'berry'
print(combine('Shrub'))
Shrubberry
------这里的关键是,通过将问题分解为较小的部分,可避免递归没完没了,因为问题终将被分解成基线条件可以解决的最小问题
------因为每次调用函数时,都将为此创建一个新的命名空间,这意味着函数调用自身时,是两个不同的函数(更准确的说,是不同版本(即命名空间不同)的同一个函数)在交流,就好像两个属于相同物种的动物在彼此交流。·
第一种
def factorial(n):
result = n#result设置为n
for i in range(1,n):
result *= i #再将其依次乘以1到n-1的每个数字
return result
第二种
def factorial(n):
if n == 1:
return 1
else:
return n * factorial(n-1)
数学定义 : 1的阶乘为1。 对于大于1的数字n,其阶乘为n 1的阶乘再乘以n。
第三种:幂
def power(x,n):
result = 1
for i in range(n):
result *= x
return result
power(x, n)(x的n次幂)是将数字x自乘n - 1次的结果,即将n个x相乘的结果。换而言之,power(2, 3)是2自乘两次的结果,即2 × 2 × 2 = 8。
第四种
def power(x, n):
if n == 0:
return 1
else:
return x * power(x, n - 1)
数学定义: 对于任何数字x,power(x, 0)都为1。 n>0时,power(x, n)为power(x, n-1)与x的乘积
def search(sequence,number,lower,upper):
if lower == upper:#如果lower == upper,就返回upper,即上限
assert number == sequence[upper]#,你
假设(断言)找到的确实是要找的数字(number == sequence[upper])。
return upper
else:
middle = (lower +upper)//2#如果还未达到基线条件,
就找出中间位置
if number > sequence[middle]:#确定数字在它左边还是右边,再使用新的上限和下限递归地调用search。
return search (sequence,number,middle + 1,upper)
else:
return search(sequence,number,lower,middle)
如果上限和下限相同,就说明它们都指向数字所在的位置,因此将这个数字返回。
否则,找出区间的中间位置(上限和下限的平均值),再确定数字在左半部分还是右半部分。然后在继续在数字所在的那部分中查找
-------为方便调用,还可将上限和下限设置为可选的。为此,只需给参数lower和upper指定默认值,并在函数开头添加如下条件语句:
def search(sequence, number, lower=0, upper=None):
if upper is None: upper = len(sequence) - 1
抽象:抽象是隐藏不必要细节的艺术。通过定义处理细节的函数,可让程序更抽象。
函数定义:函数是使用def语句定义的。函数由语句块组成,它们从外部接受值(参数),并可能返回一个或多个值(计算结果)。
参数:函数通过参数(调用函数时被设置的变量)接收所需的信息。在Python中,参数有两类:位置参数和关键字参数。通过给参数指定默认值,可使其变成可选的。
作用域:变量存储在作用域(也叫命名空间)中。在Python中,作用域分两大类:全局作用域和局部作用域。作用域可以嵌套。
递归:函数可调用自身,这称为递归。可使用递归完成的任何任务都可使用循环来完成,但有时使用递归函数的可读性更高。
函数式编程:Python提供了一些函数式编程工具,其中包括lambda表达式以及函数map、filter和reduce。
函 数 描 述
map(func, seq[, seq, …]) 对序列中的所有元素执行函数
filter(func, seq) 返回一个列表,其中包含对其执行函数时结果为真的所有元素
reduce(func, seq[, initial]) 等价于 func(func(func(seq[0], seq[1]), seq[2]), …)
sum(seq) 返回 seq 中所有元素的和
apply(func[, args[, kwargs]]) 调用函数(还提供要传递给函数的参数)
在面向对象编程中,术语对象大致意味着一系列数据(属性)以及一套访问和操作这些数据的方法
多态:可对不同类型的对象执行相同的操作,而这些操作就像“被施了魔法”一样能够正常运行。
封装:对外部隐藏有关对象工作原理的细节。
继承:可基于通用类创建出专用类。
大致意味着即便你不知道 变量指向的是哪种对象,依然能够对其执行操作,且操作的行为将随对象1属的类型而异。
object.get_price()---#像这样与对象属性相关联的函数称为方法
from random import choice
x = choice(['Hello,world!',[1,2,'e','e',4]])
print(x.count('e'))
1
执行这些代码后,x可能包含字符串'Hello, world!',也可能包含列表[1, 2, 'e', 'e', 4]。具体是哪一个,你不知道也不关心。你只关心x包含多少个'e',而不管x是字符串还是列表你都能找到答案。
从上述结果看,x包含的应该是字符串。但关键在于你无需执行相关的检查,只要x有一个名为count的方法,它将单个字符作为参数并返回一个整数就行。
def length_message(x):
print("The length of",repr(x),"is",len(x))#repr是多态的集大成者之一,可用于任何对象
print(length_message('Fnord'))
print(length_message([1,2,3]))
The length of 'Fnord' is 5
The length of [1, 2, 3] is 3
要破坏多态,唯一的办法是使用诸如type、issubclass等函数显式地执行类型检查,但你应尽可能避免以这种方式破坏多态。
封装(encapsulation)指的是向外部隐藏不必要的细节。
封装与多态都是抽象的原则,它们都像函数一样,可帮助你处理程序的组成部分,让你无需关心不必要的细节
区别:多态让你无需知道对象所属的类(对象的类型)就能调用其方法;而封装让你无需知道对象的构造就能使用它。
c = ClosedObject()
c.set_name('Sir Lancelot')
c.get_name()
'Sir Lancelot'
如避免干扰全局变量。如何将名称“封装”在对象中呢?没问题,将其作为一个属性即可。
属性是归属于对象的变量,就像方法一样。实际上,方法差不多就是与函数相关联的属性。
r = ClosedObject()
r.set_name('Sir Robin')
r.get_name()
'Sir Lancelot'
正确地设置了新对象的名称
c.get_name() ---第一个对象
'Sir Lancelot'
但第一个对象现其名称还在!因为这个对象有自己的状态。对象的状态由其属性(如名称)描述。对象的方法可能修改这些属性,因此对象将一系列函数(方法)组合起来,并赋予它们访问一些变量(属性)的权限,而属性可用于在两次函数调用之间存储值。
为了使其不能从外部访问,我们可将属性定义为私有,私有属性不能从对象外部访问,而只能通过存取方法来访问。
要让方法或属性成为私有的(不能从外部访问),只需让其名称以两个下划线打头即可。
lass Secretive:
def __inaccessible(self):
print("Bet you can't see me...")
def accessible(self):
print("The secret message is:")
self.__inaccessible()
s = Secretive()
Traceback (most recent call last):
File "" , line 1, in <module>
AttributeError: Secretive instance has no attribute '__inaccessible'
s.accessible()
----现在从外部不能访问__inaccessible,但在类中(如accessible中)依然可以使用它。
The secret message is:
Bet you can't see me ...
--------幕后的处理手法并不标准:在类定义中,对所有以两个下划线打头的名称都进行转换,即在开头加上一个下划线和类名。 Secretive._Secretive__inaccessible
要确定一个类是否是另一个类的子类,可使用内置方法issubclass。
>>> issubclass(SPAMFilter, Filter)
True
>>> issubclass(Filter, SPAMFilter)
False
如果你有一个类,并想知道它的基类,可访问其特殊属性__bases__。
>>> SPAMFilter.__bases__
(,)
>>> Filter.__bases__
(,)
要确定对象是否是特定类的实例,可使用isinstance。
>>> s = SPAMFilter()
>>> isinstance(s, SPAMFilter)
True
>>> isinstance(s, Filter)
126
True
>>> isinstance(s, str)
False
如果你要获悉对象属于哪个类,可使用属性__class__。
>>> s.__class__
每个对象都属于特定的类,并被称为该类的实例。一个类的对象为另一个类的对象的子集时,前者就是后者的子类。如“云雀”为“鸟类”的子类,而“鸟类”为“云雀”的超类。
类是由其支持的方法定义的,类的所有实例都有该类的所有方法,因此子类的所有实例都有超类的所有方法。由此,要定义子类,只需定义多出来的方法(还可能重写一些既有的方法)
例:
这个示例包含三个方法定义,它们类似于函数定义,但位于class语句内。class语句创建独立的命名空间,用于在其中定义函数
class Person:---Person是类的名称
def set_name(self,name):
self.name = name
def get_name(self):
return self.name
def greet(self):
print("Hello,world! I'm {}.".format(self.name))
对foo调用set_name和greet时,foo都会作为第一个参数自动传递给它们。我将这个参数命名为self。
foo = Person()
bar = Person()
foo.set_name('Luke Skywalker')
bar.set_name('Anakin Skwalker')
print(foo.greet())
print(bar.greet())
Hello,world! I'm Luke Skywalker.
Hello,world! I'm Anakin Skwalker.
也可以从外部访问这些属性
print(foo.name)
bar.name = 'Yoda'
print(bar.greet())
Luke Skywalker
Hello,world! I'm Yoda.
def foo(x):return x * x
foo = lambda x: x * x
这两条语句大致等价,它们都创建一个返回参数平方的函数,并将这个函数关联到变量foo.可以在全局(模块)作用域内定义名称foo,也可以在函数或方法内定义。
定义类时情况如此:在class语句中定义的代码都是在一个特殊的命名空间(类的命名空间)内执行,而类的所有成员都可访问这个命名空间。类定义其实就是要执行的代码段。
class MemberCounter:
members = 0
def init(self):
MemberCounter.members += 1
m1 = MemberCounter()
m1.init()
print(MemberCounter.members)
1
m2 = MemberCounter()
m2.init()
print(MemberCounter.members)
2
这个代码在类作用域内定义了一个变量,所有的成员(实例)都可以访问它
要指定超类,可在class语句中的类名后加上超类名,并将其用圆括号括起来。
class Filter:
def init(self):
self.blocked = []
def filter(self, sequence):
return [x for x in sequence if x not in self.blocked]
class SPAMFilter(Filter): # SPAMFilter是Filter的子类----Filter是一个过滤序列的通用类。实际上,它不会过滤掉任何东西
def init(self): # 重写超类Filter的方法init
self.blocked = ['SPAM']
f = Filter()
f.init()
f.filter([1, 2, 3])
[1, 2, 3]
s = SPAMFilter()
s.init()
s.filter(['SPAM', 'SPAM', 'SPAM', 'SPAM', 'eggs', 'bacon', 'SPAM'])
['eggs', 'bacon']
请注意SPAMFilter类的定义中有两个要点。
以提供新定义的方式重写了Filter类中方法init的定义。
直接从Filter类继承了方法filter的定义,因此无需重新编写其定义。
第二点说明了继承很有用的原因:可以创建大量不同的过滤器类,它们都从Filter类派生而来,并且都使用已编写好的方法filter。
class Calculator:
def calculate(self, expression):
self.value = eval(expression)
class Talker:
def talk(self):
print('Hi, my value is', self.value)
class TalkingCalculator(Calculator, Talker):
pass
tc = TalkingCalculator()
tc.calculate('1 + 2 * 3')
print(tc.talk())
Hi, my value is 7
子类 TalkingCalculator本身无作为,其所有的行为都是从超类哪里继承的,关键是通过从Calculator那里继承calculate,并从Talker那里继承talk,它会成为会说话的计算器
这被称为多重继承。
多个超类的超类相同时,查找特定方法或属性时访问超类的顺序称为方法解析顺序(MRO)
通常,你要求对象遵循特定的接口(即实现特定的方法),但如果需要,也可非常灵活地提出要求:不是直接调用方法并期待一切顺利,而是检查所需的方法是否存在;如果不存在,就改弦易辙。
class Calculator:
def calculate(self, expression):
self.value = eval(expression)
class Talker:
def talk(self):
print('Hi, my value is', self.value)
class TalkingCalculator(Calculator, Talker):
pass
tc = TalkingCalculator()
tc.calculate('1 + 2 * 3')
print(tc.talk())
print(hasattr(tc,'talk'))
print(hasattr(tc,'fnoed'))
print(callable(getattr(tc,'talk',None)))
print(callable(getattr(tc,'fnord',None)))
Hi, my value is 7
True
False
True
False
要查看对象中存储的所有值,可检查其__dict__属性。如果要确定对象是由什么组成的,应研究模块inspect。这个模块主要供高级用户创建对象浏览器(让用户能够以图形方式浏览Python对象的程序)以及其他需要这种功能的类似程序。
抽象类是不能(至少是不应该)实例化的类,其职责是定义子类应实现的一组抽象的方法。
抽象类(即包含抽象方法的类)最重要的是不能实例化。
from abc import ABC, abstractmethod
class Talker(ABC):
@abstractmethod
def talk(self):
pass
class Knigget(Talker):#现在实例化它没有任何问题。这是抽象基类的主要用途,而且只有在这种情形下使用
#isinstance才是妥当的:如果先检查给定的实例确实是Talker对象,就能相信这个实例 #在需要的
# 情况下有方法talk。
def talk(self):
print("NI!")
k = Knigget()
print(isinstance(k,Talker))
print(k.talk())
class Herring:#这个类的实例能够通过是否为Talker对象的检查,可它并不是Talker对象。
def talk(self):
print("Blub")
h = Herring()
print(isinstance(h,Talker))
print(Talker.register(Herring))#诚然,你可从Talker派生出Herring,这样就万事大吉了,但Herring可能 #是从他人的模块中导入的。在这种情况下,就无法采取这样的做法。为解决这 #个问题,你可将Herring注册为Talker(而不从Herring和Talker派生出子 #类),这样所有的Herring对象都将被 视为Talker对象。
print(isinstance(h,Talker))
print(issubclass(Herring,Talker))
将相关的东西放在一起。如果一个函数操作一个全局变量,最好将它们作为一个类的属性和方法。
不要让对象之间过于亲密。方法应只关心其所属实例的属性,对于其他实例的状态,让它们自己去管理就好了。
慎用继承,尤其是多重继承。继承有时很有用,但在有些情况下可能带来不必要的复杂性。要正确地使用多重继承很难,要排除其中的bug更难。
保持简单。让方法短小紧凑。一般而言,应确保大多数方法都能在30秒内读完并理解。对于其余的方法,尽可能将其篇幅控制在一页或一屏内。
(1) 将有关问题的描述(程序需要做什么)记录下来,并给所有的名词、动词和形容词加上标记。
(2) 在名词中找出可能的类。
(3) 在动词中找出可能的方法。
(4) 在形容词中找出可能的属性。
(5) 将找出的方法和属性分配给各个类。
(1) 记录(或设想)一系列用例,即使用程序的场景,并尽力确保这些用例涵盖了所有的功能。
(2) 透彻而仔细地考虑每个场景,确保模型包含了所需的一切。如果有遗漏,就加上;如果有不太对的地方,就修改。不断地重复这个过程,直到对模型满意为止。
对象:对象由属性和方法组成。属性不过是属于对象的变量,而方法是存储在属性中的函数。相比于其他函数,(关联的)方法有一个不同之处,那就是它总是将其所属的对象作为第一个参数,而这个参数通常被命名为self。
类:类表示一组(或一类)对象,而每个对象都属于特定的类。类的主要任务是定义其实例将包含的方法。
多态:多态指的是能够同样地对待不同类型和类的对象,即无需知道对象属于哪个类就可调用其方法。
封装:对象可能隐藏(封装)其内部状态。在有些语言中,这意味着对象的状态(属性)只能通过其方法来访问。在Python中,所有的属性都是公有的,但直接访问对象的状态时程序员应谨慎行事,因为这可能在不经意间导致状态不一致。
继承:一个类可以是一个或多个类的子类,在这种情况下,子类将继承超类的所有方法。你可指定多个超类,通过这样做可组合正交(独立且不相关)的功能。为此,一种常见的做法是使用一个核心超类以及一个或多个混合超类。
接口和内省:一般而言,你无需过于深入地研究对象,而只依赖于多态来调用所需的方法。然而,如果要确定对象包含哪些方法或属性,有一些函数可供你用来完成这种工作。
抽象基类:使用模块abc可创建抽象基类。抽象基类用于指定子类必须提供哪些功能,却不实现这些功能。
面向对象设计:关于该如何进行面向对象设计以及是否该采用面向对象设计,有很多不同的观点。无论你持什么样的观点,都必须深入理解问题,进而创建出易于理解的设计。
函 数 描 述
callable(object) 判断对象是否是可调用的(如是否是函数或方法)
getattr(object,name[,default]) 获取属性的值,还可提供默认值
hasattr(object, name) 确定对象是否有指定的属性
isinstance(object, class) 确定对象是否是指定类的实例
issubclass(A, B) 确定A是否是B的子类
random.choice(sequence) 从一个非空序列中随机地选择一个元素
setattr(object, name, value) 将对象的指定属性设置为指定的值
type(object) 返回对象的类型
每个异常都是某个类的实例,你能以各种方式引发和捕获这些实例,从而逮住错误并采取措施,而不是放任整个程序失败。
例:
print(1 / 0)
Traceback (most recent call last):
File "E:/pythonProject/1.py", line 1, in
print(1 / 0)
ZeroDivisionError: division by zero-----类ZeroDivisionError
python使用异常对象来表示异常状态,并在遇到错误时引发异常。异常对象未被处理(或捕捉)时,程序将终止并显示一条错误的信息(traceback).
要引发异常,可使用raise语句,并将一个类(必须是Exception的子类)或实例作为参数。将类作为参数时,将自动创建一个实例。
raise Exception
Traceback (most recent call last):
File "", line 1, in ?
Exception ------------------引发的是通用异常,没有指出出现了什么错误。
raise Exception('hyperdrive overload')
Traceback (most recent call last):
File "", line 1, in ?
Exception: hyperdrive overload---添加了错误信息hyperdrive overload
一些内置的异常类
类 名 描 述
Exception 几乎所有的异常类都是从它派生而来的
AttributeError 引用属性或给它赋值失败时引发
OSError 操作系统不能执行指定的任务(如打开文件)时引发,有多个子类
IndexError 使用序列中不存在的索引时引发,为LookupError的子类
KeyError 使用映射中不存在的键时引发,为LookupError的子类
NameError 找不到名称(变量)时引发
SyntaxError 代码不正确时引发
TypeError 将内置操作或函数用于类型不正确的对象时引发
ValueError 将内置操作或函数用于这样的对象时引发:其类型正确但包含的值不合适
ZeroDivisionError 在除法或求模运算的第二个参数为零时引发
如何创建异常类?
就像创建其他类一样,但务必直接或间接地继承Exception(这意味着从任何内置异常类派生都可以)。
代码如下:
class SomeCustomException(Exception): pass
例:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
----这个程序运行正常,直到用户输入的第二个数为零
Enter the first number: 10
Enter the second number: 0
Traceback (most recent call last):
File "E:/pythonProject/1.py", line 3, in <module>
print(x / y)
ZeroDivisionError: division by zero
------为捕获上面这种异常并对错误进行处理(这里只是打印一条对用户更友好的错误信息)
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except ZeroDivisionError:
print("The second number can't be zero!")
异常从函数向外传播到调用函数的地方。如果在这里也没有被捕获,异常将向程序的最顶层传播。这意味着你可使用try/except来捕获他人所编写函数引发的异常。
捕获异常后,如果要重新引发它(即继续向上传播),可调用raise且不提供任何参数(也可显式地提供捕获到的异常)
来看一个能够“抑制”异常ZeroDivisionError的计算器类。如果启用了这种功能,计算器将打印一条错误消息,而不让异常继续传播。在与用户交互的会话中使用这个计算器时,抑制异常很有用;但在程序内部使用时,引发异常是更佳的选择(此时应关闭“抑制”功能)。下面是这样一个类的代码:
class MuffledCalculator:
muffled = False
def calc(self,expr):
try:
return eval(expr)
except ZeroDivisionError:
if self.muffled:
print('Division by zero is illegal')
else:
raise
calculator = MuffledCalculator()
print(calculator.calc('10 / 2'))
print(calculator.calc('10 / 0'))#关闭了抑制功能
calculator.muffled9 = True
print(calculator.calc('10 / 0'))
5.0
Traceback (most recent call last):
File "E:/pythonProject/1.py", line 13, in <module>
print(calculator.calc('10 / 0'))
File "E:/pythonProject/1.py", line 5, in calc
return eval(expr)
File "" , line 1, in <module>
ZeroDivisionError: division by zero
Division by zero is illegal
----关闭了抑制功能时,捕获了异常ZeroDivisionError,但继续向上传播它。
如果无法处理异常,在except子句中使用不带参数的raise通常是不错的选择,但有时你可能想引发别的异常。在这种情况下,导致进入except子句的异常将被作为异常上下文存储起来,并出现在最终的错误的消息中。
你可使用raise…from…语句来提供自己的异常上下文,也可使用None来禁用上下文。
Enter the first number: 10
Enter the second number: "Hello, world!"
Traceback (most recent call last):
File "exceptions.py", line 4, in ?
print(x / y)
TypeError: unsupported operand type(s) for /: 'int' and 'str'
为捕获上面这种异常,可在tye/except语句中再添加一个except子句。
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except ZeroDivisionError:
print("The second number can't be zero!")
except TypeError:
print("That wasn't a number, was it?")
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except (ZeroDivisionError, TypeError, NameError):# ---使用元组
print('Your numbers were bogus ...')
---用户输入字符串,其他非数字值或输入的第二个数为零,都将打印同样的错误信息。
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except (ZeroDivisionError, TypeError) as e:
print(e)
---expect子句也捕获两种异常,但由于你同时显式地捕获了对象本身,因此可将其打印出来,让用户知道发生了什么情况。
try:
print('A simple task')
except:
print('What? Something went wrong?')
else:
print('Ah ... It went as planned.')
while True:
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
value = x / y
print('x / y is', value)
except:
print('Invalid input. Please try again.')
else:
break
Enter the first number: 1
Enter the second number: 0
Invalid input. Please try again.
Enter the first number: 'foo'
Invalid input. Please try again.
Enter the first number: 'bar'
Invalid input. Please try again.
Enter the first number: baz
Invalid input. Please try again.
Enter the first number: 10
Enter the second number: 2
x / y is 5.0
在这里,仅当没有引发异常时,才会跳出循环(这是由else子句中的break语句实现的)。换而言之,只要出现错误,程序就会要求用户提供新的输入
x = None #若是不赋值,,ZeroDivisionError将导致根本没有机会给它赋值,进而导致在finally子句中对其执行del时引发未捕获的异常。
try:
x = 1 / 0
finally:
print('Cleaning up ...')
del x
Traceback (most recent call last):
File "E:/pythonProject/1.py", line 3, in <module>
x = 1 / 0
ZeroDivisionError: division by zero
Cleaning up ...
运行这个程序,它将在执行清理工作后崩溃。
如果你想知道代码肯引发某种异常,且不希望出现这种异常时程序终止并显示栈跟踪信息,可添加必要的try/except或try/finally语句来处理它。
检查对象是否含特定的属性时,try,except也很有用,
例子:加入你要检查一个对象是否包含属性write。
try:
obj.write
except AttributeError:
print('The object is not writeable')
else:
print('The object is writeable')
如果你只想发出警告,指出情况偏离了正轨,可使用模块warnings中的函数warn.
from warnings import warn
warn("I've got a bad feeling about this.")
E:/pythonProject/1.py:2: UserWarning: I've got a bad feeling about this.
如果其他代码在使用你的模块,可使用模块warning中的函数filterwarns来抑制你发出的警告(或特定类型的警告),并指出要采取的措施。如“error"或"ignore"
from warnings import warn
warn("I've got a bad feeling about this.")
from warnings import filterwarnings
filterwarnings("ignore")
warn("Anyone out there?")
filterwarnings("error")
warn("Something is very wrong!")
Traceback (most recent call last):
File "E:/pythonProject/1.py", line 7, in
warn("Something is very wrong!")
UserWarning: Something is very wrong!
异常对象:异常情况(如发生错误)是用异常对象表示的。对于异常情况,有多种处理方式;如果忽略,将导致程序终止。
引发异常:可使用raise语句来引发异常。它将一个异常类或异常实例作为参数,但你也可提供两个参数(异常和错误消息)。如果在except子句中调用raise时没有提供任何参数,它将重新引发该子句捕获的异常。
自定义的异常类:你可通过从Exception派生来创建自定义的异常。
捕获异常:要捕获异常,可在try语句中使用except子句。在except子句中,如果没有指定异常类,将捕获所有的异常。你可指定多个异常类,方法是将它们放在元组中。如果向except提供两个参数,第二个参数将关联到异常对象。在同一条try/except语句中,可包含多个except子句,以便对不同的异常采取不同的措施。
else子句:除except子句外,你还可使用else子句,它在主try块没有引发异常时执行。
finally:要确保代码块(如清理代码)无论是否引发异常都将执行,可使用try/finally,并将代码块放在finally子句中。
异常和函数:在函数中引发异常时,异常将传播到调用函数的地方(对方法来说亦如此)。
警告:警告类似于异常,但(通常)只打印一条错误消息。你可指定警告类别,它们是Warning的子类。
函 数 描 述
warnings.filterwarnings(action,category=Warning, …) 用于过滤警告
warnings.warn(message, category=None) 用于发出警告
只需要命名为:——init——,构造函数将在创建对象后自动调用他们。
class FooBar:
def __init__(self):
self.somevar = 42
f = FooBar()
print (f.somevar)
42
class FooBar:
def __init__(self,value=42):#给构造函数添加几个参数
self.somevar = value
f = FooBar('This is a constructor argument')#指定这个参数
print (f.somevar)
This is a constructor argument
**:——def——,也称作析构函数,这个方法在对象被销毁(作为垃圾被收集)前被调用。
每个类都有一个或多个超类,并从它们那里继承。
对类B的实例调用方法(或访问其属性时)如果找不到该方法(或属性),并从其超类A中查找。例如:
class A:
def hello(self):#类A定义了名为Hello的方法
print("Hello, I'm A.")
class B(A):#B继承了A的方法
pass
a = A()
b = B()
print(a.hello())
print(b.hello())
Hello, I'm A.
Hello, I'm A.
由于类B没有定义方法Hello,因此对其调用方法hello时,打印的是消息Hello, I'm A.
B可以重写方法Hello,例:
class B(A):
def hello(self):
print("Hello,I'm B.")
b = B()
print(b.hello())
Hello,I'm B.
重写构造函数时,必须调用超类(继承的类)的构造函数,否则可能无法正确地初始化对象。
class Bird:
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry:
print('Aaaah...')
self.hungry = False
else:
print('No,thanks!')
b = Bird()
print(b.eat())
print(b.eat())
class SongBird(Bird):#新增了鸟叫的功能,SongBird是Bird的子类,继承了方法eat
def __init__(self):
self.sound = 'Squawk!'
def sing(self):
print(self.sound)
sb = SongBird()
print(sb.sing())
print(sb.eat())
Aaaah...
No,thanks!
Squawk!
Traceback (most recent call last):#这里之所以会出错,是因为在 SongBird中重写了构造函数,但新的构造#函数没有包含任何初始化属性hungry的代码,要消除这种错误,SongBird的构造函数必须调用其超类(Bird)的构造#函数,以确保基本的初始化得以执行。为此,有两种方法:调用未关联的超类构造函数,以及使用函数super。
File "E:/pythonProject/1.py", line 20, in <module>
print(sb.eat())
File "E:/pythonProject/1.py", line 5, in eat
if self.hungry:
AttributeError: 'SongBird' object has no attribute 'hungry'
class SongBird(Bird):
def __init__(self):
Bird.__init__(self)#通过调用方法( Bird.__init__),就没有实例与其相关联,通过将这个未关联#方法的self参数设置为当前实例,将使用超类的构造函数来初始化SongBird对象。这意味着将设置其属性hungry
self.sound = 'Squawk!'
def sing(self):
print(self.sound)
sb = SongBird()
print(sb.sing())
print(sb.eat())
Squawk!
Aaaah...
调用upper函数时,将当前类和当前实例作为参数,对其返回的对象调用方法时,调用的将是超类(而不是当前类)的方法。因此,在在SongBird的构造函数中,可不使用Bird,而是使用super(SongBird, self)。另外,可像通常那样(也就是像调用关联的方法那样)调用方法__init__。
class Bird:
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry:
print('Aaaah...')
self.hungry = False
class SongBird(Bird):
def __init__(self):
super().__init__()
self.sound = 'Squawk!'
def sing(self):
print(self.sound)
sb = SongBird()
print(sb.sing())
print(sb.eat())
Squawk!
Aaaah...
在python中,协议通常指的是规范行为的规则,协议指定应实现哪些方法以及这些方法应做什么。在python中,多态仅仅基于对象的行为(而不基于祖先,如属于哪个类或其超类等)。
len(self):这个方法应返回集合包含的项数,对序列来说为元素个数,对映射来说为键-值对数。如果__len__返回零(且没有实现覆盖这种行为的__nonzero__),对象在布尔上下文中将被视为假(就像空的列表、元组、字符串和字典一样)。
getitem(self, key):这个方法应返回与指定键相关联的值。对序列来说,键应该是0~n -1的整数(也可以是负数,这将在后面说明),其中n为序列的长度。对映射来说,键可以是任何类型。
setitem(self, key, value):这个方法应以与键相关联的方式存储值,以便以后能够使用__getitem__来获取。当然,仅当对象可变时才需要实现这个方法。
delitem(self, key):这个方法在对对象的组成部分使用del语句时被调用,应删除与key相关联的值。同样,仅当对象可变(且允许其项被删除)时,才需要实现这个方法。对于这些方法,还有一些额外的要求。
对于序列,如果键为负整数,应从末尾往前数。换而言之,x[-n]应与x[len(x)-n]等效。
如果键的类型不合适(如对序列使用字符串键),可能引发TypeError异常。
对于序列,如果索引的类型是正确的,但不在允许的范围内,应引发IndexError异常。
例子:创建一个无穷序列
def check_index(key):
#指定的键是否是可接受的索引?键必须是非负整数,才是可接受的。如果不是整数,将引发TypeError异常;如果是负数,将引发Index Error异常(因为这个序列的长度是无穷的)
if not isinstance(key,int):raise TypeError
if key < 0: raise IndexError
class ArithmeticSequence:
def __init__(self,start=0,step=1):
#初始化这个算术序列 start -序列中的第一个值, step -两个相邻值的差,changed -一个字典,包含用户修改后的值
self.start = start#存储起始值
self.step = step#存储步长值
self.changed = {
}#没有任何元素被修改
def __getitem__(self,key):
#从算术序列中获取一个元素
check_index(key)
try: return self.changed[key]#修改过?
except KeyError:#如果没有修改过
return self.start + key * self.step#就计算元素的值
def __setitem__(self,key,value):
# 修改算术序列中的元素
check_index(key)
self.changed[key] = value#存储修改后的值
s = ArithmeticSequence(1,2)
print(s[4])
s[4] = 2
print(s[4])
print(s[5])
9
2
11
这些代码实现的是一个算术序列,其中任何两个相邻数字的差都相同。第一个值是由构造函数的参数start(默认为0)指定的,而相邻值之间的差是由参数step(默认为1)指定的。你允许用户修改某些元素,这是通过将不符合规则的值保存在字典changed中实现的。如果元素未被修改,就使用公式self.start + key * self.step来计算它的值。
print(s["four"])
print(s[-42])
#如果所使用索引的类型非法,将引发TypeError异常;如果索引的类型正确,但不在允许的范围内(即为负数),将引发IndexError异常。
Traceback (most recent call last):
File "E:/pythonProject/1.py", line 22, in <module>
print(s["four"])
File "E:/pythonProject/1.py", line 10, in __getitem__
check_index(key)
File "E:/pythonProject/1.py", line 2, in check_index
if not isinstance(key,int):raise TypeError
TypeError
Traceback (most recent call last):
File "" , line 1, in ?
File "arithseq.py", line 31, in __getitem__
check_index(key)
File "arithseq.py", line 11, in checkIndex
if key < 0: raise IndexError
IndexError
如果只想定制某种操作的行为,就没有理由去重新实现其他所有方法。那么该如何做呢?
答案是:继承。在标准库中,模块collections提供了抽象和具体的基类,但你也可以继承内置类型。因此,如果要实现一种行为类似于内置列表的序列类型,可直接继承list。
例子:一个带访问计数器的列表
class CounterList(list):
def __init__(self,*args):
super().__init__(*args)
self.counter = 0
def __getitem__(self,index):
self.counter += 1
return super(CounterList,self).__getitem__(index)
#CounterList类深深地依赖于其超类(list)的行为。CounterList没有重写的方法(如append、extend、index等)都可直接使用。在两个被重写的方法中,使用super来调用超类的相应方法,并添加了必要的行为:初始化属性counter(在__init__中)和更新属性counter(在__getitem__中)。
#重写__getitem__并不能保证一定会捕捉用户的访问操作,因为还有其他访问列表内容的方式,如通过方法pop。
c1 = CounterList(range(10))
print(c1)
c1.reverse()
print(c1)
del c1[3:6]
print(c1)
print(c1.counter)
print(c1[4] + c1[2])
print(c1.counter)
#CounterList的行为在大多数方面都类似于列表,但它有一个counter属性(其初始值为0)。每当你访问列表元素时,这个属性的值都加1。执行加法运算cl[4] + cl[2]后,counter的值递增两次,变成了2。
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
[9, 8, 7, 3, 2, 1, 0]
0
9
2
存取方法:它们是名称类似于getHeight和setHeight的方法,用于获取或设置属性(这些属性可能是私有的)。如果访问给定属性时必须采取特定的措施,那么像这样封装状态变量(属性)很重要。
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def set_size(self,size):
self.width,self.height = size
def get_size(self):
return self.width,self.height
r = Rectangle()
r.width = 10
r.height = 5
print(r.get_size())
r.set_size((150,100))
print(r.width)
(10, 5)
150
#get_size和set_size是假想属性size的存取方法,这个属性是一个由width和height组成的元组。(可随便将这个属性替换为更有趣的属性,如矩形的面积或其对角线长度。)
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def set_size(self,size):
self.width,self.height = size
def get_size(self):
return self.width,self.height
size = property(get_size,set_size)#在这个新版的Rectangle中,通过调用函数property并将存取方法作为参数(获取方法在前,设置方法在后)创建了一个特性,然后将名称size关联到这个特性。这样,你就能以同样的方式对待width、height和size,而无需关心它们是如何实现的。
r = Rectangle()
r.width = 10
r.height = 5
print(r.get_size())
r.set_size((150,100))
print(r.width)
(10, 5)
150
class MyClass:
@staticmethod
def smeth():
print('This is a static method')
@classmethod
def cmeth(cls):
print('This is a class method of',cls)
print(MyClass.smeth())
print(MyClass.cmeth())
This is a static method
This is a class method of <class '__main__.MyClass'>
getattribute(self, name):在属性被访问时自动调用(只适用于新式类)。
getattr(self, name):在属性被访问而对象没有这样的属性时自动调用。
setattr(self, name, value):试图给属性赋值时自动调用。
delattr(self, name):试图删除属性时自动调用。
例子:
class Rectangle:
def __init__ (self):
self.width = 0
self.height = 0
def __setattr__(self, name, value):
if name == 'size':
self.width, self.height = value
else:
self. __dict__[name] = value
def __getattr__(self, name):
if name == 'size':
return self.width, self.height
else:
raise AttributeError()
即便涉及的属性不是size,也将调用方法__setattr__。因此这个方法必须考虑如下两种情形:如果涉及的属性为size,就执行与以前一样的操作;否则就使用魔法属性__dict__。dict__属性是一个字典,其中包含所有的实例属性。之所以使用它而不是执行常规属性赋值,是因为旨在避免再次调用__setattr,进而导致无限循环。
仅当没有找到指定的属性时,才会调用方法__getattr__。这意味着如果指定的名称不是size,这个方法将引发AttributeError异常。这在要让类能够正确地支持hasattr和getattr等内置函数时很重要。如果指定的名称为size,就使用前一个实现中的表达式。
什么是迭代?
迭代意味着重复多次,就像循环那样。1.使用for循环迭代,2.实现了方法__iter__的对象。
什么是方法__iter__?
方法__iter__返回一个迭代器,它是包含方法__next__的对象,而调用这个方法时可不提供任何参数。当你调用方法__next__时,迭代器应返回其下一个值。如果迭代器没有可供返回的值,应引发StopIteration异常。你还可使用内置的便利函数next,在这种情况下,next(it)与it.next()等效。
class Fibs:
def __init__(self):
self.a = 0
self.b = 1
def __next__(self):
self.a,self.b = self.b,self.a + self.b
return self.a
def __iter__(self):
return self
#这个迭代器实现了方法__iter__,而这个方法返回迭代器本身,在很多情况下,都在另一个对象中实现返回迭代器的方法__iter__,并在for循环中使用这个对象,但推荐在迭代器中也实现方法__iter__(并像刚才那样让它返回self),这样迭代器就可直接用于for循环中。
fibs = Fibs()
for f in fibs:
if f >1000:
print(f)
break
1957
除了对迭代器和可迭代对象进行迭代之外,还可将它们转换为序列。在可以使用序列的情况下,大多也可以使用迭代器或可迭代对象。
例子:使用构造函数list显示的将迭代器转换为列表。
class TestIterator:
value = 0
def __next__(self):
self.value += 1
if self.value > 10: raise StopIteration
return self.value
def __iter__(self):
return self
ti = TestIterator()
print(list(ti))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
生成器也被称为简单生成器。它是一种使用普通函数语法定义的迭代器。
例子:创建一个将嵌套列表展开的函数
nested = [[1,2],[3,4],[5]]
def flatten(nested):
for sublist in nested:
for element in sublist:
yield element
nested = [[1,2],[3,4],[5]]
for num in flatten(nested):
print(num)
1
2
3
4
5
#它首先迭代所提供嵌套列表中的所有子列表,然后按顺序迭代每个子列表的元素。包含yield语句的函数都被称为生成器。这可不仅仅是名称上的差别,生成器的行为与普通函数截然不同。差别在于,生成器不是使用return返回一个值,而是可以生成多个值,每次一个。每次使用yield生成一个值后,函数都将冻结,即在此停止执行,等待被重新唤醒。被重新唤醒后,函数将从停止的地方开始继续执行。
如果要处理任意层嵌套的列表
def flatten(nested):
try:
for sublist in nested:
for element in flatten(sublist):
yield element
except TypeError:
yield nested
print(flatten([[[1],2],3,4,[5,[6,7]],8]))
[1, 2, 3, 4, 5, 6, 7, 8]
#调用flatten时,有两种可能性(处理递归时都如此):基线条件和递归条件。在基线条件下,要求这个函数展开单个元素(如一个数)。在这种情况下,for循环将引发TypeError异常(因为你试图迭代一个数),而这个生成器只生成一个元素。
*存在一个问题:如果nested是字符串或类似于字符串的对象,它就属于序列,因此不会引发TypeError异常,可你并不想对其进行迭代。要戒酒这个问题怎么办呢?
要处理这种问题,必须在生成器开头进行检查。要检查对象是否类似于字符串,最简单、最快捷的方式是,尝试将对象与一个字符串拼接起来,并检查这是否会引发TypeError异常。添加这种检查后的生成器如下:
def flatten(nested):
try:
# 不迭代类似于字符串的对象:
try: nested + ''
except TypeError: pass
else: raise TypeError
for sublist in nested:
for element in flatten(sublist):
yield element
except TypeError:
yield nested
生成器是包含关键字yield的函数,但被调用时不会执行函数体内的代码,而是返回一个迭代器。每次请求值时,都将执行生成器的代码,直到遇到yield或return。yield意味着应生成一个值,而return意味着生成器应停止执行(即不再生成值;仅当在生成器调用return时,才能不提供任何参数)。
换而言之,生成器由两个单独的部分组成:生成器的函数和生成器的迭代器。生成器的函数是由def语句定义的,其中包含yield。生成器的迭代器是这个函数返回的结果。用不太准确的话说,这两个实体通常被视为一个,通称为生成器。
def simple_generator():
yield 1
print(simple_generator )
print(simple_generator())
<function simple_generator at 0x000001F434D76160>
<generator object simple_generator at 0x000001F434D5AF90>
生成器开始运行后,可使用生成器和外部之间的通信渠道向它提供值。这个通信渠道包含如下两个端点。
外部世界:外部世界可访问生成器的方法send,这个方法类似于next,但接受一个参数(要发送的“消息”,可以是任何对象)。
生成器:在挂起的生成器内部,yield可能用作表达式而不是语句。换而言之,当生成器重新运行时,yield返回一个值——通过send从外部世界发送的值。如果使用的是next,yield将返回None。
请注意,仅当生成器被挂起(即遇到第一个yield)后,使用send(而不是next)才有意义。要在此之前向生成器提供信息,可使用生成器的函数的参数。
生成器还包含另外两个方法。
方法throw:用于在生成器中(yield表达式处)引发异常,调用时可提供一个异常类型、一个可选值和一个traceback对象。
方法close:用于停止生成器,调用时无需提供任何参数。
方 法close( 由Python垃圾收集器在需要时调用)也是基于异常的:在yield处引发GeneratorExit异常。因此如果要在生成器中提供一些清理代码,可将yield放在一条try/finally语句中。如果愿意,也可捕获GeneratorExit异常,但随后必须重新引发它(可能在清理后)、引发其他异常或直接返回。对生成器调用close后,再试图从它那里获取值将导致RuntimeError异常。
首先,在函数体开头插入如下一行代码:
result = [] #若result已被用,应改为其他名称
接下来,将类似于yield some_expression的代码行替换为如下代码行:
yield some_expression with this:
result.append(some_expression)
最后,在函数末尾添加如下代码行:
return result
你需要将8个皇后放在棋盘上,条件是任何一个皇后都不能威胁其他皇后,即任何两个皇后都不能吃掉对方。怎样才能做到这一点呢?应将这些皇后放在什么地方呢?
1.可简单地使用元组(或列表)来表示可能的解(或其一部分),其中每个元素表示相应行中皇后所在的位置(即列)。
2.先来做些简单的抽象。要找出没有冲突(即任何一个皇后都吃不到其他皇后)的位置组合,首先必须定义冲突是什么。函数conflict接受(用状态元组表示的)既有皇后的位置,并确定下一个皇后的位置是否会导致冲突。
检测冲突:
def conclict(state,nextX):
nextY = len(state)
for i in range(nextY):
if abs(state[i] - nextX) in (0,nextX - i):
return True
return False
#参数nextX表示下一个皇后的水平位置(x坐标,即列),而nextY为下一个皇后的垂直位置(y坐标,即行)。这个函数对既有的每个皇后执行简单的检查:如果下一个皇后与当前皇后的x坐标相同或在同一条对角线上,将发生冲突,因此返回True;如果没有发生冲突,就返回False。
abs(state[i] - nextX) in (0,nextY - i)
#如果下一个皇后和当前皇后的水平距离为0(在同一列)或与它们的垂直距离相等(位于一条对角线上),这个表达式就为真;否则为假。
基线条件:
def queens(num,state):
if len(state) ==num-1:
for pos in range(num):
if not conflict(state,pos):
yield pos
#如果只剩下最后一个皇后没有放好,就遍历所有可能的位置,并返回那些不会引发冲突的位置。参数num为皇后总数,而参数state是一个元组,包含已放好的皇后的位置。
print(queens(4,(1,3,0)))
[2]
递归条件:
对于递归调用,向它提供的是由当前行上面的皇后位置组成的元组。对于当前皇后的每个合法位置,递归调用返回的是由下面的皇后位置组成的元组。为了让这个过程不断进行下去,只需将当前皇后的位置插入返回的结果开头,如下所示:
else:
for pos in range(num):
if not conflict(state, pos):
for result in queens(num, state + (pos,)):
yield (pos,) + result
#这里的for pos和if not conflict部分与前面相同,因此可以稍微简化一下代码。另外,还可给参数指定默认值。
def queens(num=8, state=()):
for pos in range(num):
if not conflict(state, pos):
if len(state) == num-1:
yield (pos,)
else:
for result in queens(num, state + (pos,)):
yield (pos,) + result
print(list(queens(3)))
print(list(queens(4)))
for solution in queens(8):
print solution
print(len(list(queens(8))))
[]
[(1, 3, 0, 2), (2, 0, 3, 1)]
(0, 4, 7, 5, 2, 6, 1, 3)
(0, 5, 7, 2, 6, 3, 1, 4)
...
(7, 2, 0, 5, 1, 4, 6, 3)
(7, 3, 0, 2, 5, 1, 6, 4)
92
扫尾工作:
def prettyprint(solution):
def line(pos, length=len(solution)):
return '. ' * (pos) + 'X ' + '. ' * (length-pos-1)
for pos in solution:
print(line(pos))
import random
prettyprint(random.choice(list(queens(8))))
. . . . . X . .
. X . . . . . .
. . . . . . X .
X . . . . . . .
. . . X . . . .
. . . . . . . X
. . . . X . . .
. . X . . . . .
新式类和旧式类:Python类的工作方式在不断变化。较新的Python 2版本有两种类,其中旧式类正在快速退出舞台。新式类是Python 2.2引入的,提供了一些额外的功能,如支持函数super和property,而旧式类不支持。要创建新式类,必须直接或间接地继承object或设置__metaclass__。
魔法方法:Python中有很多特殊方法,其名称以两个下划线开头和结尾。这些方法的功能各不相同,但大都由Python在特定情况下自动调用。例如__init__是在对象创建后调用的。
构造函数:很多面向对象语言中都有构造函数,对于你自己编写的每个类,都可能需要为它实现一个构造函数。构造函数名为__init__,在对象创建后被自动调用。
重写:类可重写其超类中定义的方法(以及其他任何属性),为此只需实现这些方法即可。要调用被重写的版本,可直接通过超类调用未关联版本(旧式类),也可使用函数super来调用(新式类)。
序列和映射:要创建自定义的序列或映射,必须实现序列和映射协议指定的所有方法,其中包括__getitem__和__setitem__等魔法方法。通过从list(或UserList)和dict(或UserDict)派生,可减少很多工作量。
迭代器:简单地说,迭代器是包含方法__next__的对象,可用于迭代一组值。没有更多的值可供迭代时,方法__next__应引发StopIteration异常。可迭代对象包含方法__iter__,它返回一个像序列一样可用于for循环中的迭代器。通常,迭代器也是可迭代的,即包含返回迭代器本身的方法__iter__。
生成器:生成器的函数是包含关键字yield的函数,它在被调用时返回一个生成器,即一种特殊的迭代器。要与活动的生成器交互,可使用方法send、throw和close。
八皇后问题:八皇后问题是个著名的计算机科学问题,使用生成器可轻松地解决它。这个问题要求在棋盘上放置8个皇后,并确保任何两个皇后都不能相互攻击。
函 数 描 述
iter(obj) 从可迭代对象创建一个迭代器
next(it) 让迭代器前进一步并返回下一个元素
property(fget, fset, fdel, doc) 返回一个特性;所有参数都是可选的
super(class, obj) 返回一个超类的关联实例
任何python程序都可作为模块导入。
import math
print(math.sin(0))
0.0
import sys
sys.path.append('C:/python')#这告诉解释器,除了通常将查找的位置外,还应到目录C:\python中去查找这个模块。
因为模块并不是用来执行操作(如打印文本)的,而是用于定义变量、函数、类等。鉴于定义只需做一次,因此导入模块多次和导入一次的效果相同。
模块在首次被导入程序时执行。模块中定义的类和函数以及对其进行赋值的变量都将成为模块的属性。
# hello2.py
def hello():
print("Hello, world!")
#编写了一个模块,并将其储存在文件hello.2py中
import hello2#导入,这将执行这个模块,也就是在这个模块的作用域内定义函数hello.
print( hello2.hello())#访问这个函数
提问:在模块的全局作用域内定义的名称都可像上面这样访问。为何要这样做呢?为何不在主程序中定义一切呢?
主要是为了重用代码。通过将代码放在模块中,就可在多个程序中使用它们。要让代码是可重用的,务必将其模块化!
检查模块是作为程序运行还是被导入另一个程序。为此,需要使用变量__name__,例如:
__name__
'__main__'
hello3.__name__
'hello3'
#在主程序中(包括解释器的交互式提示符),变量__name__的值是'__main__',而在导入的模块中,这个变量被设置为该模块的名称。
在前面的示例中,我修改了sys.path。sys.path包含一个目录(表示为字符串)列表,解释器将在这些目录中查找模块。然而,通常你不想这样做。最理想的情况是,sys.path一开始就包含正确的目录(你的模块所在的目录)。为此有两种办法:将模块放在正确的位置;告诉解释器到哪里去查找。
将模块放在正确的位置很容易,只需找出Python解释器到哪里去查找模块,再将文件放在这个地方即可。
import sys, pprint
pprint.pprint(sys.path)
['E:\\pythonProject',
'E:\\pythonProject',
'E:\\python\\python38.zip',
'E:\\python\\DLLs',
'E:\\python\\lib',
'E:\\python',
'E:\\python\\lib\\site-packages']
这里的要点是:每个字符串都表示一个位置,如果要让解释器能够找到模块,可将其放在其中任何一个位置中。虽然放在这里显示的任何一个位置中都可行,但目录site-packages是最佳的选择,因为它就是用来放置模块的。
将模块放在正确的位置可能不是合适的解决方案,其中的原因很多。
不希望Python解释器的目录中充斥着你编写的模块。
没有必要的权限,无法将文件保存到Python解释器的目录中。
想将模块放在其他地方。
最重要的是,如果将模块放在其他地方,就必须告诉解释器到哪里去查找。前面说过,要告诉解释器到哪里去查找模块,办法之一是直接修改sys.path,但这种做法不常见。标准做法是将模块所在的目录包含在环境变量PYTHONPATH中。
除使用环境变量PYTHONPATH外,还可使用路径配置文件。这些文件的扩展名为.pth,位于一些特殊目录中,包含要添加到sys.path中的目录。
为组织模块,可将其编组为包(package)。包其实就是另一种模块,但有趣的是它们可包含其他模块。模块存储在扩展名为.py的文件中,而包则是一个目录。要被Python视为包,目录必须包含文件—init—.py。如果像普通模块一样导入包,文件__init__.py的内容就将是包的内容。
例如:如果有一个名为constants的包,而文件constants/init.py包含语句PI = 3.14,就可以像下面这样做:
import constants
print(constants.PI)
要将模块加入包中,只需将模块文件放在包目录中即可。你还可以在包中嵌套其他包。
1. dir: 要查明模块包含哪些东西,可使用函数dir,它列出对象的所有属性(对于模块,它列出所有的函数、类、变量等)
2. 变量—all—:旨在定义模块的公有接口。具体地说,它告诉解释器从这个模块导入所有的名称意味着什么。
help:可提供你通常需要的所有信息
例如:
import copy
print(help(copy.copy))
Help on function copy in module copy:
copy(x)
Shallow copy operation on arbitrary Python objects.
See the module's __doc__ string for more info.
#上述帮助信息指出,函数copy只接受一个参数x,且执行的是浅复制。模块也可能有文档字符串
相比于直接查看文档字符串,使用help的优点是可获取更多的信息
查看模块本身:
print(range.__doc__)
range(stop) -> range object
range(start, stop[, step]) -> range object
Return an object that produces a sequence of integers from start (inclusive)
to stop (exclusive) by step. range(i, j) produces i, i+1, i+2, ..., j-1.
start defaults to 0, and stop is omitted! range(4) produces 0, 1, 2, 3.
These are exactly the valid indices for a list of 4 elements.
When step is given, it specifies the increment (or decrement).
就学习Python编程而言,最有用的文档是“Python库参考手册”,它描述了标准库中的所有模块。
假设你要阅读标准模块copy的代码,可以在什么地方找到呢?一种办法是像解释器那样通过sys.path来查找,但更快捷的方式是查看模块的特性file。
例如:
import copy
print(copy.__file__)
E:\python\lib\copy.py
模块sys让你能够访问与python解释器紧密相连的变量和函数。
表10-2 模块sys中一些重要的函数和变量
函数/变量 描 述
argv 命令行参数,包括脚本名
exit([arg]) 退出当前程序,可通过可选参数指定返回值或错误消息
modules 一个字典,将模块名映射到加载的模块
path 一个列表,包含要在其中查找模块的目录的名称
platform 一个平台标识符,如sunos5或win32
stdin 标准输入流——一个类似于文件的对象
stdout 标准输出流——一个类似于文件的对象
stderr 标准错误流——一个类似于文件的对象
模块os让你能够访问多个操作系统服务。os及其子模块os.path还包含多个查看、创建和删除目录及文件的函数,以及一些操作路径的函数(例如,os.path.split和os.path.join让你在大多数情况下都可忽略os.pathsep)。
表10-3 模块os中一些重要的函数和变量
函数/变量 描 述
environ 包含环境变量的映射
system(command) 在子shell中执行操作系统命令用于运行外部程序
sep 路径中使用的分隔符
pathsep 分隔不同路径的分隔符
linesep 文本文件中的 行分隔符(’\n’、’\r’或’\r\n’)
urandom(n) 返回n个字节的强加密随机数据
模块fileinput让你能够轻松地迭代一系列文本文件中的所有行。
模块fileinput中一些重要的函数
函 数 描 述
input([files[, inplace[, backup]]]) 帮助迭代多个输入流中的行
filename() 返回当前文件的名称
lineno() 返回(累计的)当前行号
filelineno() 返回在当前文件中的行号
isfirstline() 检查当前行是否是文件中的第一行
isstdin() 检查最后一行是否来自sys.stdin
nextfile() 关闭当前文件并移到下一个文件
close() 关闭序列
fileinput.input是其中最重要的函数,它返回一个可在for循环中进行迭代的对象。如果要覆盖默认行为(确定要迭代哪些文件),可以序列的方式向这个函数提供一个或多个文件名。还可将参数inplace设置为True(inplace=True),这样将就地进行处理。对于你访问的每一行,都需打印出替代内容,这些内容将被写回到当前输入文件中。就地进行处理时,可选参数backup用于给从原始文件创建的备份文件指定扩展名。
集合是由内置类set实现的
print(set(range(10)))
print(type({
}))#可使用序列(或其他可迭代对象)来创建集合,也可使用花括号显式地指定。请注意,不能仅使用花括号来创建空集合,因为这将创建一个空字典。
print({
0,1,2,3,0,1,2,3,4,5})#必须在不提供任何参数的情况下调用set。集合主要用于成员资格检查,因此将忽略重复的元素:
print({
'fee','fie','foo'})#与字典一样,集合中元素的排列顺序是不确定的
a = {
1,2,3}
b = {
2,3,4}
print(a.union(b))#要计算两个集合的并集,可对其中一个集合调用方法union
print(a|b)#也可使用按位或运算符|
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
<class 'dict'>
{
0, 1, 2, 3, 4, 5}
{
'fie', 'foo', 'fee'}
{
1, 2, 3, 4}
{
1, 2, 3, 4}
集合是可变的,因此不能用作字典中的键。另一个问题是,集合只能包含不可变(可散列)的值,因此不能包含其他集合。
若遇到集合的集合要怎么解决?用frozenset类型,它表示不可变(可散列)的集合。
堆(heap):它是一种优先队列。优先队列让你能够以任意顺序添加对象,并随时(可能是在两次添加对象之间)找出(并删除)最小的元素。
Python没有独立的堆类型,而只有一个包含一些堆操作函数的模块。这个模块名为heapq(其中的q表示队列),它包含6个函数,其中前4个与堆操作直接相关。必须使用列表来表示堆对象本身。
模块heapq中一些重要的函数
函 数 描 述
heappush(heap, x) 将x压入堆中,用于在队中添加一个元素
heappop(heap) 从堆中弹出最小的元素
heapify(heap) 让列表具备堆特征
**** heapreplace(heap, x) 弹出最小的元素,并将x压入堆中
nlargest(n, iter) 返回iter中n个最大的元素
nsmallest(n, iter) 返回iter中n个最小的元素
函数heappush:用于在堆中添加一个元素。请注意,不能将它用于普通列表,而只能用于使用各种堆函数创建的列表。原因是元素的顺序很重要(虽然元素的排列顺序看起来有点随意,并没有严格地排序)
from heapq import *
from random import shuffle
data = list(range(10))
shuffle(data)
heap = []
for n in data:
... heappush(heap, n)
...
>>> heap
[0, 1, 3, 6, 2, 8, 4, 7, 9, 5]
>>> heappush(heap, 0.5)
>>> heap
[0, 0.5, 3, 6, 1, 8, 4, 7, 9, 5, 2]
位置i处的元素总是大于位置i // 2处的元素(反过来说就是小于位置2 * i和2 * i + 1处的元素)。这是底层堆算法的基础,称为堆特征(heap property)。
在需要按添加元素的顺序进行删除时,双端队列很有用。在模块collections中,包含类型deque以及其他几个集合(collection)类型。
与集合(set)一样,双端队列也是从可迭代对象创建的,它包含多个很有用的方法。
from collections import deque
q = deque(range(5))
q.append(5)
q.appendleft(6)
print(q)
print(q.pop())
print(q.popleft())
q.rotate(3)
print(q)
q.rotate(-1)
print(q)
deque([6, 0, 1, 2, 3, 4, 5])
5
6
deque([2, 3, 4, 0, 1])
deque([3, 4, 0, 1, 2])
双端队列很有用,因为它支持在队首(左端)高效地附加和弹出元素,而使用列表无法这样做。另外,还可高效地旋转元素(将元素向右或向左移,并在到达一端时环绕到另一端)。双端队列对象还包含方法extend和extendleft,其中extend类似于相应的列表方法,而extendleft类似于appendleft。请注意,用于extendleft的可迭代对象中的元素将按相反的顺序出现在双端队列中。
模块time包含用于获取当前时间、操作时间和日期、从字符串中读取日期、将日期格式化为字符串的函数。
Python日期元组中的字段
索 引 字 段 值
0 年 如2000、2001等
1 月 范围1~12
2 日 范围1~31
3 时 范围0~23
4 分 范围0~59
5 秒 范围0~61
6 星期 范围0~6,其中0表示星期一
7 儒略日 范围1~366
8 夏令时 0、1或-1
模块time中一些重要的函数
函 数 描 述
asctime([tuple]) 将时间元组转换为字符串
localtime([secs]) 将秒数转换为表示当地时间的日期元组
mktime(tuple) 将时间元组转换为当地时间
sleep(secs) 休眠(什么都不做)secs秒
strptime(string[, format]) 将字符串转换为时间元组
time() 当前时间(从新纪元开始后的秒数,以UTC为准)
模块random包含生成伪随机数的函数,有助于编写模拟程序或生成随机输出的程序。
模块random中一些重要的函数
函 数 描 述
random() 返回一个0~1(含)的随机实数
getrandbits(n) 以长整数方式返回n个随机的二进制位
uniform(a, b) 返回一个a~b(含)的随机实数
randrange([start], stop, [step]) 从range(start, stop, step)中随机地选择一个数
choice(seq) 从序列seq中随机地选择一个元素
shuffle(seq[, random]) 就地打乱序列seq
sample(seq, n) 从序列seq中随机地选择n个值不同的元素
例子1:
from random import *
from time import *
date1 = (2016, 1, 1, 0, 0, 0, -1, -1, -1)#获取表示时间段(2016年)上限和下限的实数。为此,可使用时间元组来表示日期(将星期、儒略日和夏令时都设置为1,让Python去计算它们的正确值),并对这些元组调用mktime:
time1 = mktime(date1)
date2 = (2017, 1, 1, 0, 0, 0, -1, -1, -1)
time2 = mktime(date2)
random_time = uniform(time1, time2)#以均匀的方式生成一个位于该范围内(不包括上限)的随机数
print(asctime(localtime(random_time)))
Mon Jul 18 02:15:26 2016
若是简单的存储方案,模块shelve可替你完成大部分工作——你只需提供一个文件名即可。对于模块shelve,你唯一感兴趣的是函数open。这个函数将一个文件名作为参数,并返回一个Shelf对象,供你用来存储数据。你可像操作普通字典那样操作它(只是键必须为字符串),操作完毕(并将所做的修改存盘)时,可调用其方法close。
至关重要的一点是认识到shelve.open返回的对象并非普通映射,如下例所示:
import shelve
s = shelve.open('test.dat')
s['x'] = ['a','b','c']
s['x'].append('d')
print(s['x'])
['a', 'b', 'c']
#为什么没有d?
# 列表['a', 'b', 'c']被存储到s的'x'键下。
# 获取存储的表示,并使用它创建一个新列表,再将'd'附加到这个新列表末尾,但这个修改后的版本未被存储!
# 最后,再次获取原来的版本——其中没有'd'。
要正确地修改使用模块shelve存储的对象,必须将获取的副本赋给一个临时变量,并在修改这个副本后再次存储
import shelve
s = shelve.open('test.dat')
s['x'] = ['a','b','c']
temp = s['x']
temp.append('d')
s['x'] = temp#再次存储
print(s['x'])
['a', 'b', 'c', 'd']
还有另一种避免这个问题的办法:将函数open的参数writeback设置为True。这样,从shelf对象读取或赋给它的所有数据结构都将保存到内存(缓存)中,并等到你关闭shelf对象时才将它们写入磁盘中。如果你处理的数据不多,且不想操心这些问题,将参数writeback设置为True可能是个不错的主意。
# database.py
import sys,shelve
def store_person(db):
"""
让用户输入数据并将其存储到shelf对象中
"""
pid = input('Enter unique ID number: ')
person = {
}
person['name'] = input('Enter name: ')
person['age'] = input('Enter age')
person['phone'] = input('Enter phone number: ')
db[pid] = person
def lookup_person(db):
"""
让用户输入ID和所需的字段,并从shelf对象中获取相应的数据
"""
pid = input('Enter ID number: ')
field = input('What would you like to know? (name,age,phone)')
field = field.strip().lower()
print(field.capitaliz()+':',db[pid][field])
def print_help():
print('The available commands are:')
print('store : Store information about a person')
print('lookup : looks up a person from ID number')
print('auit : Save changes and exit')
print('? : Prints this message')
def enter_command():
cmd = input('Enter command (? for help): ')
cmd = cmd.strip().lower()
return cmd
def main():
database = shelve.open('C:\\database.dat')#你可能想修改这个名称
try:
while True:
cmd = enter_command()
if cmd == 'store':
store_person(database)
elif cmd == 'lookup':
lookup_person(database)
elif cmd == '?':
print_help()
elif cmd == 'quit':
return
finally:
database.close()
if name == '__main__':main()
# 所有代码都放在函数中,这提高了程序的结构化程度(一个可能的改进是将这些函数作为一个类的方法)。
# 主程序位于函数main中,这个函数仅在__name__== '__main__'时才会被调用。这意味着可在另一个程序中将这个程序作为模块导入,再调用函数main。
# 在函数main中,我打开一个数据库(shelf),再将其作为参数传递给其他需要它的函数。由于这个程序很小,我原本可以使用一个全局变量,但在大多数情况下,最好不要使用全局变量——除非你有理由这样做。
# 读入一些值后,我调用strip和lower来修改它们,因为仅当提供的键与存储的键完全相同时,它们才匹配。如果对用户输入的内容都调用strip和lower,用户输入时就无需太关心大小写,且在输入开头和末尾有多余的空白也没有关系。另外,注意到打印字段名时使用了capitalize。
# 为确保数据库得以妥善的关闭,我使用了try和finally。不知道什么时候就会出现问题,进而引发异常。如果程序终止时未妥善地关闭数据库,数据库文件可能受损,变得毫无用处。通过使用try和finally,可避免这样的情况发生。我原本也可像第11章介绍的那样,将shelf用作上下文管理器。
正则表达式可与多个字符串匹配,你可使用特殊字符来创建这种正则表达式。例如,句点与除换行符外的其他字符都匹配.
句点与除换行符外的任何字符都匹配,因此被称为通配符(wildcard)。
普通字符只与自己匹配,但特殊字符的情况完全不同。例如,假设要匹配字符串’python.org’,可以直接使用模式’python.org’吗?可以.
要让特殊字符的行为与普通字符一样,可对其进行转义.在它前面加上一个反斜杠。
为表示模块re要求的单个反斜杠,需要在字符串中书写两个反斜杠,让解释器对其进行转义。换而言之,这里包含两层转义:解释器执行的转义和模块re执行的转义。如果你厌烦了两个反斜杆,可使用原始字符串,如r’python.org’。
匹配任何字符很有用,但有时你需要更细致地控制。为此,可以用方括号将一个子串括起,创建一个所谓的字符集。这样的字符集与其包含的字符都匹配.,字符集只能匹配一个字符。
要指定排除字符集,可在开头添加一个****字符,例如’[abc]'与除a、b和c外的其他任何字符都匹配。
**一般而言,对于诸如句点、星号和问号等特殊字符,要在模式中将其用作字面字符而不是正则表达式运算符,必须使用反斜杠对其进行转义。在字符集中,通常无需对这些字符进行转义,但进行转义也是完全合法的。然而,你应牢记如下规则。
脱字符(^)位于字符集开头时,除非要将其用作排除运算符,否则必须对其进行转义。换而言之,除非有意为之,否则不要将其放在字符集开头。
同样,对于右方括号(])和连字符(-),要么将其放在字符集开头,要么使用反斜杠对其进行转义。实际上,如果你愿意,也可将连字符放在字符集末尾。
需要以不同的方式处理每个字符时,但如果只想匹配字符串’python’和’perl’,该如何办呢?使用字符集或通配符无法指定这样的模式,而必须使用表示二选一的特殊字符:管道字符(|)。所需的模式为’python|perl’。
而只想将其用于模式的一部分。为此,可将这部分(子模式)放在圆括号内。对于前面的示例,可重写为’p(ython|erl)’。请注意,单个字符也可称为子模式。
通过在子模式后面加上问号,可将其指定为可选的,即可包含可不包含。例如:
r’(http://)?(www.)?python.org’ 只与这些字符串匹配:‘http://www.python.org’ ;‘http://python.org’ ;‘www.python.org’ ;‘python.org’ 。需要注意以下几点:
我对句点进行了转义,以防它充当通配符。
为减少所需的反斜杠数量,我使用了原始字符串。
每个可选的子模式都放在圆括号内。
每个可选的子模式都可以出现,也可以不出现。
问号表示可选的子模式可出现一次,也可不出现。还有其他几个运算符用于表示子模式可重复多次。
(pattern):pattern可重复0、1或多次。r’w.python.org’与’www.python.org’匹配,与’.python.org’、'ww.python.org和’wwwwwww.python.org’匹配
(pattern)+:pattern可重复1或多次。 r’w+.python.org’与’w.python.org’匹配,但与’.python.
org’不匹配
(pattern){m,n}:模式可从父m~n次。 r’w{3,4}.python.org’只与’www.python.org’和’wwww.python.org’匹配。
例如,你可能想确定字符串的开头是否与模式’ht+p’匹配,为此可使用脱字符(’’)**来指出这一点。例如,'ht+p’与’http://python.org’和’htttttp://python.org’匹配,但与’www.http.org’不匹配。同样,要指定字符串末尾,可使用美元符号($)。**
模块re中一些重要的函数
函 数 描 述
compile(pattern[, flags]) 根据包含正则表达式的字符串创建模式对象
search(pattern, string[, flags]) 在字符串中查找模式(第一个匹配的)
match(pattern, string[, flags]) 在字符串开头匹配模式
split(pattern, string[, maxsplit=0]) 根据模式来分割字符串
findall(pattern, string) 返回一个列表,其中包含字符串中所有与模式匹配的子串
sub(pat, repl, string[, count=0]) 将字符串中与模式pat匹配的子串都替换为repl
escape(string) 对字符串中所有的正则表达式特殊字符都进行转义
函数re.compile将用字符串表示的正则表达式转换为模式对象,以提高匹配效率。调用
search、match等函数时,如果提供的是用字符串表示的正则表达式,都必须在内部将它们转换
为模式对象。通过使用函数compile对正则表达式进行转换后,每次使用它时都无需再进行转换。
模式对象也有搜索/匹配方法,因此re.search(pat, string)(其中pat是一个使用字符串表示的正
则表达式)等价于pat.search(string)(其中pat是使用compile创建的模式对象)。编译后的正则
表达式对象也可用于模块re中的普通函数中。
some_text = 'alpha, beta,,,,gamma delta'
re.split('[, ]+', some_text)
re.split('[, ]+', some_text, maxsplit=2)#参数maxsplit指定最多分割多少次,这里最多分割2次
re.split('[, ]+', some_text, maxsplit=1)#最多分割1次
['alpha', 'beta', 'gamma', 'delta']
['alpha', 'beta', 'gamma delta']
['alpha', 'beta,,,,gamma delta']
pat = '[a-zA-Z]+' #对(-)进行了转义
text = '"Hm... Err -- are you sure?" he said, sounding insecure.'
re.findall(pat, text)#找出字符串中包含的所有单词
['Hm', 'Err', 'are', 'you', 'sure', 'he', 'said', 'sounding', 'insecure']
pat = r'[.?\-",]+'
re.findall(pat, text) #查找所有的标点符号
['"', '...', '--', '?"', ',', '.']
在模块re中,查找与模式匹配的子串的函数都在找到时返回MatchObject对象。这种对象包
含与模式匹配的子串的信息,还包含模式的哪部分与子串的哪部分匹配的信息。这些子串部分称
为编组(group)。
编组就是放在圆括号内的子模式,它们是根据左边的括号数编号的,其中编组0指的是整个
模式。
re匹配对象的重要方法
方 法 描 述
group([group1, …]) 获取与给定子模式(编组)匹配的子串
start([group]) 返回与给定编组匹配的子串的起始位置
end([group]) 返回与给定编组匹配的子串的终止位置(与切片一样,不包含终止位置)
span([group]) 返回与给定编组匹配的子串的起始和终止位置
为利用re.sub的强大功能,最简单的方式是在替代字符串中使用组号。在替换字符串中,任何类似于**’\n’的转义序列都将被替换为与模式中编组n匹配的字符串**。例如,假设要将’something’替换为 (引号中间的是替换了的),其中前者是在纯文本文档(如电子邮件)中表示突出的普通方式,而后者是相应的HTML代码(用于网页中)。
emphasis_pattern = r'\*([^\*]+)\*'
re.sub(emphasis_pattern, r'\1', 'Hello, *world*!')
'Hello, world!'
#成功地将纯文本转换成了HTML代码。
# find_sender.py
import fileinput, re
pat = re.compile('From: (.*) <.*?>$')
#为提高处理效率,我编译了正则表达式。
#我将用于匹配要提取文本的子模式放在圆括号内,使其变成了一个编组。
#我使用了一个非贪婪模式,使其只匹配最后一对尖括号(以防姓名也包含尖括号)。
#我使用了美元符号指出要使用这个模式来匹配整行(直到行尾)
for line in fileinput.input():
m = pat.match(line)
if m: print(m.group(1))#我使用了if语句来确保匹配后才提取与特定编组匹配的内容。
print($ python find_sender.py message.eml)
Foo Fie
问题要求找出邮件头中的地址,但这个程序找出了整个文件中的所有地址。为避免这一点,可在遇到空行后调用fileinput.close(),因为邮件头不可能包含空行。如果有多个文件,也可在遇到空行后调用fileinput.nextfile()来处理下一个文件。
例子:假设要把所有的’[something]’(字段)都替换为将something作为Python表达式计算得到的结果。
‘The sum of 7 and 9 is [7 + 9].’ 应转换为 ‘The sum of 7 and 9 is 16.’
‘[name=“Mr. Gumby”]Hello, [name]’ 应转换为 ‘Hello, Mr. Gumby’
可使用正则表达式来匹配字段并提取其内容。
可使用eval来计算表达式字符串,并提供包含作用域的字典。可在try/except语句中执行这种操作。如果出现SyntaxError异常,就说明你处理的可能是语句(如赋值语句)而不是表达式,应使用exec来执行它。
可使用exec来执行语句字符串(和其他语句),并将模板的作用域存储到字典中。
可使用re.sub将被处理的字符串替换为计算得到的结果。
例子:一个模板系统
#templates.py
import fileinput,re
#与使用方括号的字段匹配
filed_pat = re.compile(r'\[(.+?)\]')
#我们将把变量收集到这里
scope = {}
#用于调用re.sub
def replacement(match):#从match中获取与编组1匹配的内容,并将其存储到变量code中。
code = match.group(1)
try:
#如果字段为表达式,就返回其结果
return str(eval(code,scope))
except SyntaxError:
#否则在当前作用域内执行该赋值语句
#并返回一个空字符串
return ''
#获取所有文本并合并成一个字符串
#(还可采用其他方法来完成这项任务)
lines = []
for line in fileinput.input():
lines.append(line)
text = ''.join(lines)
#替换所有与字段模式匹配的内容
print(filed_pat.sub(replacement,text))
定义一个用于匹配字段的模式。
创建一个用作模板作用域的字典。
定义一个替换函数,其功能如下。
从match中获取与编组1匹配的内容,并将其存储到变量code中。
将作用域字典作为命名空间,并尝试计算code,再将结果转换为字符串并返回它。如果成功,就说明这个字段是表达式,因此万事大吉;否则(即引发了SyntaxError异常),就进入下一步。
在对表达式进行求值时使用的命名空间(作用域字典)中执行这个字段,并返回一个空字符串(因为赋值语句没有结果)。
使用fileinput读取所有的行,将它们放在一个列表中,再将其合并成一个大型字符串。
调用re.sub来使用替换函数来替换所有与模式field_pat匹配的字段,并将结果打印出来。
argparse:在UNIX中,运行命令行程序时常常需要指定各种选项(开关),Python解释器就是这样的典范。这些选项都包含在sys.argv中,但要正确地处理它们绝非容易。模块argparse使得提供功能齐备的命令行界面易如反掌。
cmd:这个模块让你能够编写类似于Python交互式解释器的命令行解释器。你可定义命令,让用户能够在提示符下执行它们。或许可使用这个模块为你编写的程序提供用户界面?
csv:CSV指的是逗号分隔的值(comma-seperated values),很多应用程序(如很多电子表格程序和数据库程序)都使用这种简单格式来存储表格数据。这种格式主要用于在不同程序之间交换数据。模块csv让你能够轻松地读写CSV文件,它还以非常透明的方式处理CSV格式的一些棘手部分。
datetime:如果模块time不能满足你的时间跟踪需求,模块datetime很可能能够满足。datetime支持特殊的日期和时间对象,并让你能够以各种方式创建和合并这些对象。相比于模块time,模块datetime的接口在很多方面都更加直观。
difflib:这个库让你能够确定两个序列的相似程度,还让你能够从很多序列中找出与指定序列最为相似的序列。例如,可使用difflib来创建简单的搜索程序。
enum:枚举类型是一种只有少数几个可能取值的类型。很多语言都内置了这样的类型,如果你在使用Python时需要这样的类型,模块enum可提供极大的帮助。
functools:这个模块提供的功能是,让你能够在调用函数时只提供部分参数(部分求值,partial evaluation),以后再填充其他的参数。在Python 3.0中,这个模块包含filter和reduce。
hashlib:使用这个模块可计算字符串的小型“签名”(数)。计算两个不同字符串的签名时,几乎可以肯定得到的两个签名是不同的。你可使用它来计算大型文本文件的签名,这个模块在加密和安全领域有很多用途①。
itertools:包含大量用于创建和合并迭代器(或其他可迭代对象)的工具,其中包括可以串接可迭代对象、创建返回无限连续整数的迭代器(类似于range,但没有上限)、反复遍历可迭代对象以及具有其他作用的函数。
logging:使用print语句来确定程序中发生的情况很有用。要避免跟踪时出现大量调试输出,可将这些信息写入日志文件中。这个模块提供了一系列标准工具,可用于管理一个或多个中央日志,它还支持多种优先级不同的日志消息。
statistics:计算一组数的平均值并不那么难,但是要正确地获得中位数,以确定总体标准偏差和样本标准偏差之间的差别,即便对于偶数个元素来说,也需要费点心思。在这种情况下,不要手工计算,而应使用模块statistics!
timeit、profile和trace:模块timeit(和配套的命令行脚本)是一个测量代码段执行时间的工具。这个模块暗藏玄机,度量性能时你可能应该使用它而不是模块time。模块profile(和配套模块pstats)可用于对代码段的效率进行更全面的分析。模块trace可帮助你进行覆盖率分析(即代码的哪些部分执行了,哪些部分没有执行),这在编写测试代码时很有用
模块:模块基本上是一个子程序,主要作用是定义函数、类和变量等。模块包含测试代码时,应将这些代码放在一条检查name == 'main’的if语句中。如果模块位于环境变量PYTHONPATH包含的目录中,就可直接导入它;要导入存储在文件foo.py中的模块,可使用语句import foo。
包:包不过是包含其他模块的模块。包是使用包含文件__init__.py的目录实现的。
探索模块:在交互式解释器中导入模块后,就可以众多不同的方式对其进行探索,其中包括使用dir、查看变量__all__以及使用函数help。文档和源代码也是获取信息和洞见的极佳来源。
标准库:Python自带多个模块,统称为标准库。本章介绍了其中的几个。
sys:这个模块让你能够访问多个与Python解释器关系紧密的变量和函数。
os:这个模块让你能够访问多个与操作系统关系紧密的变量和函数。
fileinput:这个模块让你能够轻松地迭代多个文件或流的内容行。
sets、heapq和deque:这三个模块提供了三种很有用的数据结构。内置类型set也实现了集合。
time:这个模块让你能够获取当前时间、操作时间和日期以及设置它们的格式。
random:这个模块包含用于生成随机数,从序列中随机地选择元素,以及打乱列表中元素的函数。
shelve:这个模块用于创建永久性映射,其内容存储在使用给定文件名的数据库中。
re:支持正则表达式的模块。
函 数 描 述
dir(obj) 返回一个按字母顺序排列的属性名列表
help([obj]) 提供交互式帮助或有关特定对象的帮助信息
imp.reload(module) 返回已导入的模块的重载版本
要打开文件,可使用函数open,它位于自动导入的模块io中,函数open将文本名作为唯一必不可少的参数,并返回一个文件对象。
调用函数open时,如果只指定文件名,将获得一个可读取的文件对象。如果要写入文件,必须通过指定模式来显式地指出这一点。函数open的参数mode的可能取值有多个。
函数open的参数mode的最常见取值
值 描 述
‘r’ 读取模式(默认值)
‘w’ 写入模式
‘x’ 独占写入模式
‘a’ 附加模式
‘b’ 二进制模式(与其他模式结合使用)
‘t’ 文本模式(默认值,与其他模式结合使用)
‘+’ 读写模式(与其他模式结合使用)
显式地指定读取模式的效果与根本不指定模式相同。写入模式让你能够写入文件,并在文件不存在时创建它。独占写入模式更进一步,在文件已存在时引发FileExistsError异常。在写入模式下打开文件时,既有内容将被删除(截断),并从文件开头处开始写入;如果要在既有文件末尾继续写入,可使用附加模式。
'+'可与其他任何模式结合起来使用,表示既可读取也可写入。'r+'和’w+'之间有个重要差别:后者截断文件,而前者不会这样做。
Python使用通用换行模式。所有合法的换行符(’\n’、’\r’和’\r\n’)。如果要使用这种模式,同时禁止自动转换,可将关键字参数newline设置为空字符串,如open(name, newline=’’)。如果要指定只将’\r’或’\r\n’视为合法的行尾字符,可将参数newline设置为相应的行尾字符。这样,读取时不会对行尾字符进行转换,但写入时将把’\n’替换为指定的行尾字符。
文件最重要的功能是提供和接收数据。在文本和二进制模式下,基本上分别将str和bytes类用作数据。
f = open('somefile.txt','w')
print(f.write('Hello, '))
print(f.write('World!'))
f= open('somefile.txt', 'r')
print(f.read(4))
print(f.read())
7
6
Hell
o, World!
在bash等shell中,可依次输入多个命令,并使用管道将它们链接起来,如下所示:
$ cat somefile.txt | python somescript.py | sort
这条管道线包含三个命令。
cat somefile.txt:将文件somefile.txt的内容写入到标准输出(sys.stdout)。
python somescript.py:执行Python脚本somescript。这个脚本从其标准输入中读取,并将结果写入到标准输出。
sort:读取标准输入(sys.stdin)中的所有文本,将各行按字母顺序排序,并将结果写入到标准输出。
管道将一个命令的标准输出链接到下一个命令的标准输入。
readline:读取一行(从当前位置到下一个分行符的文本),调用这个方法时,可不提供任何参数(在这种情况下,将读取一行并返回它);也可提供一个非负整数,指定readline最多可读取多少个字符
writelines::接受一个字符串列表(实际上,可以是任何序列或可迭代对象),并将这些字符串都写入到文件(或流)中。请注意,写入时不会添加换行符,因此你必须自行添加。另外,没有方法writeline,因为可以使用write。
对于写入过的文件,一定要将其关闭
要确保文件得以关闭,可使用一条try/finally语句,并在finally子句中调用close.
# 在这里打开文件
try:
# 将数据写入到文件中
finally:
file.close()
with语句是一条专门为此设计的语句。
with open("somefile.txt") as somefile:
do_something(somefile)
#with语句让你能够打开文件并将其赋给一个变量(这里是somefile)。在语句体中,你将数据写入文件(还可能做其他事情)。到达该语句末尾时,将自动关闭文件,即便出现异常亦如此。
一种最简单(也可能是最不常见)的文件内容迭代方式是,在while循环中使用方法read。
with open(filename) as f:
char = f.read(1)
while char:
process(char)
char = f.read(1)
#到达文件末尾时,方法read将返回一个空字符串,但在此之前,返回的字符串都只包含一个字符(对应于布尔值True)。只要char为True,你就知道还没结束。
如果文件不大,可一次读取整个文件:1.可使用方法read并不提供任何参数(将整个文件读取到一个字符串中),
2.可使用方法readlines(将文件读取到一个字符串列表中,其中每个字符串都是一行)。
迭代大型文件中的行------for循环
可使用一种名为延迟行迭代的方法——说它延迟是因为它只读取实际需要的文本部分。
类似于文件的对象:类似于文件的对象是支持read和readline(可能还有write和writelines)等方法的对象。
打开和关闭文件:要打开文件,可使用函数open,并向它提供一个文件名。如果要确保即便发生错误时文件也将被关闭,可使用with语句。
模式和文件类型:打开文件时,还可指定模式,如’r’(读取模式)或’w’(写入模式)。通过在模式后面加上’b’,可将文件作为二进制文件打开,并关闭Unicode编码和换行符替换。
标准流:三个标准流(模块sys中的stdin、stdout和stderr)都是类似于文件的对象,它们实现了UNIX标准I/O机制(Windows也提供了这种机制)。
读取和写入:要从文件或类似于文件的对象中读取,可使用方法read;要执行写入操作,可使用方法write。
读取和写入行:要从文件中读取行,可使用readline和readlines;要写入行,可使用writelines。
迭代文件内容:迭代文件内容的方法很多,其中最常见的是迭代文本文件中的行,这可通过简单地对文件本身进行迭代来做到。还有其他与较旧Python版本兼容的方法,如使用readlines。
函 数 描 述
open(name, …) 打开文件并返回一个文件对象