基础
表示二进制的字母是b,表示八进制的字母是o(这是英文字母中小写的o,不要和数字0搞混了),表示十六进制的字母是x。因此,二进制数的正确写法是 0b110011,八进制数的正确写法是 0o56432,十六进制数的正确写法是 0xF765A。
除了这三种进制外,前面章节一直使用的是十进制。因此,Python 语言一共可以表示4种进制:二进制、八进制、十进制和十六进制。Python 语言提供了一些函数用于在这4种进制数之间进行转换。
如果是从其他进制转换到十进制,需要使用 int 函数。该函数有两个参数,含义如下:
口 第1个参数为字符串类型,表示待转换的二进制、八进制或十六进制数。参数值只需要指定带转换的数即可,不需要使用前缀,如二进制直接指定 11011,不需要指定 0b11011。
口 第2个参数为数值类型,表示第1个参数值的进制。例如,如果要将二进制转换为十进制,第2个参数值就是 2。
int 函数返回一个数值类型,表示转换后的十进制数。
下面的代码将二进制数 110011 转换为十进制数,并输出返回结果。
print(int("110011",2))//输出结果:51
如果要从十进制转换到其他进制,需要分别使用 bin、oct 和 hex 函数。bin 函数用于将十进制数转换为二进制数,oct 函数用于将十进制数转换为八进制数,hex 函数用于将十进制数转换为十六进制数。这三个函数都接收一个参数,就是待转换的十进制数。不过要注意,这三个函数的参数值也可以是二进制数、八进制数和十六进制数,也就是说,这三个函数可以在二进制、八进制、十进制和十六进制之间互转。
3.4.4 比较运算符
尽管if语句本身的知识到现在为止已经全部讲完了,不过我们的学习远没有结束。前面给出的if语句的条件都非常简单,但在实际应用中,if 语句的条件可能非常复杂,这就需要使用到本节要介绍的比较运算符。
现在先来看一下表 3-1 列出的 Python 语言中的比较运算符。
3.4.4 比较运算符
尽管if语句本身的知识到现在为止已经全部讲完了,不过我们的学习远没有结束。前面给出的if语句的条件都非常简单,但在实际应用中,if 语句的条件可能非常复杂,这就需要使用到本节要介绍的比较运算符。
现在先来看一下表 3-1 列出的 Python 语言中的比较运算符。
在表 3-1 描述的比较运算符中,涉及对象和容器的概念,目前还没讲这些技术,在本节只需了解Python 语言可以通过比较运算符操作对象和容器即可,在后面介绍对象和容器的章节,会详细介绍如何利用相关比较运算符操作对象和容器。
在比较运算符中,最常用的就是判断两个值是否相等。例如,a大于 b,a等于 b。这些运算符包括“==”“<”“>”“>=”“<=”和“!=”。
3.4.5 断言
断言(assertions)的使用方式类似于i语句,只是在不满足条件时,会直接抛出异常。类似面的if语句(伪代码):
如果不满足条件,会直接抛出异常,程序会中断
if not condition:
crash program
那么究竟为什么需要这样的代码呢?主要是因为需要监测程序在某个地方是否满足条件,如果满足条件,应该及时通知开发人员,而不是将这些bug隐藏起来,直到关键的时刻再崩溃。
其实在 TDD(test-driven development,测试驱动开发®)中经常使用断言,TDD 会在程序发现异常时执行断言,并抛出异常。
在 Python 语言中,断言需要使用 assert 语句,在 assert 关键字的后面指定断言的条件表达式。如果条件表达式的值是 False,那么就会抛出异常。而且断言后面的语句都不会被执行,相当于程序的一个断点。
value = 20
assert value < 10 or value > 30#条件不满足,会抛出异常
Traceback (most recent call last):File "<stdin>", line 1, in <module>
AssertionError
assert value < 30 #条件满足,会正常执行后面的语句
可以看到,value 变量的值是 20,而 assert 后面的条件是“value<10 or value>30”,很明显,条件不满足,所以在断言处会抛出异常。而后面的断言,条件是“value <30”,这个条件是满足的,所以在断言后面的语句都会正常执行。
当断言条件不满足时抛出异常,在默认情况下,只显示了抛出异常的位置,并没有显示抛出异常的原因,所以为了异常信息更明确,可以为 assert 语句指定异常描述。
value = 20
assert value <10 or value >30,'value 值必须在 10~20’# 为断言指定异常描述信息
Traceback (most recent call last):File "<stdin>", line 1, in <module>AssertionError: value 值必须在 10~20
while x< 10:
x += 1
if x == random.randint(1,20): #产生一个1到 20的随机整数
break flag = True #如果循环中断,将标志设为 True
print (x)
break;
if not break flag: #如果标志为 False,表示循环是正常退出的
print("没有中断 while 循环")
其实有更简单的写法,就是为 while 循环加一个 else 子句,else 子句的作用仅仅是 while 正常退出时执行(在循环中没有执行 break 语句)。else 子句可以用在 while 和 for 循环中。else 子句在循环中的用法请详见实例 3.9。
3.6 使用 exec 和 eval 执行求值字符串
使用过 JavaScript 语言的读者应该对其中的 eval 函数印象深刻。eval 函数可以将一个字符串当作JavaScript 代码执行,也就是说,可以动态执行 JavaScript 代码。其实 Python 语言也有类似的功能,这就是 exec 函数。
exec('i = 20')
exec('print(i)')
20
print(i * i)
400
可以看到上面的代码中,调用了两次 exec 函数,该函数的参数是字符串类型的值,在本例中是两句合法的 Python 语句。exec 函数成功地执行了这两条语句,并输出了最终的结果。从这一点可以看出exec 函数不仅可以执行 Python 代码,还可以共享上下文,而且通过 exec 函数执行 Python 代码,与直接通过 Python 解释器执行是完全一样的。上下文都是共享的。所以最后用 print 函数输出ii的结果是 400。
不过使用 exec 函数执行 Python 代码要注意,尽可能不要让用户可以在全局作用域下执行 Python代码,否则可能会与命名空间冲突。
from random import randint
randint(1,20)
assert value < 30 #条件满足,会正常执行后面的语句
可以看到,value 变量的值是 20,而 assert 后面的条件是“value<10 or value>30”,很明显,条件不满足,所以在断言处会抛出异常。而后面的断言,条件是“value <30”,这个条件是满足的,所以在断言后面的语句都会正常执行。
当断言条件不满足时抛出异常,在默认情况下,只显示了抛出异常的位置,并没有显示抛出异常的原因,所以为了异常信息更明确,可以为 assert 语句指定异常描述。
>>> value = 20
>>>assert value <10 or value >30,'value 值必须在 10~20’# 为断言指定异常描述信息
Traceback (most recent call last):File "<stdin>", line 1, in <module>AssertionError: value 值必须在 10~20
while x< 10:
x += 1
if x == random.randint(1,20): #产生一个1到 20的随机整数
break flag = True #如果循环中断,将标志设为 True
print (x)
break;
if not break flag: #如果标志为 False,表示循环是正常退出的
print("没有中断 while 循环")
其实有更简单的写法,就是为 while 循环加一个 else 子句,else 子句的作用仅仅是 while 正常退出时执行(在循环中没有执行 break 语句)。else 子句可以用在 while 和 for 循环中。else 子句在循环中的用法请详见实例 3.9。
##### 3.6 使用 exec 和 eval 执行求值字符串
使用过 JavaScript 语言的读者应该对其中的 eval 函数印象深刻。eval 函数可以将一个字符串当作JavaScript 代码执行,也就是说,可以动态执行 JavaScript 代码。其实 Python 语言也有类似的功能,这就是 exec 函数。
>>> exec('i = 20')
>>> exec('print(i)')
20
>>> print(i * i)
400
可以看到上面的代码中,调用了两次 exec 函数,该函数的参数是字符串类型的值,在本例中是两句合法的 Python 语句。exec 函数成功地执行了这两条语句,并输出了最终的结果。从这一点可以看出exec 函数不仅可以执行 Python 代码,还可以共享上下文,而且通过 exec 函数执行 Python 代码,与直接通过 Python 解释器执行是完全一样的。上下文都是共享的。所以最后用 print 函数输出ii的结果是 400。
不过使用 exec 函数执行 Python 代码要注意,尽可能不要让用户可以在全局作用域下执行 Python代码,否则可能会与命名空间冲突。
>>> from random import randint
>>> randint(1,20)
11
exec('randint = 30')
randint(1,20)
Traceback (most recent call last):
File "<stdin>",line 1, in <module>
TypeError:'int' object is not callable
在上面的代码中,导入了random 模块中的 randint 函数,该函数用于返回一个随机整数。但在用exec 函数执行的 Python 代码中,将 randint 函数作为一个变量赋值了,因此在后面的代码中就无法使用 randint 函数随机生成整数了。为了解决这个问题,可以为 exec 函数指定第2个参数值,用来表示放置 exec 函数执行的 Python 代码的作用域(一个字典)。
from random import randint
randint(1,20)
5
scope={}
exec('randint = 30',scope)
randint(1,20)
scope.keys()
dict keys([' builtins ','randint'])
可以看到,在上面的代码中,为 exec 函数指定了第2个参数(一个字典®类型的变量)。这时 randint=30 设置的 randint 变量实际上属于 scope,而不是全局的,所以与 randint 函数并没有冲突、
scope.keys 函数查看 scope 中的 key,会看到 randint。其实 exec 函数还有第3个参数,用于为 exec 函数要指定的 Python 代码传递参数值
a=20
args ={'a':20,'b':30}
scope ={]
exec('print (a + b)', scope, args)
50
在上面的代码中,exec函数要执行的代码是print(a+b),这里的a和b是两个变量,不过这两变量的定义代码并不是由 exec 函数执行的,而是在调用 exec 函数前通过 args 定义的,args 是一个字典,其中有两个 key:a和b,它们的值分别是 20和 30。exec 会根据字典的 key对应要执行代码中的
同名变量,如果匹配,就会将字典中相应的值传入要执行的代码。在 Python 语言中还有另外一个函数 eval。这个函数与 exec 函数类似,只是 eval 是用于执行表议式的,并返回结果值。而 exec 函数并不会返回任何值,该函数只是执行 Python 代码。可以利用 ewa函数的特性实现一个可以计算表达式的计算器。另外,eval也可以像 exec 函数一样,指定 scope 和为要执行的代码传递参数值。
eval('1+2-4')
-1
eval('2 *(6 - 4)')
4
scope={'x':20}
args={'y':40}
eval('x + y', scope, args)
60
列表和元组
1 定义序列
本节将介绍一下在 Python 语言中如何定义序列。定义序列的语法与 Java 中的数组类似,使用一中括号将序列中的元素值括起来。
【例4.1】 创建一个元素类型是字符串的序列,实现代码如下:
names =["Bill","Mary", "Jack"]
同一个序列,不仅可以包含相同类型的值,还可以包含不同类型的值。
【例 4.2】 在一个序列中放置不同类型的值,实现代码如下:
values = ["Bil1", 30,12.5, True]
4.2 序列的基本操作
序列支持很多特定的操作,这些操作所有类型的序列都可以使用。如通过索引引用序列元素(indexing)、分片(slicing)、加(adding)、乘(multiplying)以及检査某个元素是否属于序列的成员除此之外,Python 语言还可以计算序列的长度,找出最大元素和最小元素,以及对序列的迭代。
4.2.1 通过索引操作序列元素
序列中的所有元素都是有编号的,编号从0开始递增。序列中的所有元素都可以通过编号访问,这个编号被称为索引。
names =["Bill", "Mary", "Jack"]
print(names[0]) #运行结果:Bi11
print(names[2]) #运行结果:Jack
如果索引是0或正整数,那么 Python语言会从序列左侧第1个元素开始取值;如果索引是负数那么 Python 语言会从序列右侧第1个元素开始取值。序列最后一个元素的索引是-1,倒数第2个元素的索引是-2,以此类推。
由于字符串可以看作字符的序列,所以可以用序列的这个分片特性截取子字符串。【例 4.10】 通过分片操作获取一个 Url 的一级域名和完整的域名。实例位置:
PythonSamples\srclchapter4\demo4.08.py
url ='https://geekori.com'
print(ur1[8:15]) #运行结果:geekori
print(url[8:19]) #运行结果:geekori.com
在上面的代码中,使用 url[8:15]来截取 ur! 中的“geekori”,其中8和 15 是 url 中的两个索引。可以看到,两个索引之间要使用冒号(:)分隔。可能有的读者会发现,索引 15 并不是“i”的索引,而是“.”的索引,没错,在指定子序列结束索引时,要指定子序列最后一个元素的下一个元素的索引,
因此,应该指定“.”的索引,而不是“i”的索引。
那么如果子序列的最后一个元素恰好是父序列的最后一个元素该怎么办呢?例如,url 中的最后个元素是“m”,如果要截取“geekori.com”,子序列的结束索引应该如何指定呢?其实子序列的结束索引只要指定父序列最后一个元素的索引加1即可。由于父索引最后一个元素“m”的索引是 18,因此,要截取“geekori.com”,需要指定结束索引为 19,也就是 url[8:19]。
1.省略子序列的索引
首先看一个用分片截取数字序列的例子。
numbers = [1,2,3,4,5,6,7,8]
print(numbers[3:5]) #运行结果:[4,5]
print(numbers[0:1]) #运行结果:[1]
print(numbers[5:8]) #运行结果:[6,7,8]
print(numbers[-3:-1]) #运行结果:[6,7]
Python 语言规定,如果结束索引比开始索引晚出现,那么就会返回空序列,在这里索引0比索引-3 晚出现。如果要使用负数作为索引,并且获取的子序列的最后一个元素与父序列的最后一个元素相同,那么可以省略结束索引,或结束索引用正整数。
【例 4.13】 使用负数作为开始索引,并省略结束索引。
numbers = [1,2 3 4,5,6,7,8]
print(numbers[-3:]) #省略了结束索引,运行结果:[6,7,8]
print(numbers[-3:8]) #结束索引用了正整数作为索引,运行结果:[6,7,8]
numbers = [1,2,3,4,5,6,7,8,9]
print(numbers[1:6:2])#指定步长为 2,运行结果:[2,4,6]
在上面的代码中,使用 numbers[1:6:2]获取了索引为 1、3、5 的元素作为子序列的元素,其中 2是步长,可以看到,开始索引、结束索引和步长之间都用冒号分隔(:)。其实,开始索引、结束索引和步长都是可以省略的。
numbers = [1,2,3,4,5,6,7,8,9]
print(numbers[:7:2])#省略了开始索引,运行结果:[1,3,5,7]
print(numbers[::2])#省略了开始索引和结束索引,运行结果:[1,3,5,7,9]
print(numbers[3::2]) #省略了结束索引,运行结果:[4,6,8]
步长不能为 0,但可以是负数。如果步长为0,则会抛出异常;如果步长是负数,分片会从序列的右侧开始,这时开始索引要大于结束索引。
numbers = [1,2,3,4 5,6,7,8,9]
步长为-2,从索引为8的元素开始,一直到索引为3的元素,运行结果:[9,7,5]
print(numbers[8:2:-2])
print(numbers[8:2:-1]) #步长为-1,运行结果:[9,8,7,6,5,4]
print(numbers[1:6:0]) #步长为 0,会抛出异常
numbers1 = numbers[0::2] #分片获取序列中的奇数
numbers2 = numbers[1::2] #分片获取序列中的偶数
for number in numbersl: #在第1行输出所有的奇数
print(number, end="")
print("")
print(" ",end="")
for number in numbers2: #在第2行错位输出所有的偶数
print(number, end= "")
4.2.3 序列相加
序列也可以相加,但要注意,这里的相加,并不是相对应的序列元素值相加,而是序列首尾相接由于字符串属于字符序列,所以字符串相加也可以看作序列相加。但一个字符串不能和一个序列相加,否则会抛出异常。
print([1,2,3] +[6,7,8]) #运行结果:[1,2,3,6,7,8]
print("Hello" +" world")#运行结果:Hello world
print([1,2,3] + ["hello"]) #把字符串作为序列的一个元素,运行结果:[1,2,3,"hel1o"]
运行结果:[1,2,3,'h','e','1','1','o’]
print([1,2,3] + ['h','e',"1',"1'.'o'])
print([1,2,3] + "hello")#抛出异常,序列不能和字符串直接相加
4.2.4 序列的乘法
如果用数字n乘以一个序列会生成新的序列,而在新的序列中,原来的序列将被重复n次。如果序列的值是 None(Python 语言内建的一个值,表示“什么都没有”),那么将这个序列与数字n相乘假设这个包含 None 值的序列长度是1,那么就会产生占用n个元素空间的序列。
字符串与数字相乘,运行结果:hellohellohellohellohello
print('hello'*5)
序列与数字相乘,运行结果:[20,20,20,20,20,20,20,20,20,20]
print([20]*10)
将值为None的序列和数字相乘,运行结果:[None,None,None,None,None,None]
print([None]*6)
4.2.5 检查某个值是否属于一个序列
为了检査某个值是否属于一个序列,可以使用 in 运算符。这个运算符在第3章讲解条件语句时曾经提到过,但没有深入讲。因为那时还没有讲到序列和其他集合。
这个运算符是布尔运算符,也就是说,如果某个值属于一个序列,那么in 运算符返回 True,否则返回 False。
4.3 列表的基本操作
列表可以使用所有适用于序列的标准操作,例如索引、分片、连接和乘法。但列表还有一些属于自己的操作,如修改列表本身的操作,这些操作包括元素赋值、元素删除、分片赋值以及下一节要讲的列表方法。
1.列表元素赋值
如果要修改列表中的某一个元素,可以像使用数组一样对列表中的特定元素赋值,也就是使用对中括号指定元素在列表中的索引,然后使用赋值运算符(=)进行赋值。
s =["Bil1","Mike", "John"]s[0] = "Mary"
s[1]= 20
print(s)
运行结果:['Mary', 20,"John']
在列表元素赋值的操作中,列表索引可以是负数,在这种情况下,会从列表最后一个元素开始起。例如,s[-1]表示倒数第1个列表元素,s[-2]表示倒数第2个列表元素。不过不管列表索引使用数还是负数,都不能超过索引范围,否则会抛出异常。
2.删除列表元素
从列表中删除元素也很容易,使用 del语句就可以做到。
numbers = [1,2,3,4 5,6,7,8]
del numbers[2] #删除列表 numbers 中的第 3个元素
3.分片赋值
分片赋值和分片获取子列表一样,也需要使用分片操作,也就是需要指定要操作的列表的范围。
s =["hello", "world","yeah"]
s[1:] =["a","b","c"] #将列表s从第2个元素开始替换成一个新的列表
print(s) #运行结果:['hello','a','b''c']
name = list("Mike") #使用 list 函数将“Mike”转换成由字符组成的列表
print (name) #运行结果:['M',"i','k','e']
name[1:]= list("ary") #利用分片赋值操作将“Mike”替换成了“Mary’
print(name) #运行结果:['M’,'a','r','y']
4.4 列表方法
在前面的章节已经接触过了什么是函数,在这里再接触一个概念:方法。其实方法和函数非常类似,只是函数是全局的,而方法需要定义在类中,需要通过对象引用。刚刚说了,只接触一个概念,
1,2 3,4,5 #创建一个元组
当然,也可以将元组用一对圆括号括起来。
(1,2,3,4,5)#创建一个元组
既然元组中的元素值是用逗号分隔的,那么如何定义只有一个元素的元组呢?当然也是在一个值后面加逗号了(看着很另类)。
30,#创建一个只有一个元素值的元组
(12,) #创建一个只有一个元素值的元组
40 #只是一个普通的值,并不是元组
如果要创建一个空元组(没有任何元素的元组),可以直接用一对圆括号。
()
创建一个空的元组
如果想将序列转换为元组,可以使用 tuple 函数。
value = tuple([1,2,3]) #将列表[1,2,3]转换为元组(value 变量是元组类型)
引用一个方法需要使用下面的格式。
对象.方法 (参数)
在调用方法时,除了对象要放到方法前面,方法调用与函数调用类似。列表提供了一些方法,用于检查和修改列表中的内容。这些方法及其描述如下:
口 append:在列表最后插入新的值。
口 clear:用于清除列表的内容。
口 copy:用于复制一个列表。
口 count:用于统计某个元素在列表中出现的次数。
口 extend:用于在列表结尾插入另一个列表,也就是用新列表扩展原有的列表。有点类似列表相加,不过 extend方法改变的是被扩展的列表,而列表相加产生了一个新列表。口 index:用于从列表中找出某个值第一次出现的索引位置。
口 insert:用于将值插入到列表的指定位置。
□ pop:用于移除列表中的元素(默认是最后一个元素),并返回该元素的值。
口 remove:用于移除列表中某个值的第一次匹配项。
口 reverse:用于将列表中的元素反向存放。
口 sort:用于对列表进行排序,调用该方法会改变原来的列表
print("----测试 append 方法-----")
numbers = [1,2,3,4]
numbers.append(5) #将5 添加到 numbers 列表的最后
print(numbers) #运行结果:[1,2,3,4,5]
numbers.append([6,7]) #将列表[6,7]作为一个值添加到 numbers 列表后面
print(numbers) #运行结果:[1,2,3,4,5,[6,7]]
上面的代码中,“%”后面的s是什么呢?其实字符串格式化操作符后面需要跟着动态值的数据类型,以及更细节的格式(如对于浮点数来说,小数点后要保留几位),这里的“%s”表示动态部分要被替换成字符串类型的值。如果在字符串模板中有多个要被替换的部分,需要按顺序用“%”表示,然后在格式化字符串时,传入的值也要符合这个顺序。例 5.2演示了格式化字符串的基本用法。
定义字符串模板
formatStr = "Hello %s. Today is %s, Are there any activities today?"
初始化字符串格式化参数值,此处必须使用元组,不能使用列表
values =('Mike', 'Wednesday')
格式化字符串
print(formatstr % values)
在这个字符串模板中,包含了浮点数和整数类型的格式化参数
formatstr1 ="PI 是圆周率,它的值是%.4f(保留小数点后%d 位)"
导入 math 模块中的 pi 变量
from math import pi
定义与 formatstr1 对应的格式化参数值
values1 =(pi, 4)
格式化字符串,运行结果:PI是圆周率,它的值是3.1416(保留小数点后4位)
print(formatstr1 % values1)
在这个字符串模板中,包含了整数和字符串类型的格式化参数
formatStr2 ="这件事的成功率是%d%%,如果有%s参与的话,成功率会提升至%d%%"
values2 =(56,"John",70)
运行结果:这件事的成功率是56%,如果有 John 参与的话,成功率会提升至 70%
print(formatstr2 % values2)
values3 =(66 "Mike")
由于指定的参数值的数量和格式化参数的数量不匹配,所以会抛出异常
print(formatstr2 % values3)
在上面的代码中,为格式化字符串指定了不同数据类型的格式化参数。如果要在格式化字符串中显示百分号(%),就要使用两个百分号(%%)表示。当传入的参数值的数量与格式化参数的数量不匹配时,就会抛出异常。
5.2.2 模板字符串
在 string 模块中提供了一个用于格式化字符串的 Template 类,该类的功能是用同一个值替换所有相同的格式化参数。Template 类的格式化参数用美元符号(s
s ")
template,substitute(s ="Hello")
这种参数被称为关键字参数,会在后面的章节详细介绍
在上面的代码中,通过 Template 类的构造方法传入了一个格式化字符串,在这个格式化字符串中包含了三个“s”,就替换多少个“$s”。substitute 方法还可以通过字典(见下一章)设置格式化参数的值。例5.4完整地演示了如何使用 Template 类格式化字符串。
5.2.3 字符串的 format 方法
字符串本身也有一个 format 方法用于格式化当前的字符串。这个 format 方法和前面讲的格式化探作符(%)不太一样。字符串格式化参数并不是用百分号(%)表示,而是用一对大括号({}),而且支持按顺序指定格式化参数值和关键字格式化参数。例如,下面的代码通过 format 方法按顺序为格式化字符串指定了参数值。
print("{} {} {}".format(1,2,3))#运行结果:1 2 3
可以看到,上面的代码在字符串中指定了三对空的大括号,这代表三个格式化参数,不需要指定数据类型,可以向其传递 Python 语言支持的任何值。通过 format 方法传入三个值(1,2,3),这三个值会按顺序替换格式化字符串中的三对空的大括号。
命名格式化参数是指在一对大括号中指定一个名称,然后调用 format 方法时也要指定这个名称。print("{a} {b} {c}".format(a=1,c=2,b=3)) #运行结果:1 3 2
上面的代码在三对大括号中分别添加了“a”“b”“c”。通过 format 方法指定了这三个关键字参数的值。可以看到,并没有按顺序指定关键字参数的值。这也是使用关键字参数的好处,只要名字正确,format 参数的顺序可以任意指定。当然,顺序方式和关键字参数方式可以混合使用,而且还可以指定顺序方式中格式化参数从 format 方法提取参数值的顺序,甚至可以取 format 方法参数值的一部分。接连抛出了这么多功能,可能很多读者有点应接不暇了,别着急,例5.5 演示了 format 方法的一些常用使用方式。
包含了2个空的大括号,format方法需要按顺序指定格式化参数值
s1 = "Today is {}, the temperature is {} degrees."
format 方法的第1个参数值对应 s1的第1对大括号,第2个参数值对应 s1 的第2对大括号
运行结果:Today is Saturday, the temperature is 24 degrees.
print(sl,format("Saturday", 24))
包含了2个命名格式化参数,一个是{week},另一个是{degree}
s2 = "Today is lweek), the temperature is {degree) degrees."
format 方法的第1个参数指定了{degree}的值,第2个参数指定了{week}的值
可以将 degree和 week调换,s2.format(week ="Sunday",degree =22)
运行结果:Today is Sunday, the temperature is 22 degrees.
print(s2.format(degree = 22, week ="Sunday"))
混合了顺序格式化参数和关键字格式化参数两种方式
s3 = "Today is (week}, l), the {) temperature is {degree) degrees."
format 方法的参数,前面应该是按顺序传递的格式化参数值,后面是关键字格式化参数值,顺序不能调
这样做是错误的:s3.format(degree = 22,"aaaaa",12345, week ="Sunday")
运行结果: Today is Sunday, aaaaa, the 12345 temperature is 22 degrees.
print(s3.format("aaaaa", 12345, degree = 22, week ="Sunday"))
为顺序格式化参数指定了从 format 方法获取参数值的顺序,{1}表示从 format 方法的第2个参数取值
(0}表示从 format 方法的第1个参数取值
s4 = "Today is {week}, {1}, the l0} temperature is {degree) degrees."
运行结果: Today is Sunday, 12345, the aaaaa temperature is 22 degrees.print(s4.format("aaaaa", 12345, degree = 22, week ="Sunday"))
定义了一个列表
fullname =["Bill", "Gates"]
{name[1]}取 fullname 列表中的第2个值(Gates)
format 方法通过关键字参数,为name 名字指定了 fullname 列表。运行结果:Mr Gatesprint("Mr {name[1]}".format(name = fullname))
导入 math 模块
import math
访问 math 模块中的“name-”变量来获取模块的名字,访问 math 模块中的 pi 变量获取 PI 的值
s5 = "The {mod. name ) module defines the value {mod.pi} for pI"
format 方法为 mod 关键字参数指定了 math 模块
运行结果:The math module defines the value 3.141592653589793 for PI
print(s5.format(mod = math))
5.2.4 更进一步控制字符串格式化参数
format 方法的功能远不止这些,在一对大括号中添加一些字符串格式化类型符,可以对格式化字韦进行更多的控制。例如,下面的代码会将一个字符串类型的格式化参数值按原样输出、通过 rep以函数输出,以及输出其 Unicode编码。
print("{first!s} {first!r} {first!a}".format(first ="中"))
执行这行代码,会输出如下的结果。
中 "中" '\u4e2d'
除此之外,format 方法还支持很多其他的控制符,例如,可以将整数按浮点数输出,也可以将十
进制数按二进制、八进制、十六进制格式输出。例 5.6演示了如何使用这些控制符格式化字符串。
【例 5.6】 下面的代码使用了 s、r、a、f、b、o、x和%字符串格式化类型符对字符串进行格式化
实例位置:PythonSamples\src\chapter5\demo5.06.py
运行结果:原样输出:中 调用repr 函数:'中· 输出 Unicode 编码:'\u4e2d'
print("原样输出:{first!s} 调用 repr 函数:{first!r} 输出 Unicode 编码;
{first!a}".format(first ="中"))
将 21按浮点数输出,运行结果:整数:21 浮点数:21.000000
print("整数:{num} 浮点数:{num:f}".format(num = 21))
将 56 按十进制、二进制、八进制和十六进制格式输出
运行结果:十进制:56 二进制:111000 八进制:70 十六进制:38
print("十进制:{num) 二进制:{num:b} 八进制:{num:o) 十六进制:{num:x}".format(num=56))
将 533 按科学计数法格式输出,运行结果:科学计数法:5.330000e+02
print("科学计数法:{num:e}".format(num = 533))#将0.56按百分比格式输出,运行结果:百分比:56.000000%
print("百分比:{num:号}".format(num = 0.56))
在表 5-1 中提到的 inf 和 nan 是 Python 语言中的特殊值。inf表示无穷大。float("inf")表示正无穷
float("-inf")表示负无穷(无穷小)。NaN 可解释为非数字,NaN 既不是无穷大,也不是无穷小,而是无法计算时返回的一个符号,例如,执行下面的代码会格式化 inf和 NaN。
运行结果:NAN inf
print("{:F} {:f}".format(float("nan"),float("inf")))
注意,在使用表 5-1所示的字符串格式化类型符时需要在前面加上冒号(:)或感叹号(!),大多数类型符加冒号,有一部分(如 a、r)要加感叹号。如{!r}、{!a},如果写成{r}、{a}会抛出异常。
5.2.5 字段宽度、精度和千位分隔符
使用类型符f格式化浮点数时,默认在小数点后会保留6位数。其实,使用format 方法也可以让该格式化数值的整数部分占用一个固定的位数,也可以看作控制字段的宽度。例如,使用{num:10}格式化一个数值,可以让该数值靠右对齐,如果数值的长度(整数部分+小数点+小数部分的长度)不足10位,那么左边会保留空格。当然,如果数值的长度超过了 10位,就会按原样显示。
format方法同样也可以控制一个浮点数显示的小数位数,也就是数值的精度。例如,使用{pi:2f}可以让 pi指定的浮点数保留2位小数,这种格式与格式化运算符(%)类似。
还可以使用{num:10.2f}让 num 指定的数值既保留2位小数,又可以左对齐,不足 10 位左侧补空格。
本节涉及最后一个问题就是千分位分隔符(,),对于一个特别长的数值,如果不使用千分位分隔符对数值进行分隔,那么就需要一位一位地数了。如果使用{:,}格式化一个数值,那么 format 方法就会为该数值的整数部分加上千分位分隔符。
现在已经了解了如何控制字段的宽度、精度和千分位分隔符,下面通过例 5.7 来实践一下,看看这个格式化方法具体如何应用。
设置 52 的显示宽度为 12,也就是说,52的左侧会有 10个空格
print("{num:12}".format(num = 52))
将“Bil1”的显示宽度设为 10,对于字符串来说,是右补空格,也就是说,“Bi11”右侧会显示6个字符print("{name:10}Gates".format(name="Bill"))
从 math 模块导入了 pi
from math import pi
让圆周率 PI 保留2 位小数
print("float number:{pi:.2f}".format(pi= pi))
让圆周率 PI 保留2位小数的同时,整个宽度设为 10,如果不足 10 位,会左补空格
print("float number:{pi:10.2f}".format(pi= pi))
将精度应用于字符串,{:.5}表示截取“Hello world”的前5个字符,运行结果:Hello
print("{:.5}".format("Hello world"))
用千分位分隔符输出 googo1
print("One googol is {:,}".format(10 **100))
让圆周率 PI 保留2 位小数
print("float number:{pi:.2f}".format(pi= pi))#让圆周率 PI 保留2位小数的同时,整个宽度设为 10,如果不足 10 位,会左补空格print("float number:{pi:10.2f}".format(pi= pi))#将精度应用于字符串,{:.5}表示截取“Hello world”的前5个字符,运行结果:Helloprint("{:.5}".format("Hello world"))#用千分位分隔符输出 googo1print("One googol is {:,}".format(10 **100))
5.2.6 符号、对齐、用0填充和进制转换
在上一节讲到使用 format 方法可以让待格式化的值左侧或右侧补空格,不过这种填空格的效果看、并不美观,而且一般的用户也分不清前面或后面到底有多少个空格。所以最合适的方式就是在值前面或后面补 0。例如,如果写一本书,章节超过了 10章,为了让每一章的序号长度都一样,可以月01、02、03、…、11、12 这样的格式。对于 10 以后的章节,按原样输出即可。不过对于 10 以下节,就需要在数字前面补一个0了。要实现这个功能,就需要使用{chapter:02.0f}来格式化章节序其中,chapter 是格式化参数,第一个0表示位数不足时前面要补 0;2表示整数部分是2位数字:个0表示小数部分被忽略;f表示以浮点数形式格式化 chapter 指定的值。
运行结果:第 04章
print("第{chapter:02.0f}章",format(chapter = 4));
如果想用 format 方法控制值的左、中、右对齐,则可以分别使用“<”“^”和“>”。
让 1、2、3分别以左对齐、中对齐和右对齐方式显示
print('{:<10.2f}\n{:^10.2f}\n{:>10.2f}'.format(1,2,3))
不管是哪种方式对齐(左、中、右),在很多情况下,值的总长度要比指定宽度小,在默认情况不足的位要补空格,但也可以通过在“<”“^”和“>”前面加符号,让这些不足的位用这些符号空格补齐。
“并号”在宽度为 20的区域内中心对齐,并左右两侧添加若干个井号(#),两侧各添加8个井号#运行结果:########井号########
print("{:#^20}".format(”井号"))
5.3.4 split 方法
split 和join 方法互为逆方法。split 方法通过分隔符将一个字符串拆成一个序列。如果 splt 方法不指定任何参数,那么 split方法会把所有空格(空格符、制表符、换行符等)作为分隔符。
将表达式的操作数放到了序列中,并输出该序列
运行结果:['1',"2',"3','4','5’]
print("1+2+3+4+5".split("+"))
将 Linux格式的路径的每一个组成部分放到一个序列中
list ='/usr/local/nginx'.split('/')
运行结果:['','usr','local','nginx']
print(list)
利用 join 方法重新生成了 windows 格式的路径
运行结果:C:\usr\local\nginx
print("c:" + "\".join(list))
将英文句子中的单词放到序列中,然后输出
运行结果:['I','like','python']
print("I like python".split())
5.3.5 lower 方法、upper 方法和 capwords 函数
lower 方法和 upper 方法分别用于将字符串中的所有字母字符转换为小写和大写。而 capwords 并不是字符串本身的方法,而是 string 模块中的函数,之所以在这里介绍,是因为该函数与 lower 方法和 upper 方法有一点点关系,就是 capwords 函数会将一个字符串中独立的单词的首字母都转换为大写,
例如,"that’s all"如果用 capwords 函数转换,就会变成"That's All"。
5.3.6 replace 方法
replace 方法用于将一个字符串中的子字符串替换成另外一个字符串。该方法返回被替换后的字符串,如果在原字符串中未找到要替换的子字符串,那么replace 方法就返回原字符串。其实 replace 方法就是一个查找替换的过程。
运行结果:This is a bike
print("This is a car".replace("car", "bike"))
运行结果:This is a car
print("This is a car".replace("place", "bike"))
5.3.7 strip 方法
strip 方法用于截取字符串的前后空格,以及截取字符串前后指定的字符。
print(" geekori.com ".strip())
截取字符串前后空格,运行结果:< geekori.com
print(" < geekori.com > ".strip())
langList =["python", "java", "ruby", "scala", "perl"]
lang =" python "
lang 前后带有空格,因此无法在 langList 中找到相应的元素
if lang in langList:
print("<找到了 python>")
else:
print("<未找到 python>")
将 lang 前后空格去掉,可以在 langList 中找到相应的元素
if lang.strip() in langList:.
print("{找到了 python}")
else:
print("{未找到 python}")
指定要截取字符串前后的字符是空格、*和&,运行结果:Hellos *World
print("*** &* Hello& World*&&&".strip(" *&"))
print(" geekori.com ".strip())#截取字符串前后空格,运行结果:< geekori.comprint(" <
geekori.com> ".strip())
langList =["python", "java", "ruby", "scala", "perl"]lang ="Python "
1ang 前后带有空格,因此无法在 1angList 中找到相应的元素if lang in langList:
print("<找到了 python>")
else:
print("<未找到 python>")
将 1ang 前后空格去掉,可以在 1angList 中找到相应的元素
if lang.strip() in langList:.
print("{找到了 python}")
else:
print("{未找到 python}")
指定要截取字符串前后的字符是空格、和&,运行结果:Hellos worldprint("* &* Hello& World*&&&".strip(" *&"))
使用 strip 方法应了解如下几点:
(1)strip 方法与 lower 方法一样,在比较字符串时,最好利用 lower 方法将两个要比较的字符串变成小写,以及都截取前后的空格。因为无法保证数据源是否满足要求,所以要尽可能通过代码来证规范一致。
(2)strip 方法只会截取字符串前后的空格,不会截取字符串中间的空格。(3)如果指定 strip 方法的参数(一个字符串类型的值),strip 方法会将字符串参数值中的每一个符当作要截取的目标。只要在字符串前后出现了其中一个字符,将会被截取。在本例中指定的参数是“ &”,因此,只要在字符串前后有空格、"*"和&,就会被截取。但字符串中间的这些字符不会截取。
5.3.8 translate 方法与 maketrans 方法
translate 方法与 replace 方法类似,都用来替换字符串中的某一部分,只是 translate 方法只用来替换单个字符,而 replace 方法可以用来替换一个子字符串。不过从效率上来说,translate 方法要更快一些。
在使用 translate 方法之前,需要先使用 maketrans 方法创建一个替换表,该方法属于字符串本身.
创建一个替换表,表示要将"a'和"k'分别替换成'*'和'$'
table = s.maketrans("ak","*$")
然后调用字符串的 translate 方法根据 table 替换相应的字符。
在使用 translate 方法和 maketrans方法时要了解如下几点:
(1)translate 方法替换的不止一个字符,如果在原字符串中有多个字符满足条件,那么就替换所有满足条件的字符。
(2)maketrans 方法的第3个参数指定了要从原字符串中删除的字符,不是字符串。如果第3个参数指定的字符串长度大于 1,那么在删除字符时只会考虑其中的每一个字符。例如,参数值为"ab",那么只会删除原字符串中的"a"或"b",包括在字符串中间出现的这些字符。
创建和使用字典
字典可以用下面的方式创建。
phoneBook = {"Bill":"1234", "Mike":"4321", "John":"6645","Mary":"7753"}
可以看到,一个字典是用一对大括号来创建的,键与值之间用冒号(:)分隔,每一对键值之间用逗号(,)分隔。如果大括号中没有任何的值,就是一个空的字典。
在字典中,键是唯一的,这样才能通过键唯一定位某一个值。当然,如果键不唯一,那么程序也不会抛出异常,只是相同的键值会被覆盖。
phoneBook =("Bill":"1234", "Bill":"4321", "John":"6645","Mary":"7753")
可以看到上面的这行代码定义的字典中,前两对键值中的键是相同的,如果通过 Bil 定位,那么查到的值是"4321",而不是"1234"。
6.2.1 dict 兩数
可以用 dict 函数,通过其他映射(如其他的字典)或键值对的序列建立字典。
items [["Bi1l","1234"],("Mike","4321"),["Mary","7753"]]
d = dict(items)
运行结果:('Bi11':'1234','Mike':'4321','Mary':'7753')
print(d)
从上面的代码可以看出,为 dict 函数传入了一个列表类型参数值,列表的每一个元素或者是一个列表,或者是一个元组。每一个元素值包含两个值。第1个值表示键,第2个值表示值。这样 dict 函数就会将每一个 items 列表元素转换为字典中对应的一个键值。
dict 函数还可以通过关键字参数来创建字典。
items = dict(name ="Bill", number ="5678",age = 45)
运行结果:{'name':'Bill','number':'5678','age': 45)
print(items)
dict 函数如果不指定任何参数,那么该函数会返回一个空的字典。
图 6-1 用 dict 函数将列表转换为字典并输出
6.2.2 字典的基本操作
字典的很多操作与列表类似,如下面的一些操作仍然适合于字典。
口 len(dict):返回字典 dict 中元素(键值对)的数量。
口 dict[key]:返回关联到键 key 上的值,对于列表,key 就是索引。
□ dict[key]=value:将值 value 关联到键 key 上。
□ del dict[key]:删除键为 key 的项。
□ key in dict:检査 dict 中是否包含有键为 key 的项。
尽管字典和列表有很多特性相同,但也有下面的一些重要区别。
口 键类型:字典的键可以是任意不可变类型,如浮点数、元组、字符串等,而列表的 key 只能是整数类型。
口自动添加:字典可以通过键值自动添加新的项,也就是说,进行 dict[key]= value 操作时,如果 key 在字典 dict 中不存在,那么就会在 dict 中添加一个新的元素(键-值对)。而在列表中,
必须要使用 append 方法或 insert 方法才能添加新的元素。
口 查找成员:在字典中使用 key in dict 操作,査找的是key,而不是 value。在列表中使用 key in dict 操作,查找的是值,而不是索引。对于列表来说,key 就代表值。尽管字典和列表在引用
其中的值时都是用 dict[key],但 key in dict 操作的含义是不同的。
注意:由于字典中的元素是通过一定的数据结构(排序树或其他数据结构)存储的,所以在字典中查找 key要比在字典中查找值更高效,数据量越大,这种效果越明显。
6.2.3 字典的格式化字符串
在 5.2.1 节讲过使用百分号(%)配合元组对字符串进行格式化的方式。在字符串中使用%s、%d等格式表示要替换的值,这个字符串可以称为模板,然后用字符串模板与元组通过%进行格式化
xyz %d abc %s' % (20,'ok')
如果使用字典对字符串进行格式化,要比使用元组更酷。因为在字符串模板中可以用命名的方式指定格式化参数。在Python2.x中,仍然可以使用%运算符和字典对字符串进行格式化,不过在Python3.中,改用了字符串的 format_map 方法,而且格式化参数需要用一对花括号({})括起来,格式化字符串的具体使用方式见例 6.3。
6.2.4 序列与迭代
之所以在这里讲序列如何在迭代中使用,是因为到现在为止,已经讲了三种序列:列表、元组和字典。通过不同的迭代方式,可以非常方便地对序列进行迭代。本节会详细介绍 Python语言中提供的各种序列迭代方式。
1.获取字典中 key 的列表
在使用字典时,如果要想知道字典里有哪些 key,可以直接使用 for 语句对字典进行遍历。
dict = {'x':1,'y':2,'z':3}
输出x y z
for key in dict:
print(key, end='')
上面的代码中,key 的值分别为x、y、z,因此,会输出“x yz”。
2.同时获取字典中的 key 和 value 列表
如果要同时获取字典中的 key 和 value,除了在上面的代码中使用 dict[key]获取值外,还可以使用子典中的 items 方法同时获取 key 和 value。
dict = {'x':1, 'y':2,'z':3)
同时获取字典中的 key 和 value
运行结果:x1 y2 z3
for key,value in dict.items():
print(key, value, end='")
3.并行迭代
如果想同时迭代两个或多个序列,那么可以使用 range 函数获取序列索引的范围,然后使用for
语句进行迭代。对多个序列进行迭代,一般要求序列中元素个数相同。
names =["Bill","Mary", "John"]
ages =[30,40,20]
运行结果:Bil1 30 Mary 40 John 20
for i in range(len(names)):
print(names[i],ages[i], end="")
4.压缩序列
这里的压缩序列是指使用 zip函数将两个或多个序列的对应元素作为一个元组放到一起,进行缩的两个或多个序列的元素个数如果不相同,以元素个数最少的为准。例如,下面的两个序列如果zip 函数进行压缩,会得到一个长度为2的新序列,每一个序列元素是一个元组。
companies =["欧瑞科技","Google","Facebook"]
websites =["https://geekori.com","https://www.google.com"]
运行结果:('欧瑞科技",'https://geekori.com') ('Google','https://www.google.com')
for value in zip(companies, websites):
print(value, end ="")
在上面的代码中,companies 列表中有三个元素,而 websites 列表中只有两个元素,所以 zip 函依少数选取,因此最后的结果是以 websites 列表为准,得到的压缩后的列表元素只有两个元素。由zip 函数返回的序列形式比较复杂,所以不能直接使用 print 函数输出。
5.反转序列迭代
通过 reversed 函数可以将一个序列反转,代码如下:
companies = reversed(["欧瑞科技","Google","Facebook"])
运行结果:Facebook Google 欧瑞科技
for value in companies:
print(value, end ="")
6.3 字典方法
与其他内建类型一样,字典也有方法。这些方法非常有用,不过字典中的这些方法可能并不。列表、宝符串定的方法那样频繁使用。本节介绍的方法也不需要全部记住,只需要浏览一下,看典中有哪些方法,并了解一下这些方法的作用与使用方法,等以后需要用时再查一下即可。
6.3.1 clear 方法
clear 方法用于清空字典中的所有元素。
dict = {'a':1,'b':2}
dict.clear();
清空字典中的元素:运行结果:()
print(dict)
6.3.2copy 方法与 deepcopy 函数
copy方法用于复制一个字典,该方法返回复制后的新字典。
dict = {"a":30, "b":"hello","c":[1,2,3,4]}
复制一个新的字典
newDict = dict.copy()
copy 方法复制的字典只是浅复制,也就是说只复制第1层的字典数据。至于第2层及以下的所有层,原字典和新字典都指向同一个值,也就是说,不管是修改原字典中的这些元素,还是新字典中的这些元素,原字典和新字典中对应的元素都会同时改变。对于上面的代码,如果修改字典 dict 中 key等于“a”或“b”的值,字典 newDict 中对应的值并不会发生改变,因为“a”和“b”的值都属于第1层(只是一个简单的数值或字符串),而不管修改哪一个字典中 key 为“c”的值,另外一个字典对应的值都会改变。这里修改 key 为“c”的值并不是指替换整个列表([1,2,3,4]),而是修改该列表中的某个值,如将“4”修改成“20”。
如果要想改变这种情况,就需要使用 copy 模块中的 deepcopy 函数,该函数可以对序列进行深层复制
导入 copy 模块中的 deepcopy 函数
from copy import deepcopy
dict ={"a":30, "b":"hello","c":[1,2,3,4]}
newDict 是经过深层复制的字典,与 dict 中的元素完全脱离
newDict = deepcopy(dict)
6.3.3 fromkeys 方法
fomkeys 方法用于根据 key 建立新的字典(该方法的返回值就是新的字典)。在新的字典中,所有的 key 都有相同的默认值。在默认情况下,fomkeys 方法会为每一个 key 指定 None 为其默认值。不过可以使用 fromkeys 方法的第2个参数设置新的默认值。
应的值
6.3.5 items 方法和 keys 方法
items 方法用于返回字典中所有的 key-value 对。获得的每一个 key-value 对用一个元组表示。items方法返回的值是一个被称为字典视图的特殊类型,可以被用于迭代(如使用在 for 循环中)。items 方法的返回值与字典使用了同样的值,也就是说,修改了字典或 items 方法的返回值,修改的结果就会反映在另一方法上。keys 方法用于返回字典中所有的key,返回值类型与 items 方法类似,可以用于迭代。
####### 6.3.6 pop 方法和 popitem 方法
pop 方法与 popitem 方法都用于弹出字典中的元素。pop 方法用于获取指定 key 的值,并从字典中弹出这个 key-value 对。popitem 方法用于返回字典中最后一个 key-value 对,并弹出这个 key-value 对。对于字典来说,里面的元素并没有顺序的概念,也没有 append或类似的方法,所以这里所说的最后-个 key-value 对,也就是为字典添加 key-value 对时的顺序,最后一个添加的 key-value 对就是最后一个元素。
####### 6.3.7setdefault 方法
setdefault 方法用于设置 key 的默认值。该方法接收两个参数,第1个参数表示 key,第2个参数表示默认值。如果 key 在字典中不存在,那么 setdefault 方法会向字典中添加这个 key,并用第2个参数值作为 key 的值。该方法会返回这个默认值。如果未指定第2个参数,那么 key 的默认值是 None如果字典中已经存在这个 key,setdefault 不会修改key 原来的值,而且该方法会返回 key 原来的值。
####### 6.3.8update 方波
update 方法可以用一个字典中的元素更新另外一个字典。该方法接收一个参数,该参数表示用作更新数据的字典数据源。如 dict1.update(dict2)可以用 dict2 中的元素更新 dict1。如果 dict2 中的 key-value对在 dict1 中不存在,那么会在 dict1 中添加一个新的 key-value 对。如果 dictl 中已经存在了这个 key.
那么会用 dict2 中 key 对应的值更新 dict1.中 key 的值。
####### 6.3.9 values 方法
values 方法用于以迭代器形式返回字典中值的列表。与keys方法不同的是,values 方法返回的值列表可以有重复的,而 keys 方法返回的键值列表不会有重复的 key。
7.2函数基础
本节将介绍如何创建函数、如何为函数添加文档注释,以及定义和调用没有返回值的函数。
7.2.1 创建函数
在前面提到,函数是可以调用的,而且是可以交互的,既然可以调用和交互,那么就需要有一函数名,以及函数参数和返回值。这是函数的三个重要元素,其中函数名是必需的,函数参数和返回值是可选的。如果函数只是简单地执行某段代码,并不需要与外部进行交互,那么函数参数与返回信可以省略。
定义函数要使用 def语句、
def greet(name):
return 'Hello {}'.format(name)
从上面的代码可以看出,函数名是 greet。后面是一对圆括号,函数的参数就放在这里。圆括号中有一个 name 参数。最后用一个冒号(:)结尾。这表示函数与 if、while、for 语句一样,也是一个代码块,这就意味着函数内部的代码需要用缩进量来与外部代码分开。
由于 Python 是动态语言,所以函数参数与返回值都不需要事先指定数据类型,函数参数就直接写参数名即可,如果函数有多个参数,中间用逗号(,)分隔。如果函数有返回值,直接使用return 语句返回即可。return 语句可以返回任何东西,一个值,一个变量,或是另一个函数的返回值,如果函数没有返回值,可以省略 return 语句。
将代码封装在函数中后,就可以调用函数了。
print(greet("李宁"))
print(greet("马云"))
7.3 函数参数
函数使用起来很简单,创建起来也不复杂,但函数参数的用法却需要详细讨论一下,因为函数参数用起来非常灵活。
写在 def 语句中函数名后面圆括号中的参数称为形参,而调用函数时指定的参数称为实参。形参对于函数调用者来说是透明的。也就是说形参叫什么,与调用者无关。这个形参是在函数内部使用的,函数外部并不可见。
7.3.1 改变参数的值
如果将一个变量作为参数传入函数,并且在函数内部改变这个变量的值,那么结果会怎么样呢?
不妨做一个实验。
x= 20
s="世界您好"
def test(x,s):
x= 40
s = "hello world"
test(x,s)
print(x,s)
7.3.3 可变参数
在前面的章节已经多次使用过 print 函数,这个函数可以接收任意多个参数,在输出到控制台时会将输出的参数值之间加上空格。像 print 函数这样可以传递任意多个参数的形式称为可变参数。定义函数的可变参数需要在形参前面加一个星号(*)。
定义一个带有可变参数的函数
def printParams(*params)
print(params)
可使用下面的代码调用 printParams 函数。
printParams("hello", 1,2,3,True,30.4)
7.3.4 将序列作为函数的参数值
函数参数值可以是任何数据类型,自然也包括序列(元组、列表、字典等)。不过本节讲的并不是直接将序列作为单个的参数值传入函数,而是将序列中的每个元素单独作为函数的参数值,相当于
把序列拆开进行传值。现在先看看下面的代码。
def printParams(s1, s2):
print(s1 s2)
printParams("hello","world")
list =["hello","world"]
将列表或元组中的元素作为单个参数值传递为 printparams 函数,需要在实参前面加星号(*)
printParams(*list)
7.5 递归
递归对于初学者来说是一个难点,其实单从编写递归的方式上来看并不难理解。所谓递归,就是在函数内部调用自身。在执行过程中,Python解析器会利用栈(stack)处理递归函数返回的数据。所以递归函数的一个必要条件是要有终止条件,否则栈就会溢出。在这里并不讨论递归的底层原理,只讨论如何编写递归函数。函数实现阶乘和斐波那契数列。
通过递归可以实现很多经典的算法,如阶乘、斐波那契数列等。
类
8.2.3 类代码块
class 语句与 for、while 语句一样,都是代码块,这就意味着,定义类其实就是执行代码块。
class MyClass:
print("Myclass")
执行上面的代码后,会输出“MyClass”。在 class 代码块中可以包含任何语句。如果这些语句是立即可以执行的(如 print 函数),那么会立即执行它们。除此之外,还可以动态向 class 代码块中添加新的成员。
8.2.4 类的继承
与其他面向对象编程语言(Jav8、C#等)一样,Pyon 也支持类的继承。所谓类的继承,就是指一个类(子类)从另外一个类(父类)中获得了所有的成员。父类的成员可以在子类中使用,就像子类本身的成员一样。
Python 类的父类需要放在类名后的圆括号中。
父类
class Filter:
def filterl(self):
return 20
子类
class MyFilter(Filter):
def filter2 (self):
return 30
在上面的代码中,MyFilter 是 Filter 的子类,拥有 Filter 类的所有成员,包括 flter1 方法。所以在创建 MyFilter 类的实例后,可以直接调用 filter1 方法。
filter = MyFilter()
filter.filter1()
8.2.5 检测继承关系
在很多场景中,需要知道一个类A 是否是从另外一个类B继承,这种校验主要是为了调用B类中的成员(方法和属性)。如果B是A的父类,那么创建A类的实例肯定会拥有B类所有的成员,关健是要判断 B是否为 A 的父类。
判断类与类之间的关系可以使用 issubclass 函数,该函数接收两个参数,第1个参数是子类、第2个乡数是父类。如果第1个参数指定的类与第2个参数指定的类确实是继承关系,那么该函数返回True,否则返回 False。
8.2.6 多继承
Python 类支持多继承,这一点与 C++相同。不过目前支持多继承的面向对象语言不多,但 Python语言算是其中之一。
要想为某一个类指定多个父类,需要在类名后面的圆括号中设置。多个父类名之间用逗号(,)分隔。
class MyClass(MyParent1,MyParent2,MyParent3):
pass #如果类中没有任何代码,必须加一条 pass,否则会编译出错
注意,MyClass 类有三个父类,所以 MyClass 会同时拥有这三个父类的所有成员。但如果多个父类中有相同的成员,例如,在两个或两个以上父类中有同名的方法,那么会按着父类书写的顺序继承也就是说,写在前面的父类会覆盖写在后面的父类同名的方法。在 Python 类中,不会根据方法参数个数和数据类型进行重载。
class Calculator:
def calculatelself,expression):
self.value = eval(expression)
def printResult(self):
print("result:{}".format(self.value))
class MyPrint:
def printResult(self):
print("计算结果:(}".format(self.value))
Calculator 在 MyPrint 的前面,所以Calculator 类中的 printResult方法会覆盖
MyPrint 类中的同名方法
class NewCalculator(Calculator, MyPrint):
pass#如果类中没有代码,需要加 pass 语句
MyPrint 在 Calculator 的前面,所以MyPrint 类中的 printResult 方法会覆盖
calculator 类中的同名方法
class NewCalculatorl(MyPrint,Calculator):
pass #如果类中没有代码,需要加 pass 语句
calc = NewCalculator()
calc.calculate("1+3*5")
运行结果:result:16
calc.printResult()
运行结果:(<class' main_.Calculator'>,<class'main.Myprint">)
print(NewCalculator. bases )
calc1 = NewCalculator1()
运行结果:(<class' main_.MyPrint'>,<class'main.Calculator'>)
print(NewCalculator1. bases )
calcl.calculate("1 +3*5")
运行结果:计算结果:16
calc1.printResult()
8.2.7 接口
在很多面向对象语言(如 Java、C#等)中都有接口的概念。接口其实就是一个规范,指定了一个类中都有哪些成员。接口也被经常用在多态中,一个类可以有多个接口,也就是有多个规范。不过Python语言中并没有这些东西,在调用一个对象的方法时,就假设这个方法在对象中存在吧。当然,更稳妥的方法就是在调用方法之前先使用 hasatr 函数检测一下,如果方法在对象中存在,该函数返回 True,否则,返回 False。
c是一个对象,如果c中存在名为process的方法,hasattr 函数返回True,否则返回 False
print (hasattr(c, "process"))
除了可以使用 hasattr 函数判断对象中是否存在某个成员外,还可以使用 getattr 函数实现同样的功能。该函数有三个参数,其中前两个参数与 hasattr 函数完全一样,第3个参数用于设置默认值。当第2个参数指定的成员不存在时,getattr 函数会返回第3个参数指定的默认值。
与 getattr 函数对应的是 setattr 函数,该函数用于设置对象中成员的值。setattr 函数有三个参数,前两个参数与 getattr 函数完全相同,第3个参数用于指定对象成员的值。
如果c对象中有 name 属性,则更新该属性的值,如果没有 name属性,会添加一个新的 name 属性setattr(c, "name", "new value")
【例8.7】本例创建了一个MyClass类,该类中定义了两个方法:method1 和 default。在调用MyClass对象中的方法时,会首先判断调用的方法是否存在。使用 getattr 函数判断方法是否在对象中存在时,
将 default 方法作为默认值返回。
class MyClass:
def methodl(self):
print("methodl")
def default(self):
print("default"
my = MyClass()
判断 method1 是否在 my 中存在
if hasattr(my, 'method1'):
my.method1()
else:
print("method2 方法不存在")
判断 method2 是否在 my 中存在
if hasattr(my,'method2'):
my.method2()
else:
print("method2 方法不存在")
从 my 对象中获取 method2 方法,如果 method2 方法不存在,返回 default 方法作为默认值
method = getattr(my, 'method2',my.default)
如果 method2 方法不存在,那么 method 方法实际上就是 my.default 方法
method()
def method2():
print("动态添加的 method2")
通过 setattr 函数将 method2 函数作为 method2 方法的值添加到 my 对象中
如果sethod2 方法在my中不存在,那么会添加一个新的method2 方法,相当于动态添加method2方法
setattr(my, 'method2', method2)
调用 my 对象中的 method2 方法
my.method2()
9.2 主动抛出异常
异常可以是系统自动抛出的,也可以由程序员编写代码来主动抛出。本节会详细介绍如何通过ise 语句自己来抛出异常,以及如何自定义异常类。
9.2.1 raise 语句
实例位置:PythonSamples\src\chapter9\demo9.01.py
使用 raise 语句可以直接抛出异常。raise 语句可以使用一个类(必须是 Exception 类或 Exceptior的子类)或异常对象抛出异常。如果使用类,系统会自动创建类的实例。下面的一些代码会使用内的 Exception 异常类抛出异常。
raise Exception
raise Exception("这是自己主动抛出的一个异常")
自定义异常类9.2.2
在很多时候需要自定义异常类。任何一个异常类必须是 Exception 的子类。最简单的自定义异常类就是一个空的 Exception 类的子类。
class MyException(Exception):
pass
下面用一个科幻点的例子来演示如何自定义异常类,以及如何抛出自定义异常。
定义曲速引擎过载的异常类
class WarpdriveOverloadException(Exception):
pass
当前的曲速值
warpSpeed=12
当曲速为 10或以上值时认为是曲速引擎过载,应该抛出异常
if warpSpeed >= 10:
#抛出自定义异常
raise WarpdriveOverloadException("曲速引擎已经过载,请停止或弹出曲速核心,否则飞船将会爆炸")
程序运行结果如图 9-6 所示。
9.3捕捉异常
如果异常未捕捉,系统就会一直将异常传递下去,直到程序由于异常而导致中断。为了尽可能避免出现这种程序异常中断情况,需要对“危险”的代码段进行异常捕捉。在 Python 语言中try...except语句进行异常捕获。那么这个语句有哪些用法呢?要知详情,
9.3.1 try...except 语句的基本用法
try..except 语句用于捕捉代码块的异常。在使用 try..except 语句之前,先看一看不使用该语情况。
x= int(input("请输入分子:"))
y = int(input("请输入分母:"))
print("x / y={}",format(x / y))
执行上面的代码后,分子输入任意的数值,分母输入0,会抛出如图 9-7所示的异常,从而导程序崩溃,也就是说,本来正常执行第3条语句(print 函数),但由于 x/y 中的y变量是 0,所以直抛出了异常,因此,第3条语句后面的所有语句都不会被执行。
从例 9.2中可以了解关于 try...except 语句的如下几方面内容:
口 try...except 语句是一个代码块,所以 try 和 except 后面都要加冒号(:)。
口 try 和 except 之间是正常执行的语句,如果这些代码不发生错误,那么就会正常执行下去,这时 except 部分的代码是不会执行的。如果 try 和 except 之间的代码发生了错误,那么错误点后面的代码都不会被执行了,而会跳到except 子句去执行 except 代码块中的代码。
口 如果 except 关键字后面没有指定任何异常类,那么 except 部分可以捕捉任何的异常,如果想捕捉具体的异常,请继续看本章后面的部分。
9.3.2 捕捉多个异常
我们并不能预估一个代码块到底会不会抛出异常,以及抛出多少种异常。所以需要使用 try..excep!语句捕捉尽可能多的异常,因此,except 子句可以包含任意多个。不过程序员并不能准确估计一个代码块抛出的异常种类,所以使用具体异常类来捕捉异常,有可能会遗漏某个异常,在这种情况下,当抛出这个被遗漏的异常后,程序还是会崩溃,所以比较保险的做法是最后一个 except 子句不使用任何异常类,这样就会捕捉其他所有未指定的异常,从而让程序更加健壮。
try:
except 异常类1:
except 异常类 2:
except 异常类n:
except:
捕捉其他未指定的异常
9.3.3 用同一个代码块处理多个异常
虽然代码块可能抛出多个异常,但有时多个异常的处理程序可以是一个,在这种情况下,如果用多个 except 子句捕捉这些异常,就需要在每一个 except 子句中使用同一段代码处理这些异常。为了解决这个问题,except 子句允许指定多个异常,这样指定后,同一个 except 子句就可以捕捉多个异常了。
try:
.....
except(异常类1,异常类2,异常类3,…,异常类 n):
....
9.3.5 异常捕捉中的 else 子句
与循环语句类似,try..except 语句也有 else 子句。与 except 子句正好相反,except 子句中的代会在 try 和 except 之间的代码抛出异常时执行,而 else 子句会在 try 和 except 之间的代码正常执行后才执行。可以利用 else 子句的这个特性控制循环体的执行,如果没有任何异常抛出,那么循环体就结束,否则一直处于循环状态。
try:
....
except:
抛出异常时执行这段代码
....
else:
正常执行后执行这段代码
....
9.3.6异常捕捉中的 finally 子句
捕捉异常语句的最后一个子句是 finally。从这个子句的名字基本可以断定是做什么用的。所有需要最后收尾的代码都要放到 finally 子句中。不管是正常执行,还是抛出异常,最后都会执行 finally 子
句中代码,所以应该在 fnally 子句中放置关闭资源的代码,如关闭文件、关闭数据库等。如果使用 return 语句退出函数,那么会首先执行 finally 子句中的代码,才会退出函数。因此并不用担心 finally 子句中的代码不会被执行,只要为 try 语句加上了 finally 子句,并且程序执行流程进入了 try 语句,fnally 子句中的代码是一定会执行的。
try:
....
except:
....
finally: #无论是否抛出异常,都会执行 finally 子句中的代码
....
方法,属性和迭代器
构造方法10.1
本章之所以首先介绍构造方法,是因为构造方法非常重要,是创建对象的过程中被调用的第一个方法,通常用于初始化对象中需要的资源,如初始化一些变量。本节会详细介绍 Python 语言中构造方法的使用细节。
class Person:
#Person 类的构造方法
def init (self,name ="Bill"):
print("构造方法已经被调用")
self.name = name
def getName (self):
return self.name
def setName (self name):
self.name= name
创建 person类的实例,在这里 Person 类的构造方法会调用
person = Person()
print(person.getName())
person1 = person(name = "Mike") #创建 Person 类的实例,并指定 name 参数值,构造方法会调用print(personl.getName())
personl.setName(name = "John" )
print(person1.getName())
10.1.2 重写普通方法和构造方法
在第8章学过类的继承,当B类继承A类时,B类就会拥有A类的所有成员变量和方法。如果B类中的方法名与A类中方法名相同,那么B类中同名方法就会重写A类中同名方法。如果在B类中定义了构造方法,同样也会重写A类中的构造方法,也就是说,创建B对象,实际上是调用 B类中的构造方法,而不是A类中的构造方法。
class A:
def init (self):
print("A 类的构造方法")
def method (self):
10.1.3 使用 super 函数
在子类中如果重写了超类的方法,通常需要在子类方法中调用超类的同名方法,也就是说,重写超类的方法,实际上应该是一种增量的重写方式,子类方法会在超类同名方法的基础上做一些其他的工作。
如果在子类中要访问超类中的方法,需要使用 super 函数。该函数返回的对象代表超类对象,所以访问 super 函数返回的对象中的资源都属于超类。super 函数可以不带任何参数,也可以带两个参数
第1个参数表示当前类的类型,第2个参数需要传入 self。
【例 10.3】 本例对 10.1.2节的例子进行改进,再引入了一个 Animal类,Bird 类是 Animal 类的了类。在 Bird 类的构造方法中通过 super 函数调用了 Animal 类的构造方法。在 SongBird 类的构造方法中通过 super 函数调用了 Bird 类的构造方法。
实例位置:PythonSamples\src\chapter10\demo10.03.py
class Animal:
def init (self):
print("Animal init")
class Bird(Animal):
为 Bird 类的构造方法增加一个参数(hungry)
def __init__ (self, hungry):
#调用 Animal 类的构造方法
super().__init__()
self.hungry= hungry
def eat(self):
if self.hungry:
print("已经吃了虫子!")
self.hungry = False
else:
print("已经吃过饭了,不饿了!")
b= Bird(False)
b.eat()
b.eat()
class SongBird(Bird):
def init(self,hungry):
#调用 Bird类的构造方法,如果为 super 函数指定参数,第1个参数需要是当前类的类型(SongBird)
super(SongBird,self).init(hungry)
self.sound ='向天再借五百年!
def sing(self):
print(self.sound)
sb = SongBird(True)
sb.sing()
sb.eat()
10.2 特殊成员方法
尽管构造方法(init)对于一个类非常重要,但还有一些其他的特殊方法也同样重要,因此常有必要介绍一下它们。通过这些特殊方法,可以建立自定义的序列,这是一件非常酷的事。
10.2.1 自定义序列
除了构造方法(init),还可以使用如下4个特殊方法定义自己的序列类,就像以前介绍的表、字典等序列一样,只不过拥有自己特殊的行为。所有的特殊方法在名称前后都需要下画线(__)
口 len(self):返回序列中元素的个数。使用 len 函数获取序列对象的长度时会调用该方法
口getitem(self,key):返回与所给键对应的值。getitem方法的第2个参数表示键(key)在使用 sequence[key]获取值时会调用该方法。
口setitem(self,key,value):设置 key 对应的值。setitem方法的第2个参数表示键(key)第3个参数不表示值(value)。当使用 sequence[key]= value 设置序列中键对应的值时调用该方法。
口 delitem(self, key):从序列中删除键为 key 的 key-value 对。当使用 del 关键字删除序列中键为 key 的 key-value 对时调用该方法。
从这4个方法的描述来看,都是对序列的某些操作触发了这些特殊方法的调用。
10.2.2 从内建列表、字符串和字典继承
到目前为止,已经介绍了与序列/映射相关的 4 个特殊方法,在实现自定义的序列/映射时,需要实现这4个方法,不过每次都要实现所有的4个方法太麻烦了,为此,Python 提供了几个内建类(list、dict 和 str),分别实现列表、字典和字符串的默认操作。要实现自己的列表、字典和字符串,大可不必从头实现这4个方法,只需要从这三个类继承,并实现必需的方法即可。
【例10.5】 本例编写了三个类(CounterList、CounterDict 和 MultiString),分别从 list、dict 和 st继承。其中 CounterList 和 CounterDict 只重写了init方法和getitem 方法,分别用来初始化计数器(counter)和当获取值时计数器加 1。MultiString 类扩展了字符串,可以通过构造方法的可变参数指定任意多个字符串类型参数,并将这些参数值首尾相连形成一个新的字符串,MultiString 类还可以通过构造方法的最后一个参数(sep)设置多个字符串相连的分隔符,默认是一个空格(类似于 print函数)
实例位置:PythonSamples\src\chapter10\demo10.05.py
定义一个从 list 继承的类
class CounterList(list):
#1ist的构造方法必须指定一个可变参数,用于初始化列表
def init(self,args):
super()."init (args)
#初始化计数器
self.counter= 0
#当从列表中获取值时,计数器加1
def getitem(self,index):
self.counter += 1
#调用超类的getitem 方法获取指定的值,当前方法只负责计数器加1
return super(CounterList, self).getitem(index)
创建一个 CounterList 对象,并初始化列表
c= CounterList(range(10))
运行结果:[0,1,2,3,4,5,6,7,8,9]
print(c)
反转列表c
c.reverse()
运行结果:[9,8,7,6,5,4,3,2,1,0]
Print(c)
删除c中的一组值
del c[2:7]
运行结果:[9,8,2,1,0]
print(c)
运行结果:0
print (c.counter)
。将列表c中的两个值相加,这时计数器加 2,运行结果:10
print(c[1] + c[2])
事运行结果:2
print(c,counter)
从上面的程序可以看出,CounterList 类和 CounterDict 类只实现了init方法和getitem方法,当使用 del 语句删除字典中的元素时,实际上调用的是 dict 类的delitem 方法。在实现 MultiString 类时,为new方法和init方法添加了一个可变参数,是为了接收任意多个字符串,然后将这些字符串用分隔符(sep)连接起来。所以,MultiString 类的使用方法与 print 函数类似。
10.3 属性
通常会将类的成员变量称为属性,在创建类实例后,可以通过类实例访问这些属性,也就是读写属性的值。不过直接在类中定义成员变量,尽管可以读写属性的值,但无法对读写的过程进行监视。例如,在读取属性值时无法对属性值进行二次加工,在写属性值时也无法校验属性值是否有效。在Python 语言中可以通过 property 函数解决这个问题,该函数可以将一对方法与一个属性绑定,当读写该属性值时,就会调用相应的方法进行处理。当然,还可以通过某种机制,监控类中所有的属性。
10.3.1 传统的属性
在 Python 语言中,如果要为类增加属性,需要在构造方法(init)中通过 self 添加,如果要读写属性的值,需要创建类的实例,然后通过类的实例读写属性的值。
class MyClass:
def init(self):
self.value=0 #为Myclass 类添加一个 value 属性
10.3.3 监控对象中所有的属性
尽管使用 property 函数可以将三个方法与一个属性绑定,在读写属性值和删除属性时会调用相应的方法进行处理,但是如果需要监控的属性很多,则这样做就意味着在类中需要定义大量的 getter 和setter 方法。所以说,propery 函数只是解决了外部调用这些属性的问题,并没有解决内部问题。本节介绍三个特殊成员方法(getattr、setattr和delattr),当任何一个属性进行读写和删除操作时,都会调用它们中的一个方法进行处理。
口getattr(self,name):用于监控所有属性的读操作,其中 name 表示监控的属性名。
口setattr(self,name,value):用于监控所有属性的写操作,其中 name 表示监控的属性名,value表示设置的属性值。
口delattr(self,name):用于监控所有属性的删除操作,其中 name 表示监控的属性名。
【例 10.8】 本例重新改写了 10.3.2 节的 Rectangle 类,在 Rectangle 类的构造方法中为 Rectangle类添加了4个属性(width、height、left 和 top),并定义了 setattr、getattr 和delatr 方法,分别用于监控这4个属性值的读写操作以及删除操作。在这三个特殊成员方法中访问了 size 和 positior属性。读写和删除 size 属性实际上操作的是 width 和 height 属性,读写和删除 position 属性实际上操作的是 left 和 top 属性。
实例位置:PythonSamples\src\chapter10\demo10.08.py
class Rectangle:
def init(self):
self,width=0
self.height=0
self.left=0
self.top=0
#对属性执行写操作时调用该方法,当设置 s1ze属性和 position 属性时实际上
#设置了 width 属性、height 属性以及 left 属性和 top 属性的值
def setattr(self,name,value);
print("{}被设置,新值为(}",format(name,value))
if name == 'size';
self.width, self.height = value
elif name == 'position';
self.left, self,top = value
else:#_dict-是内部维护的一个特殊成员变量,用于保存成员变量的值,所以这条语句必须加上
self.dict [name]= value
#对属性执行读操作时调用该方法,当读取 s1ze 属性和 posltion 属性值时实际上
#返回的是 width 属性、helght 属性以及 1eft 属性和 top 属性的值
def getattr(self,name):
print("{)被获取".format (name))
if name == 'size':
return self.width,self.height
elif name == 'position':
return self.left, self.top。
#当删除属性时调用该方法,当删除 size 属性和 position 属性时,实际上是
#重新将 width 属性、height 属性、left 属性和 top 属性设置为0
def delattr(self,name):
if name == 'size':
self.width,self.height =0, 0
elif name == 'position':
self.left, self.top = 0,0
r = Rectangle()
设置 size 属性的值
r.size = 300,500
设置 position 属性的值
r.position =100,400
获取 size 属性的值
print('size','=', r.size)
获取 position 属性的值
print('position','=', r.position)
删除 size 属性和 position 属性
del r.size,r.position
print(r.size)
print(r.position)
10.4 静态方法和类方法
Python 类包含三种方法:实例方法、静态方法和类方法。其中,实例方法在前面的章节已经多次使用了。要想调用实例方法,必须要实例化类,然后才可以调用。也就是说,调用实例化方法需要类的实例(对象)。而静态方法在调用时根本不需要类的实例(静态方法不需要 self参数)、这一点和其他编程语言(如 Java、C#等)完全一样。
类方法的调用方式与静态方法完全一样,所不同的是,类方法与实例方法的定义方式相同,都需要一个 self参数,只不过这个 self参数的含义不同。对于实例方法来说,这个 self参数就代表当前类的实例,可以通过 self 访问对象中的方法和属性。而类方法的 self 参数表示类的元数据,也就是类本身,不能通过 self 参数访问对象中的方法和属性,只能通过这个 self参数访问类的静态方法和静态属
定义静态方法需要使用@saicmethod 装饰器(decorator),定义类方法需要使用@classmethod装
饰器。
class MyClass:
#实例方法
def instanceMethod(self):
pass
#静态方法
@staticmethod
def staticMethod():
pass
#类方法
@classmethod
def classMethod(self):
pass
10.5迭代器
在前面的章节中多次使用迭代器(iterator)。迭代就是循环的意思,也就是对一个集合中的元素进行循环,从而得到每一个元素。对于自定义的类,也可以让其支持迭代,这就是本节要介绍的特殊成员方法iter的作用。
10.5.2 将迭代器转换为列表
尽管迭代器很好用,但仍然不具备某些功能,例如,通过索引获取某个元素,进行分片操作。这些操作都是列表的专利,所以在很多时候,需要将迭代器转换为列表。但有很多迭代器都是无限迭代的,就像 10.5.1节中的斐波那契数列的迭代。因此,在将迭代器转换为列表时,需要给迭代器能够迭代的元素限定一个范围,否则内存就会溢出。要想让迭代器停止迭代,只需要抛出 StopIteration 异常即可。通过 list 函数可以直接将迭代器转换为列表。
10.6 生成器
如果说迭代器是以类为基础的单值产生器,那么生成器(generator)就是以函数为基础的单值产生器。也就是说,迭代器和生成器都只能一个值一个值地生产。每迭代一次,只能得到一个值。所同的是,迭代器需要在类中定义iter和next 方法,在使用时需要创建迭代器的实例。而生成器是通过一个函数展现的,可以直接调用,所以从某种意义上来说,生成器在使用上更简洁。
定义一个生成器函数
def myGenerator();
numList = [l,2,3,4,5,6,7,8]
for num in numList:
#yield 语句会冻结当前函数,并提交当前要生成的值(本例是 num)
yield num:
#对生成器进行迭代
for num in myGenerator():
print(num,end='')
定义一个生成器函数
def myGenerator();
numList = [l,2,3,4,5,6,7,8]
for num in numList:
yield 语句会冻结当前函数,并提交当前要生成的值(本例是 num)
yield num:对生成器进行迭代
for num in myGenerator():
print(num,end='')
如果将 yield num 换成 print(num)就非常容易理解了,对 numList 列表进行迭代,并输出该列表中每一个元素值。不过这里使用了 yield 语句来提交当前生成的值,也就是 for 循环中 num 的值,然后myGenerator 函数会被冻结(暂停不再往下执行了),直到 for循环继续下一次循环,再次对 myGenerator函数进行迭代,myGenerator 函数才会继续执行,继续使用 yield 语句提交下一个要生成的值,直到numList 列表的最后一个元素为止。从这一点可以看出,生成器函数是惰性的,在迭代的过程中,每取一个值,生成器函数就往下执行一步。
10.6.2 递归生成器
在例 10.12 中,对一个二维列表进行了一维化处理。要想对三维、四维甚至更多维的列表维化处理,可以采用递归的方式进行处理。处理的方式是先对多维列表进行迭代,然后判断很元素是否还是列表:如果仍然是列表,则继续对这个列表进行迭代;如果只是一个普通的值,则使用yield 语句返回生成的值。
【例 10.13】 本例会利用生成器函数将一个多维列表进行一维化处理。
实例位置:PythonSampleslsrc\chapter10\demo10.13.py
将多维列表进行一维化处理
def enumList(nestedList):
try:
#对多维列表进行迭代
for subList in nestedList:
#将多维列表中的每一个元素传入 enumlist 函数,如果该元素是一个列表,那么会继续迭代
#否则会抛出 TrypeError 异常,在异常处理代码中直接通过 yield 语句返回这个普通的元素值
#这个异常也是递归的终止条件
for element in enumList(subList):
yield element
except TypeError:
将普通的列表值作为生成值返回
yield nestedList
nestedlist = [4,[1,2,[3,5,6],[4,3,[1,2,[4 5]],2],[1,2,4,5,7]]
迭代生成器
for num in enumList(nestedList):
print(num, end='')
运行结果:
412356431245212457
如果多维列表中的某个元素值是字符串类型,那么也会进行迭代,原因是字符串可以看作字符的列表。因为希望将字符串作为一个整体输出,所以在进行迭代之前,先要判断当前元素值是不是字符串类型,如果是字符串类型,则直接通过 yield 语句返回即可。判断一个值是否是字符串的最简单方法就是使用 try 语句。因为只有字符串才能与另一个字符串进行连接(使用“+”运算符),所以一个非字符串类型的值与字符串相加一定会抛出异常,这样就很容易可以判断与一个字符串相加的另一个值是否为字符串类型。
将多维列表进行一维化处理,字符串整体返回
def enumList(nestedList):
try:
try: nestedList + ' ' #如果 nestedlist不是字符串类型的值,会抛出异常
except TypeError:
pass #如果 nestedlist不是字符串类型的值,会继续使用 for 语句对其进行迭代
else:
#如果 nestedlist 是字符串类型的值,直接抛出 TypeError 异常,在异常处理代码中
#会直接通过 yield 语句返回该值
raise TypeError
#继续对 nestedList 进行迭代
for subList in nestedList:
for element in enumList(subList):
yield element
except TypeError:
yield nestedList
nestedlist = ['a',['b',['c'], 20,123, [['hello world']]]]
for num in enumList(nestedList):
Print(num, end='')
运行结果:
a b c 20 123 hello world
正则
11.1.3 匹配多个字符串
在前面的例子中,只是通过 search 方法搜索一个字符串,要想搜索多个字符串,如搜索 bike、car 、truck,最简单的方法是在文本模式字符串中使用择一匹配符号(|)。择一匹配符号和逻辑或类似,只要满足任何一个,就算匹配成功。
s='bike l car l truck" #定义使用择一匹配符号的文本模式字符串
m= re.match(s, "bike') #bike满足要求,匹配成功
print (m.group()) #运行结果:bike
m= re.match(s, 'truck') #truck 满足要求,匹配成功
print (m.group()) #运行结果:truck
11.1.4 匹配任何单个字符
在前面给出的文本模式字符串都是精确匹配,不过这种精确匹配的作用不大,在正则表达式中最常用的是匹配一类字符串,而不是一个。所以就需要使用一些特殊符号表示一类字符串。本节将介绍第1个可以匹配一类字符串的特殊符号:点(.)。这个符号可以匹配任意一个单个字符。
m = re.match('.ind','bind')#匹配成功
在上面的代码中,文本模式字符串是“.ind”,第1个字符是点(.),表示可以匹配任意一个字符串。也就是说,待匹配的字符串,只要以“.ind”开头,都会匹配成功,其中“”可以表示任意一字符,例如,“bind”“xind”“5ind”都可以和文本模式字符串“.ind” 成功匹配。
使用点(.)符号会带来一个问题,如果要匹配真正的点(.)字符,应该如何做呢?要解决这问题,需要使用转义符()。
m = re.match('.ind','bind')# 匹配失败
在上面的代码中,由于使用了转义符修饰点(.)符号,所以这个点就变成了真正的字符点(.)所以匹配“bind”很显然就失败了,应该匹配“.ind”才会成功。
11.15 使用字符集
如果待匹配的字符串中,某些字符可以有多个选择,就需要使用字符集([]),也就是一对中括号来的字符串。例如,[abe]表示 a、b、c三个字符可以取其中任何一个,相当于“alble”,所以对单字符使用或关系时,字符集和择一匹配符的效果是一样的。
m= re.match('[abcd]', 'a')#使用字符集,匹配成功
print(m.group()) #运行结果:a
ms re.match('alblcld', 'a') #使用择一匹配符,匹配成功
print (m.group()) #运行结果:a
对长度大于1的字符串使用或关系时,字符集就无能为力了,这时只能使用择一匹配符。因为字符集会将中括号括起来的字符串拆成单个的字符,然后再使用“或”关系。
使用字符集,匹配成功
m = re.match('[abcd]",'ab')
运行结果:< sre,SRE Match object; span=(0,1), match='a'>
print(m)
使用择一匹配符,匹配成功
m = re.match('ablcd', "ab')
运行结果:< sre.SRE Match object; span=(0,2), match='ab'>
print(m)
如果将多个字符集写在一起,相当于字符串的连接。
相当于匹配以第1个字母是a或b,第2个字母是c或d开头的字符串,如ac、acx等
m = re.match('[ab][cd]",'ac')
运行结果:< sre.SRE Match object; span=(0,2),match='ac'>
print(m)
11.1.6 重复、可选和特殊字符
正则表达式中最常见的就是匹配一些重复的字符申,例如,匹配3个连续出现的a(aaa 符合要求),或匹配至少出现一个0的字符串(0、00、000都符合要求)、要对这种重复模式进行匹配,需要使用两个字符:””和“+”。其中,“”表示字符串出现0到n次,“+” 表示字符中出现1到n次。
s = 'a' #使用“”修饰 a
strList = ['', 'a', "aa','baa']
for value in strList:
m= re.match(s,value)
print(m)
如果对“a”使用“+”符号,就意味着“a”至少要出现1次,所以空串自然就无法匹配成功,这就是"和"baa"都无法匹配成功的原因。
前面的例子都是重复一个字符,如果要想将多个字符作为一组重复,需要用一对圆括号将这个字
将串括起来。
s='(abc)+' #匹配 abe 至少出现1 次的字符串
print(re.match(s, 'abcabcabc')) #匹配成功
除了“*"和“+”外,还有另外一个常用的符号“?”,表示可选符号。例如,“a?”表示或者有或没有a,即a可有可无。下面的代码利用“?”符号指定了匹配字符串的前缀和后缀,前缀可以是一个任意的字母或数字,而后缀可以是至少一个数字,也可以不是数字,中间必须是“wow”。在这里要引入两个特殊符号:“\w”和“\d”。其中“\w”表示任意一个字母或数字,“\d”表示任意一个数字。
s='w?wow(\d?)+' #使用了“?”“+”和“\w”“\d”的模式字符串
m = re.search(s, 'awow') #匹配成功
print(m)
m = re.search(s, 'awow12') #匹配成功
print(m)
m = re.search(s, 'wow12') #匹配成功
print(m)
m = re.search(s,'ow12') #匹配失败,因为中间不是“wow”
print(m)
import re
匹配'a'、"b'、"c"三个字母按顺序从左到右排列,而且这3个字母都必须至少有1个
abc、aabc、abbbccc 都可以匹配成功
s ='a+b+c+'
strList =['abc','aabc',"bbabc' "aabbbcccxyz']
只有"bbabc"无法匹配成功,因为开头没有'a’
for value in strList:
m = re.match(s, value)
if m is not None:
print (m.group())
else:
print('{}不匹配{}'.format (value,s))
print('--------------!
匹配任意3个数字-任意3个小写字母
123-abc、433-xyz 都可以成功
下面采用了两种设置模式字符串的方式
在本例中还用了一些特殊标识符,例如,[a-z]、[A-Z]、[0-9]是字母或关系的简写形式,分别表示26 个小写字母(az)中的任何一个,26个大写字母(AZ)中的任何一个,10个数字(0~9)中的任何一个。{N}形式表示前面修饰的部分重复N次,例如“(abc){3}”表示字符串“abc”重复3次相当于“abcabcabc”。还有就是,如果要修饰多于一个字母的字符串,要用圆括号将字符串括起来否则只会修饰前面的一个字符,例如,“abc{3}”表示字母“c”重复3次,而不是“abc”重复3次相当于“abccc”