本篇博文包含十章学习python必备的基础知识
一、变量和数据类型
1.变量命名
-
变量名只能包含字母、数字和下划线,数字不能打头
-
关键字和函数名不可以作为变量名
-
慎用小写字母l和大写字母O,以免被错认为数字1和0
在Python中,变量是可以赋给值的标签,也可以说变量指向特定的值
2.字符串
在python中,引号括起来的都是字符串。可以是单引号,也可以是双引号
修改字符串大小写
name = "ada"
print(name.title())
方法是Python可对数据执行的操作
在这个示例中,输出首字母大写的name变量。name后面点的作用是让python对变量name实现指定操作。每个方法后面都有圆括号,这是因为方法通常需要额外的信息来完成其工作。函数title不需要额外信息,所以括号中为空。
如果全部改为大写或小写,可以用如下代码:
print(name.upper())
print(name.lower())
在字符串中使用变量
nameA = "ada"
nameB = "love"
full = f"{nameA} {nameB}"
print(full)
要在字符串中插入变量的值,可以在引号前面加上字母f。这种字符串名为 f字符串
f是format的缩写,python通过把花括号内的变量替换为其值来设置字符串的格式,上述代码输出ada love
nameA = "ada"
nameB = "love"
full = f"{nameA} {nameB}"
print(f"hello,{full.title()}")
输出hello,Ada Love
3.空白
制表符\t
,换行符\n
,使用它们生成空白
在程序中,额外的空白可能使人疑惑,多一个空格都会使两个字符串不同。
要永久删除某个字符串末尾的空白,可以使用如下代码
lan = "python "
lan = lan.rstrip()
此后lan的值中不再有字母n后面的空白
剔除字符串开头的空白或同时剔除字符串两边的空白,可以分别使用方法lstrip()和strip()
lan = lan.lstrip()
lan = lan.strip()
4.数
整数
在python中,可以执行加(+)减(-)乘(*)除(/)四则运算,也可以使用括号改变运算优先级
除此之外还有乘方运算,如3 ** 2结果为9
浮点数
也可以执行四则运算,但要注意浮点数运算结果永远是一个近似值
整数和浮点数
任意两个数相除结果都是浮点数,即使他们本来是整数且能整除
在其他运算中只要有一个操作数为浮点数则结果为浮点数
数中的下划线
书写很大的数时可用下划线分组,1000与1_000在运算中没什么不同
5.多变量赋值
x,y,z = 0,0,0
用逗号将变量名隔开,对于要赋给的值也同样如此处理
6.常量
类似于变量,但其值在程序的整个生命周期内保持不变。python没有内置常量类型,但我们可以使用全大写指出某个变量应该为常量。其值应始终不变
7.注释
用井号(#)标识注释,井号后面的内容都会被python解释器忽略
二、列表简介
1.列表是什么
列表由一系列按特定顺序排列的元素组成,某些方面很像C语言的数组
在python中,用方括号[]表示列表,并用逗号分割其中的元素。
num = [1,2,3,4]
print(num)
如果要让python打印列表,python将连同方括号一起打印
2.访问列表元素
num = [1,2,3,4]
print(num[0])
这样输出数字1。这里要注意索引从0开始
另外,python中可通过将索引指定为-1可以返回最后一个列表元素,-2返回倒数第二个,以此类推
3.使用列表中的各个值
language = ['c','python','java']
message = f"My first language is {language[0].title()}"
print(message)
可以像变量一样使用这些值
4.修改元素
num = [1,2,3,4]
print(num)
num[0] = 5
print(num)
5.添加元素
append()
num = [1,2,3,4]
print(num)
num.append(5)
print(num)
方法append将元素添加到列表末尾,现在这个列表中有五个元素了
在实际开发中,为控制用户,可首先创建一个空列表,用于存储用户将要输入的值,然后将用户提供的每个新值都附加到列表中。
insert()
names = ['fang','wen','long']
print(names)
names.insert(0,'xi')
print(names)
在这个列表中,使用方法insert在列表的第一个位置插入了’xi’。这种操作使其他元素都右移了一个位置
可以用该方法将元素插入到任意位置
6.删除元素
del语句实现
names = ['fang','wen','long']
print(names)
del names[0]
print(names)
这样删除掉了列表中的第一个元素,同理可以删除任意元素
使用方法pop
方法pop()删除列表末尾的元素,并让你可以接着使用它。术语弹出(pop)来源于这样的对比:列表就像一个栈,而删除列表末尾的元素相当于弹出栈顶元素。
names = ['fang','wen','long']
print(names)
man_name = names.pop()
print(man_name)
print(names)
这样弹出最后的元素’long’,并赋给变量man_name
当然可以弹出其他位置的元素,如下所示弹出第一个元素
names = ['fang','wen','long']
print(names)
man_name = names.pop(0)
print(man_name)
print(names)
根据值删除
如果不知道位置而只知道删除元素的值,可以使用方法remove(),如下所示
names = ['fang','wen','long']
print(names)
names.remove('fang')
print(names)
这样删除掉值为’fang’的元素,需要注意的是这样只删除掉一个为该值的元素
7.列表排序
sort()方法永久排序
names = ['fang','wen','long']
names.sort()
print(names)
这样让列表升序排列,降序如下所示
names = ['fang','wen','long']
names.sort(reverse = True)
print(names)
sorted()函数临时排序
names = ['fang','wen','long']
print(sorted(names))
只在本次输出中升序排列,降序多传入一个参数(reverse = True)即可
reverse()方法使列表永久颠倒
names = ['fang','wen','long']
names.reverse()
print(name)
注意,reverse不是排序方法,它只是让整个列表的顺序颠倒过来
8.列表长度
names = ['fang','wen','long']
changdu = len(names)
print(changdu)
使用len函数直接得到了一个列表的长度
9.使用clear()方法清空列表
names = ['chen','hong','sheng']
names.clear()
三、操作列表
1.遍历列表
names = ['lichenxi','chenhongsheng']
for name in names:
print(name)
使用这个for循环遍历整个列表,输出每个元素
2.循环
for name in names:
print(name)
这是一个循环,通过缩进表示层次关系,由于缩进了print语句,即意味着print语句在该循环内反复执行。注意不要遗漏冒号
3.创建数值列表
使用函数range()
for value in range(1,5):
print(value)
这里只输出1到4,因为range参数左闭右开
使用函数list()创建数字列表
numbers = list(range(1,6))
print(numbers)
这样numbers就是一个有五个数字元素的列表[1, 2, 3, 4, 5]
使用range函数时还可以指定步长,为此,给函数加入第三个参数
numbers = list(range(2,11,2))
print(numbers)
在这个示例中,列表中的元素从2开始,不断加2,创建了一个列表[2, 4, 6, 8, 10]
numbers = []
for value in range(1,11):
square = value ** 2
numbers.append(square)
print(numbers)
在这个示例中,创建[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
4.对数字列表执行简单的统计计算
numbers = list(range(2,11,2))
print(numbers)
print(sum(numbers))
print(min(numbers))
print(max(numbers))
5.列表解析
numbers = [value ** 2 for value in range(1,11)]
print(numbers)
列表解析将for循环和创建新元素的代码合并成一行,并自动附加新元素
6.使用列表的一部分
处理列表的部分元素,python称其为切片
切片
numbers = [value ** 3 for value in range(1,11)]
print(numbers[0:3])
这样打印该列表的一个切片,其中只包含前三个元素(左闭右开)
可以生成列表的任意子集,如果没有指定第一个索引,python自动从列表开头开始;如果省略终止索引,将截止到列表结束。
可以使用负数,如numbers[-3:]切出最后三个元素
遍历切片
numbers = [value ** 3 for value in range(1,11)]
for number in numbers[0:3]:
print(number)
这样遍历前三个数字
复制列表
numbers = [value ** 3 for value in range(1,11)]
copy = numbers[:]
这样创建了一个numbers列表的复制
7.元组
python将不能修改的值称为不可变的,而不可变的列表被称为元组
定义元组
dimensions = (200,50)
print(dimensions[0])
print(dimensions[-1])
如果试图修改元组中的元素,就会引发错误
遍历元组
dimensions = (200,50)
for unit in dimensions:
print(unit)
和列表一样,可以遍历整个元组
修改元组变量
虽然不能修改元组元素,但可以给存储元组的变量重新赋值
dimensions = (200,50)
dimensions = (400,50)
8.设置代码格式
访问python网站并阅读PEP 8格式设置指南
-
python改进提案(PEP)
-
每次缩进四个空格(一个制表符)
-
每行不超过80字符
-
使用空行提高可读性
四、if语句
1.示例
cars = ['audi','bmw','subaru','toyota']
for car in cars:
if car == 'bmw':
print(car.upper())
else:
print(car.title())
输出结果如下所示:
Audi
BMW
Subaru
Toyota
if语句和else语句末尾都有冒号,一定要注意这点
2.条件测试
检查是否相等使用==,注意python会区分大小写,即相同单词大小写不同是不等的
检查是否不等使用!=,仍然会区分大小写
数值比较使用>=,>,<=,<,注意和C语言不同的地方在于python中这样写是有效的:
if 12 < age < 18:
使用and时必须两个条件都满足,使用or时至少要有一个条件满足(类似于C语言中的&&和||)
3.检查特定值是否包含在列表中
users = ['fang','chen','long']
user = 'chen'
if user in users:
print(users)
if user not in users:
print(user)
关键字in让python检查列表中是否包含元素’chen’,关键字not in检查列表中是否不包含元素’chen’。如果这里把’chen’改为’sds’,则会输出’sds’
4.布尔表达式
布尔表达式的结果要么为True,要么为False。它通常用于记录条件
5.if语句
在if语句中,缩进的作用和在for循环中的相同。第一行可包含任意条件测试,在紧跟在测试后的缩进代码块中可执行任何操作,如果结果为True,python就会执行紧跟在if语句后面的代码。否则python将忽略它们
6.if-else语句
如果if语句中的条件测试通过就执行它后面的代码,否则执行else后面的代码
7.if-elif-else语句
当遇到超过两个的情景,我们可以使用如下代码:
age = 12
if age < 4:
print("cost is $0")
elif age < 18:
print("cost is $25")
else:
print("cost is $40")
elif可以使用多个,它后面也必须跟一个条件测试
当然可以选择不用else语句,这取决于实际开发情况
注意,根据应用不同,来选择用if-elif还是单独的多个if结构
8.使用if语句处理列表
toppings = ['mushrooms','green pepers','extra cheese']
for topping in toppings:
if(topping == 'green pepers'):
print("No")
else:
print("Yes")
当遍历到元素’green pepers’就会输出No
那如果要确定列表是否为空应该怎么做?
toppings = []
if toppings:
for topping in toppings:
if(topping == 'green pepers'):
print("No")
else:
print("Yes")
else:
print("none")
因为这时的列表是空的,所以会输出none。如果列表不为空就会执行if下的代码块
五、字典
1.一个简单的字典
alien = {'color': 'green', 'point': 5}
print(alien['color'])
print(alien['point'])
输出如下所示
green
5
2.使用字典
在python中,字典是一系列键值对,每个键都与一个值相关联。你可以使用键来访问相关联的值。与值相关联的可以是数字、字符串、列表、甚至字典。事实上可以将任何python对象用作字典中的值
在python中,字典用放在花括号中的一系列键值对来表示,如刚刚的示例所示。键值对之间用逗号分隔,可以存储任意个键值对
访问字典中的值
要获取与键相关联的值,可依次指定字典名和放在方括号里的键,如下所示:
alien = {'color': 'green'}
print(alien['color'])
这将返回字典中与键’color’相关联的值
添加键值对
alien = {'color': 'green'}
print(alien['color'])
alien['x_position'] = 0
alien['y_position'] = 25
print(alien)
这样在字典中新增了两个键值对,现在它一共有三个键值对了
先创建一个空字典
alien = {}
alien['x_position'] = 0
alien['y_position'] = 25
print(alien)
这里先定义了一个字典,然后再在里面增加键值对
3.修改字典中的值
alien = {'color': 'green'}
print(alien)
alien['color'] = 'yellow'
print(alien)
这样修改了外星人的颜色
4.删除键值对
alien = {'color': 'green', 'point': 5}
del alien['point']
print(alien)
使用del语句删除了’point’键值对,其他键值对不会受影响
5.由类似对象组成的字典
favorite_languages = {
'chen': 'Python',
'long': 'C',
'fang': 'Java',
}
language = favorite_languages['chen'].upper()
print(f"this is {language}")
这种语法可以从字典中获取任何人喜欢的语言
6.使用get()来访问值
favorite_languages = {
'chen': 'Python',
'long': 'C',
'fang': 'Java',
}
language = favorite_languages.get('chen', 'hahaha')
print(f"this is {language}")
方法get()的第一个参数用于指定键,是不可或缺的;第二个参数为指定的键不存在时的返回值,是可选的
这样无论字典中有没有这个键程序都不会出错,如果没有’chen’这个键,则会输出this is hahaha
如果没有指定第二个参数且指定的键不存在,将返回None
7.遍历字典
遍历所有键值对
favorite_languages = {
'chen': 'Python',
'long': 'C',
'fang': 'Java',
}
for k,v in favorite_languages.items():
print(f"\nKey:{k}")
print(f"Value:{v}")
要编写遍历字典的for循环,可以声明两个变量,用于存储键值对中的键和值。这两个变量可以使用任意名称。
for语句的第二部分包含字典名和方法items()——它返回一个键值对列表。接下来,for循环以此将每个键值对赋给指定的两个变量,在本例中,使用这两个变量来打印每个键及其相关联的值
遍历字典中的所有键
在不需要字典中的值时,方法**keys()**很管用。如下所示:
favorite_languages = {
'chen': 'Python',
'long': 'C',
'fang': 'Java',
}
for name in favorite_languages.keys():
print(f"Name:{name}")
这和以下代码块作用相同
favorite_languages = {
'chen': 'Python',
'long': 'C',
'fang': 'Java',
}
for name in favorite_languages:
print(f"Name:{name}")
可见是否使用keys()不会影响输出,但使用它可以让代码的可读性提高
方法keys并非只能用于遍历:实际上,它返回一个列表,其中包含字典中的所有键。
按照特定顺序遍历字典中的所有键
favorite_languages = {
'chen': 'Python',
'long': 'C',
'fang': 'Java',
}
for name in sorted(favorite_languages):
print(f"Name:{name}")
这条for语句与其他for不同之处在于它在遍历前对这个列表进行了排序
遍历字典中的所有值
如果我们只对值感兴趣,可以用方法values()来返回一个值列表
favorite_languages = {
'chen': 'Python',
'long': 'C',
'fang': 'Java',
}
for lan in favorite_languages.values():
print(f"language:{lan}")
这样输出了字典中所有的值
但是如果由有键的值是相同的,我们可能会输出大量重复数据,为了规避这一情况,可使用集合(set),集合中的每个元素都是独一无二的,如下所示
favorite_languages = {
'chen': 'Python',
'long': 'C',
'fang': 'Java',
'xuan': 'C',
'xi':'Python',
}
for lan in set(favorite_languages.values()):
print(f"language:{lan}")
通过调用set(),可让python找出列表中独一无二的元素,并且使用这些元素来创建一个集合,结果是一个不重复的列表
可使用一对花括号直接创建集合,并在其中用逗号分隔元素
language = {'Python','C','Python','Java'} print(language)
这样只会输出一次’Python’
集合与字典不同的地方在于集合中的元素不是键值对
8.嵌套
在列表中存储字典
我们可以让几个字典组成一个列表,如下所示:
alien_0 = {'color': 'green', 'point': 5}
alien_1 = {'color': 'red', 'point': 10}
alien_2 = {'color': 'yellow', 'point': 15}
aliens = [alien_0, alien_1, alien_2,]
for alien in aliens:
print(alien)
我们还可以使用range()生成外星人:
# 存储外星人的列表
aliens = []
# 用range生成30个外星人
for alien in range(30):
new = {'color': 'green', 'points': 5, 'speed': 'slow'}
aliens.append(new)
# 显示现在列表中有多少个外星人
print(f"Total number of aliens: {len(aliens)}")
在字典中存储列表
下面是一个示例:
pizza = {
'crust': 'thick',
'toppings': ['mushrooms', 'extra cheese'],
}
这个字典中的信息由于使用了列表而更加丰富
favorite_languages = {
'chen': ['Python', 'C'],
'long': ['C'],
'fang': ['Java', 'C'],
}
for name,language in favorite_languages.items():
if len(language) == 1:
print(f"\n{name.title()}'s favorite languages is:")
else:
print(f"\n{name.title()}'s favorite languages are:")
for lan in language:
print(f"\t{lan.title()}")
这样每个人就可以同时有几个最喜欢的语言了
在字典中存储字典
users = {
'chen': {
'first': 'chen',
'location': 'Tianjin',
},
'fang': {
'first': 'fang',
'location': 'Sichuan',
},
}
for username,info in users.items():
print(f"\nUsername: {username}")
print(f"Location: {info['location']}")
六、用户输入和while循环
1.函数input()
函数input让程序暂停运行,等待用户输入一些文本,获取输入后,python将其赋给一个变量
message = input("tell me:")
print(message)
函数input接受一个参数——要向用户显示的**提示(promot)**或者说明,将其呈现给用户,方便用户输入应该输入的东西。每当使用函数input时,都应该有清晰易懂的提示。
有时候,提示可能超过一行。例如,你可能需要指出获取特定输入的原因。在这种情况下,可将提示赋给一个变量,然后将该变量传入input函数中
prompt = "emmm"
prompt += "\nWhat is your name:"
name = input(prompt)
print(f"\nHello,{name}!")
这样提示占据了两行
2.函数int()获取数值输入
使用函数input时,python将用户输入解读为字符串,这样输入的数无法用作运算
为解决这个问题,可使用函数int,它让python将输入视为数值。函数int将数的字符串表示转换为数值表示
age = input("How old are you:")
age = int(age)
if(age >= 18):
print("adult")
else:
print("too young")
在本例中,输入21后python将其解读为字符串,但随后int将这个字符串转换为了数值表示,这样python就能运行条件测试了。将数值输入用于计算和比较前,务必将其转换为数值表示
3.求模运算符
**求模运算符%**在处理数值信息时很有用,它将两个数相除并且返回余数
4.while循环
for循环用于针对集合中的每个元素都执行一个代码块,而while循环不断运行,直到指定的条件不满足为止
使用while循环
下面的while循环表示从1数到5:
num = 1
while num <= 5:
print(num)
num += 1
一旦num大于5,循环就将停止
让用户决定何时退出
可以使用while循环让程序在用户愿意时不断运行,我们定义一个退出值,只要用户输入的不是这个值,程序就将接着运行:
prompt = "I can repeat your message"
prompt += "\nEnter 'quit' to end the program"
message = ""
while message != 'quit':
message = input(prompt)
print(message)
首次执行循环时,message是一个空字符串,因此python进入循环。等到用户输入quit时python才会退出循环,否则将一直运行
使用标志
如果有多种情况导致循环结束,这时该怎么办呢?
在要求很多条件都满足才继续运行的程序中,可定义一个变量,判断是否整个程序处于活动状态,这个变量称为标志。将控制标志的测试放在其他地方,从而让程序更简洁
prompt = "I can repeat your message"
prompt += "\nEnter 'quit' to end the program"
message = ""
active = True
while active:
message = input(prompt)
if(message == 'quit'):
active = False
else:
print(message)
这个程序作用和前一个程序基本相同,区别在于前一个示例将条件测试放在了while语句中,而这个程序则使用一个标志来指出程序是否处于活动状态。
使用break来退出循环
prompt = "I can repeat your message"
prompt += "\nEnter 'quit' to end the program"
message = ""
while True:
message = input(prompt)
if(message == 'quit'):
break
else:
print(message)
以while True打头的循环将不断运行,直到遇到break语句。break语句直接打破循环
使用continue来返回循环开头
num = 0
while num < 10:
num += 1
if num % 2 == 0:
continue
print(num)
continue语句执行后跳过本轮循环后面的所有代码,因此这个程序输出了所有10以内的奇数
使用while语句时一定要注意避免出现死循环
5.使用while循环处理列表和字典
for循环是一种遍历列表的有效方式,但不应该在for循环中修改列表,否则将导致python难以跟踪其中的元素。要在遍历列表的同时对其进行修改,可使用while循环
在列表之间移动元素
unconfirmed = ['alice', 'brian', 'candace']
confirmed = []
while unconfirmed:
current = unconfirmed.pop()
print(f"Verifying user:{current.title()}")
confirmed.append(current)
print("\nThe following users have been confirmed:")
for confirmed_user in confirmed:
print(confirmed_user.title())
首先创建了一个未验证的用户列表,包含了几个用户,然后创建一个空列表,用于存储已验证的用户
while循环不断运行,直到未验证列表为空;每轮循环弹出一个用户,将其确认。最后所有用户都已验证
Verifying user:Candace
Verifying user:Brian
Verifying user:Alice
The following users have been confirmed:
Candace
Brian
Alice
删除为特定值的所有列表元素
在前面我们提到可以用remove来删除列表中的特定值,但它只能删除一次。如果要删除列表中所有为特定值的元素,我们可以使用while循环
pets = ['cat', 'dog', 'rabbit', 'cat']
print(pets)
while 'cat' in pets:
pets.remove('cat')
print(pets)
这样删除掉了列表中的所有’cat’
使用用户输入来填充字典
survey = {}
active = True
while active:
name = input("\nWhat is your name? ")
response = input("Who is your girlfriend? ")
survey[name] = response
repeat = input("Would you like to let another person respond? (yes/no) ")
if repeat == 'no':
active = False
print("\nTHE SURVEY")
for k,v in survey.items():
print(f"{k}'s girlfriend is {v}.")
七、函数
1.定义函数
下面是一个简单的函数:
def greet():
"""显示问候语。"""
print("Hello!")
greet()
使用关键字def来告诉python,你要定义一个函数。这是函数定义,向python指出了函数名,还可能在圆括号内指出函数为完成任务需要什么样的信息。在这里函数名为greet,它不需要任何信息就能完成工作,但仍然需要括号。最后以冒号结尾。
后面的缩进代码是函数体,"""显示问候语。"""
是称为文档字符串的注释,描述了函数是用来做什么的。python使用它们来生成有关程序中函数的文档。
下面的greet()
是函数调用。函数调用让python执行函数的代码,要调用函数,可依次指定函数名以及用圆括号括起来的必要信息。和预期一样,它打印Hello!
2.向函数传递信息
让我们对刚刚的函数稍作修改
def greet(username):
"""显示问候语。"""
print(f"Hello, {username.title()}!")
greet('xi')
现在,这个函数要求你调用它时给username指定一个值。调用greet时,可以将一个名字传递给它。它接受这个名字,并对这个人发出问候
在函数greet的定义中,变量username是一个形参,即函数完成工作所需的信息。在代码greet('xi')
中,值’xi’是一个实参,即调用函数时传递给函数的信息。
在greet('xi')
中,将实参’xi’传递给了函数greet,这个值被赋给了形参username
3.传递实参
向函数传递实参的方式很多,可以使用位置实参,这要求实参的顺序与形参的顺序相同;
也可使用关键字实参,其中每个实参都由变量名和值组成;
还可以使用列表和字典。
位置实参
def pet(animal, name):
"""显示宠物信息。"""
print(f"My pet is a {animal} which called {name}")
pet('dog', 'dudu')
这个函数的定义表明,它需要一个动物类型和一个名字。调用pet时,需要按顺序提供一个动物类型和一个名字。
你可以多次调用函数,但每次都要注意自己输入实参的顺序必须和形参保持一致
关键字实参
关键字实参是传递给函数的名称值对。因为直接在实参中将名字和值关联起来,所以向函数传递实参时不会混淆。
关键字实参让你无需考虑函数调用中的实参顺序,还清楚地指出了每个值的用途
def pet(animal, name):
"""显示宠物信息。"""
print(f"My pet is a {animal} which called {name}")
pet( name = 'dudu', animal = 'dog')
输出和上一次的一样
默认值
编写函数时,可给每个形参指定默认值。在调用函数时给形参提供实参时,则采用实参值;否则采用默认值。
如下所示:
def pet(name, animal = 'dog'):
"""显示宠物信息。"""
print(f"My pet is a {animal} which called {name}")
pet(name = 'dudu')
这时我们没有传入宠物类型,因此函数使用了默认值,给animal赋值’dog’,如果我们传入宠物类型:
def pet(name, animal = 'dog'):
"""显示宠物信息。"""
print(f"My pet is a {animal} which called {name}")
pet(name = 'dudu', animal = 'cat')
则animal赋值为’cat’,这时python将忽略默认值
注意,使用默认值时,必须先在参数列表中列出没有默认值的实参,然后再列出有默认值的实参
等效函数调用
在调用函数输入参数时,既可以采用位置方式,也可以采用关键字方式,使用对你来说最容易理解的调用方式即可。
无论如何,确保函数调用使用的参数与函数定义使用的参数相匹配
4.返回值
函数可以处理一些数据,并返回一个或一组值。函数返回的值称为返回值。在函数中,可使用return语句将值返回到调用函数的代码行。返回值让你能够将程序中的大部分繁重工作转移到函数中去完成,从而简化主程序
返回简单值
def formatted_name(first, last):
"""返回整洁姓名"""
full_name = f"{first} {last}"
return full_name.title()
musician = formatted_name('liuyun', 'tian')
print(musician)
这样返回了一个完整的姓名,每当我们要显示姓名时都可以调用这个函数。
让实参变为可选的
def formatted_name(first, last, middle = ''):
"""返回整洁姓名"""
if middle:
full_name = f"{first} {middle} {last}"
else:
full_name = f"{first} {last}"
return full_name.title()
musician = formatted_name('liuyun', 'tian', middle = 'li')
print(musician)
我们这样设置后不仅使用于只有名和姓的人,而且使用于有中间名的人。当他没有中间名的时候不传入实参给middle就可以了。
返回字典
def bulid_person(first_, last_):
person = {'first': first_, 'last': last_}
return person
musician = bulid_person('liuyun', 'tian')
print(musician)
这样返回一个字典
def bulid_person(first_, last_, age = None):
person = {'first': first_, 'last': last_}
if age:
person['age'] = age
return person
musician = bulid_person('liuyun', 'tian',age = 27)
print(musician)
修改后可以让这个字典包含关于人年龄的键值对
5.传递列表
假设有一个用户列表,我们要问候列表中的每位用户。下面的示例将包含名字的列表传递给一个名为greet的函数,这个函数问候列表中的每个人:
def greet(names):
for name in names:
msg = f"Hello, {name.title()}"
print(msg)
usernames = ['java', 'python', 'C']
greet(usernames)
每个用户都收到了一条个性化的标志语
在函数中修改列表
def print_models(unprinted, completed):
while unprinted:
current = unprinted.pop()
print(f"Printing model: {current}")
completed.append(current)
def show_models(completed):
print("\nThe following models have been printed:")
for completed_model in completed:
print(completed_model)
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []
print_models(unprinted_designs, completed_models)
show_models(completed_models)
我们创建了一个未打印的设计列表,还创建了一个空的已完成列表。在第一个函数内,我们将待打印的模型逐个打印,并移到已完成列表中;在第二个函数内,我们遍历已完成列表,逐个展示打印好的模型
Printing model: dodecahedron
Printing model: robot pendant
Printing model: phone case
The following models have been printed:
dodecahedron
robot pendant
phone case
禁止函数修改列表
从刚刚的示例我们可以看出,传入的列表会被函数修改,因此,如果我们不想让函数修改列表,我们可以修改为如下的代码:
def print_models(unprinted[:], completed):
切片表示法[:]
创建列表的副本,这样传入函数的实际上是一个未打印列表的副本,所以不会影响它本身
6.传递任意数量的实参
有时候,我们并不知道函数需要接受多少个实参,不过python可以允许函数从调用语句中收集任意数量的实参。
def pizza(*toppings):
print(toppings)
pizza('extra cheese')
pizza('mushrooms', 'green peppers')
形参名*toppings
中的星号让python创建一个名为toppings的空元组,并将收到的所有值都封装到这个元组中。也可以将这里的print改为一个for循环,遍历配料列表,并输出每种配料
结合使用位置实参和任意数量实参
如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。python先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中
def pizza(size, *toppings)
例如这样
注意,你经常会看到通用形参名*args,它也收集任意数量的位置实参
使用任意数量的关键字实参
有时候,需要接受任意数量的实参,但预先不知道传递给函数的会是什么样的信息。在这种情况下,可以将函数编写为能够接受任意数量的键值对——调用语句提供了多少就接受多少
def build_profile(first, last, **info):
info['first'] = first
info['last'] = last
return info
user = build_profile('liuyun', 'tian',
location = 'princeton',
field = 'physics')
print(user)
在这里,调用这个函数时不管提供多少个额外的键值对,它都能够处理
形参**info中的两个星号让python创建一个名为info的字典,并将所有接收到的键值对都放到这个字典中
注意,你经常看到形参名**kargs,它用于收集任意数量的关键字实参
7.将函数存储在模块中
我们可以将函数存储在称为模块的独立文件中,再将模块导入到主程序之中。import语句允许在当前运行的程序文件中使用模块中的代码
导入整个模块
模块是扩展名为.py的文件,包含要导入到程序中的代码。
这里我们有一个名为ist.py的文件
import sdj
musician = sdj.bulid_person('liuyun', 'tian',age = 27)
print(musician)
在同一个文件中,我们还有另一个文件sdj.py
def bulid_person(first_, last_, age = None):
person = {'first': first_, 'last': last_}
if age:
person['age'] = age
return person
那么运行ist.py的话,和我们运行以下这个程序的效果是一样的
def bulid_person(first_, last_, age = None):
person = {'first': first_, 'last': last_}
if age:
person['age'] = age
return person
musician = bulid_person('liuyun', 'tian',age=27)
print(musician)
import sdj让python打开文件sdj.py,并将其所有函数复制到这个程序中。调用sdj中的函数时,注意在前面加上sdj.
导入特定的函数
如果只想导入要使用的函数,代码类似于下面这样
from sdj import bulid_person
musician = bulid_person('liuyun', 'tian',age=27)
print(musician)
使用这种语法时无需加上sdj.
,指定其名称即可
使用as给函数指定别名
如果导入的函数的名称可能和现有函数冲突,或者函数名称太长,可以使用简短而独一无二的别名,如下所示
from sdj import bulid_person as bp
musician = bp('liuyun', 'tian',age=27)
print(musician)
这里使用as指定一个新的名称
导入模块中的所有函数
from sdj import *
musician = bp('liuyun', 'tian',age=27)
print(musician)
使用*运算符可以让python导入模块中的所有函数
然而这样的导入方法导入大型模块时,很容易由于函数与本项目中的同名而出错。所以最好不使用这种方法
8.函数编写指南
- 给形参指定默认值时,等号两边不要有空格
- 对于关键字实参,也应该遵守这种规定
- PEP 8建议代码行的长度不要超过79字符,如果形参太多导致超过了79字符,可在函数定义中输入左括号后按回车键,并在下一行按两次Tab键,从而将形参列表和只缩进一层的函数体区分开来
- 多个函数用空行分隔开
- import语句应该放在文件开头
八、类
1.创建和使用类
使用类可以模拟几乎任何东西。下面来编写一个表示小狗的简单类
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def sit(self):
print(f"{self.name} is now sitting.")
def roll_over(self):
print(f"{self.name} rolled over!")
根据Dog类创建的每个示例都将存储名字和年龄,我们赋予了每条小狗蹲下和打滚的的能力
根据约定,在python中首字母大写的名称指的是类。这个类定义中没有圆括号,因为要从空白创建这个类。
方法__init__()
- 类中的函数称为方法。前面学到有关函数的一切都适用于方法。就目前而言,唯一重要的差别是调用方法的方式
__init__()
是一个特殊的方法,每当你根据Dog类创建新实例时,python都会自动运行它。在这个方法中,开头和末尾各有两个下划线,这是一种约定,旨在避免python默认方法与普通方法发生名称冲突。- 我们将方法
__init__()
定义成包含三个形参。在这个方法的定义中,形参self必不可少,因为python调用这个方法来创建Dog实例时,将自动传入实参self。每个与实例相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。 - 创建Dog实例时,python将调用Dog类的方法
__init__()
。我们将通过实参向Dog()传递名称和年龄,而self自动传递,因此不需要传递它。每次使用Dog类创建实例,只需要给最后两个形参提供值。 - 以self为前缀的变量可供类中的所有方法使用,可以通过类的任何实例来访问。
self.name = name
获取与形参相关联的值,并将其赋给变量name,然后该变量被关联到当前创建的实例。self.age = age
的作用与之类似。像这样可以通过实例访问的变量称为属性
2.根据类创建实例
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def sit(self):
print(f"{self.name} is now sitting.")
def roll_over(self):
print(f"{self.name} rolled over!")
my_dog = Dog('tutu', 6)
print(f"My dog's name is {my_dog.name}")
print(f"My dog's age is {my_dog.age}")
这里使用前一个实例中编写的Dog类。python使用实参’tutu’和6调用Dog类的方法__init__()
,其创建一个表示特定小狗的实例,并且使用提供的值来设置属性name和age。
接下来,python返回一个表示这个小狗的实例,而我们将这个实例赋给了变量my_dog。通常认为首字母大写的名称表示类,小写的名称表示根据类创建的实例
访问属性
my_dog.name
这种语法演示了python如何获悉属性的值。它先找到实例my_dog,再查找与该实例相关联的属性name。在Dog类中引用这个属性时,使用的是self.name
调用方法
根据Dog类创建实例后,就能通过.
来调用Dog类中定义的任何方法,如下所示:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def sit(self):
print(f"{self.name} is now sitting.")
def roll_over(self):
print(f"{self.name} rolled over!")
my_dog = Dog('tutu', 6)
my_dog.sit()
my_dog.roll_over()
python在Dog类中查找方法并运行其代码,输出如下所示:
tutu is now sitting.
tutu rolled over!
创建多个实例
可按照需求创建任意数量的实例
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def sit(self):
print(f"{self.name} is now sitting.")
def roll_over(self):
print(f"{self.name} rolled over!")
my_dog = Dog('tutu', 6)
your_dog = Dog('huanhuan', 3)
又创建了一个实例,用法和刚刚完全相同
3.使用类和实例
可使用类来模拟现实世界中的很多情景。下面是一个表示汽车的类
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def describe(self):
name = f"{self.year} {self.make} {self.model}"
return name.title()
my_car = Car('audi', 'a4', 2019)
print(my_car.describe())
为了让这个类更加有趣,我们给它增加一个随时间变化的属性,用于存储汽车的总里程
给属性指定默认值
创建实例时,有些属性无须通过形参来定义,可在方法__init__()
中为其指定默认值
下面添加一个名为odometer_reading的属性,其初始值总为0。我们还添加了一个名为read_odometer的方法,用于读取汽车的里程表:
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def describe(self):
name = f"{self.year} {self.make} {self.model}"
return name.title()
def read_odometer(self):
print(f"This car has {self.odometer_reading} miles on it")
my_car = Car('audi', 'a4', 2019)
print(my_car.describe())
my_car.read_odometer()
一开始汽车的里程为0,但出售时里程表读数为0的汽车不多,因此需要一种方式修改该属性的值
4.修改属性的值
直接修改属性的值
最简单的方式是直接通过实例访问它,如下所示:
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def describe(self):
name = f"{self.year} {self.make} {self.model}"
return name.title()
def read_odometer(self):
print(f"This car has {self.odometer_reading} miles on it")
my_car = Car('audi', 'a4', 2019)
print(my_car.describe())
my_car.odometer_reading = 23
my_car.read_odometer()
这样我们直接找到属性odometer_reading
,并将其的值修改为23
有时候要这样直接访问属性,但更多的时候我们需要编写对属性更新的方法
通过方法修改属性的值
如果能有方法替你更新属性,就无需直接访问属性,而可以将值传递给方法,由它在内部更新。
下面的示例演示了一个名为update_odometer
的方法
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def describe(self):
name = f"{self.year} {self.make} {self.model}"
return name.title()
def read_odometer(self):
print(f"This car has {self.odometer_reading} miles on it")
def update_odometer(self, mileage):
self.odometer_reading = mileage
my_car = Car('audi', 'a4', 2019)
print(my_car.describe())
my_car.update_odometer(24)
my_car.read_odometer()
这样我们通过调用方法update_odometer
更新了它的里程数。下面增加一些逻辑,禁止将读数回调
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def describe(self):
name = f"{self.year} {self.make} {self.model}"
return name.title()
def read_odometer(self):
print(f"This car has {self.odometer_reading} miles on it")
def update_odometer(self, mileage):
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else :
print("You can't roll back an odometer!")
my_car = Car('audi', 'a4', 2019)
my_car.update_odometer(24)
my_car.read_odometer()
my_car.update_odometer(50)
my_car.read_odometer()
my_car.update_odometer(33)
my_car.read_odometer()
现在调用这个方法时,无法将里程数回调了
通过方法对属性的值进行递增
如果我们希望增加属性的值而不是赋值给他,稍微修改一下代码就可以了
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def describe(self):
name = f"{self.year} {self.make} {self.model}"
return name.title()
def read_odometer(self):
print(f"This car has {self.odometer_reading} miles on it")
def increment_odometer(self, mileage):
if mileage > 0 :
self.odometer_reading += mileage
else :
print("You can't roll back an odometer!")
my_car = Car('audi', 'a4', 2019)
my_car.increment_odometer(24)
my_car.read_odometer()
my_car.increment_odometer(50)
my_car.read_odometer()
my_car.increment_odometer(-3)
my_car.read_odometer()
这个方法将里程表读数增加指定的量,量为负数时则输出"You can't roll back an odometer!"
5.继承
编写类时,并非总要从空白开始。如果要编写的类是另一个现成类的特殊版本,可使用继承。一个类继承另一个类时,将自动获得另一个类的所有属性和方法。原有的类称为父类,而新类称为子类。子类继承父类的所有属性和方法,同时还可以定义自己的属性和方法。
子类的方法__init__()
在既有类的基础上编写新类的时候,通常要调用父类的方法__init__()
。这将初始化在父类__init__()
中定义所有的属性,从而让子类包含这些属性。
例如下面模拟电动汽车。可以在前面创建的Car类上的基础上创建新类ElectricCar。这样就只需要为电动汽车特有的属性和行为编写代码
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def describe(self):
name = f"{self.year} {self.make} {self.model}"
return name.title()
def read_odometer(self):
print(f"This car has {self.odometer_reading} miles on it")
def increment_odometer(self, mileage):
if mileage > 0 :
self.odometer_reading += mileage
else :
print("You can't roll back an odometer!")
class ElectricCar(Car):
def __init__(self, make, model, year):
super().__init__(make, model, year)
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.describe())
ElectricCar
类具备Car类的所有功能。
创建子类时,父类必须包含于当前文件中,且位于子类前。定义子类时,必须在圆括号内指定父类的名称。方法__init__()
接受创建Car实例所需的信息。
super()是一个特殊函数,让你能够调用父类的方法。这行代码让python调用Car类的方法__init__()
,让ElectricCar
包含这个方法中定义的所有属性。父类也称为超类,名称super由此而来。
这里创建了一辆电动汽车。目前它还没有特别之处呢。
给子类定义属性和方法
让一个类继承另一个类后,就可以添加区分子类和父类所需的新属性和新方法了。
下面添加一个电动汽车特有的属性,以及一个描述该属性的方法。我们将存储电瓶容量,并且编写一个打印电瓶描述的方法。
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def describe(self):
name = f"{self.year} {self.make} {self.model}"
return name.title()
def read_odometer(self):
print(f"This car has {self.odometer_reading} miles on it")
def increment_odometer(self, mileage):
if mileage > 0 :
self.odometer_reading += mileage
else :
print("You can't roll back an odometer!")
class ElectricCar(Car):
def __init__(self, make, model, year):
super().__init__(make, model, year)
self.battery_size = 75
def describe_battery(self):
print(f"This car has a {self.battery_size}-kWh battery.")
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.describe())
my_tesla.describe_battery()
现在,我们给它加了一个电瓶!电瓶容量初始值75,还有一个方法用来显示电瓶容量
用ElectricCar
类创建的所有实例都将包含该属性和该方法,但所有Car类实例都不包含它。
重写父类的方法
对于父类的方法,只要它不符合子类模拟的实物的行为,都可以进行重写。为此,可以在子类中定义一个与要重写的父类方法同名的方法。这样,python将不会考虑这个父类方法,而只关注你在子类中定义的相应方法。
假设Car有一个名为fill_gas_tank()的方法,它对于电动汽车来说是毫无意义的。因此你可能想重写他。下面演示了一种重写形式:
class ElectricCar(Car):
--snip--
def fill_gas_tank(self):
print("This car doesn't need a gas tank!")
其实本质上就是现在的方法覆盖了父类的方法,所以叫重写嘛
现在,如果有人对电动汽车调用方法fill_gas_tank
,python将忽略父类中的而使用电动汽车中的
6.将实例用作属性
使用代码模拟实物时,有可能发现自己给类添加的细节越来越多:属性和方法清单以及文件越来越长。怎么办呢?在这种情况下,我们可以将类的一部分提取出来,作为一个独立的类。可以将大型类拆分成多个协同工作的小类。
例如,当我们不断给电动汽车类添加细节时,我们可能发现其中包含很多专门针对汽车电瓶的属性和方法。在这种情况下,可以将这些属性和方法提取出来,放到一个Battery的类中,并将一个Battery实例作为ElectricCar
类的属性:
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def describe(self):
name = f"{self.year} {self.make} {self.model}"
return name.title()
def read_odometer(self):
print(f"This car has {self.odometer_reading} miles on it")
def increment_odometer(self, mileage):
if mileage > 0 :
self.odometer_reading += mileage
else :
print("You can't roll back an odometer!")
class Battery:
def __init__(self, battery_size=75):
self.battery_size = battery_size
def describe_battery(self):
print(f"This car has a {self.battery_size}-kWh battery.")
class ElectricCar(Car):
def __init__(self, make, model, year):
super().__init__(make, model, year)
self.battery_size = 75
self.battery = Battery()
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.describe())
my_tesla.battery.describe_battery()
在ElectricCar
类中,添加了一个名为self.battery
的属性。这行代码让python创建一个新的Battery实例(没有指定容量默认赋值75),并将该实例赋予属性self.battery
。每次方法__init__
被调用时,都将执行该操作,因此现在每个ElectricCar
实例都包含一个自动创建的实例。
my_tesla.battery.describe_battery()
这行代码让python在实例my_tesla
中查找属性battery,并对存储在该属性中的Battery实例调用方法describe_battery()
,输出与之前的相同
这看似做了很多无用功,但实际上我们加深了层次感,下面我们给Battery类添加一个方法,它根据电瓶容量报告汽车的续航里程。
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def describe(self):
name = f"{self.year} {self.make} {self.model}"
return name.title()
def read_odometer(self):
print(f"This car has {self.odometer_reading} miles on it")
def increment_odometer(self, mileage):
if mileage > 0 :
self.odometer_reading += mileage
else :
print("You can't roll back an odometer!")
class Battery:
def __init__(self, battery_size=75):
self.battery_size = battery_size
def describe_battery(self):
print(f"This car has a {self.battery_size}-kWh battery.")
def get_range(self):
if self.battery_size == 75:
range = 260
elif self.battery_size == 100:
range = 315
print(f"This car can go about {range} miles on a full charge")
class ElectricCar(Car):
def __init__(self, make, model, year):
super().__init__(make, model, year)
self.battery_size = 75
self.battery = Battery()
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.describe())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
7.导入类
随着不断给类添加功能,文件可能变得很长,即使妥善使用了继承也会如此。为了让文件尽可能简洁,python允许将类存储在模块中,然后在主程序中导入需要的模块。
导入单个类
我们把Car类放入一个单独的模块car中。
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def describe(self):
name = f"{self.year} {self.make} {self.model}"
return name.title()
def read_odometer(self):
print(f"This car has {self.odometer_reading} miles on it")
def increment_odometer(self, mileage):
if mileage > 0 :
self.odometer_reading += mileage
else :
print("You can't roll back an odometer!")
下面创建另一个文件,在其中导入Car类并创建其实例:
from car import Car
my_car = Car('audi', 'a4', 2019)
my_car.update_odometer(24)
my_car.read_odometer()
my_car.update_odometer(50)
my_car.read_odometer()
my_car.update_odometer(33)
my_car.read_odometer()
import语句让python打开模块car并且导入其中的Car类。这样我们就可以使用Car类,类似于前面引入函数
在一个模块中存储多个类
可以将多个作用类似的类存储于一个模块中
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def describe(self):
name = f"{self.year} {self.make} {self.model}"
return name.title()
def read_odometer(self):
print(f"This car has {self.odometer_reading} miles on it")
def increment_odometer(self, mileage):
if mileage > 0 :
self.odometer_reading += mileage
else :
print("You can't roll back an odometer!")
class Battery:
def __init__(self, battery_size=75):
self.battery_size = battery_size
def describe_battery(self):
print(f"This car has a {self.battery_size}-kWh battery.")
def get_range(self):
if self.battery_size == 75:
range = 260
elif self.battery_size == 100:
range = 315
print(f"This car can go about {range} miles on a full charge")
class ElectricCar(Car):
def __init__(self, make, model, year):
super().__init__(make, model, year)
self.battery_size = 75
self.battery = Battery()
这些都是和汽车相关的类,因此我们把它们都放到car模块中。
从一个模块中导入多个类
from car import Car,ElectricCar
my_car = Car('audi', 'a4', 2019)
my_car.update_odometer(24)
my_car.read_odometer()
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.describe())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
导入整个模块
import car
导入模块中的所有类
from car import *
不推荐这样导入,原因与之前导入函数时所说的一样
使用别名
和前面函数一章时所说的相似,导入类时,也可为其指定别名。
from car import Car as C
8.python标准库
python标准库是一组模块,我们安装的python都包括它。现在我们可以使用标准库中的任何函数和类,只需要在程序开头包含一条import语句。让我们先来了解一下模块random
from random import randint
num = randint(1,6)
print(num)
在这个模块中,randit()函数将两个整数作为参数,并随即返回一个位于这两个整数之间(含)的整数。也就是说,你每次运行这个模块的结果都在变化。
from random import choice
players = ['a','b','c','d']
first_up = choice(players)
print(first_up)
另一个有用的函数是choice(),它将一个列表或者元组作为参数,并随机返回其中的一个元素
9.类编码风格
- 类名应采用驼峰命名法,实例名和模块名在单词之间加下划线
- 对于每个类,最好在类定义的后面包含一个文档字符串,简要描述其功能。每个模块也应该有一个文档字符串,对其中的类可用于做什么进行描述。
- 可使用空行组织代码,但不要滥用。类中使用空行分隔方法,模块中使用两个空行来分隔类
- 同时导入标准库模块和自定义模块时,先编写导入标准库模块的import语句,再添加一个空行,然后编写导入你自己编写的模块的import语句。
九、文件和异常
1.从文件中读取数据
读取整个文件
我们现在有一个文件包含圆周率值,它的名字是pi_digits.txt
3.14159
26535
89793
如何在一个程序中调用该文件?
with open ('pi_digits.txt') as file_object:
contents = file_object.read()
print(contents)
这个程序在其源文件所在的目录中查找pi_digits.txt。函数open()返回一个表示文件的对象,在本例中,open ('pi_digits.txt')
返回一个表示文件pi_digits.txt的对象,python将该对象赋给file_object供以后使用。
关键字with在不再需要访问文件时将其关闭。在这个程序中,我们调用了open(),但没有调用close()。如果在程序中过早调用close(),你会发现需要使用文件时它已关闭。
有了表示文件pi_digits.txt的对象后,使用方法read()读取这个文件的全部内容,并将其作为一个字符串赋给变量contents,这样,通过打印contents的值,我们就可以将这个文本文件的全部内容显示出来
3.14159
26535
89793
相比于原始文件,该输出唯一不同的地方是末尾多了一个空行。为何会多出这个空行呢?因为read()到达文件末尾时返回一个空字符串,而将这个空字符串显示出来时就是一个空行。要删除多出来的空行,可在函数调用print()中使用rstrip():
with open ('pi_digits.txt') as file_object:
contents = file_object.read()
print(contents.rstrip())
前面说过,rstrip()方法删除字符串末尾的空白
文件路径
像刚刚那样的代码,python只会在当前执行文件的所在目录中查找,如何在不同目录下查找?
例如,你可能将源文件存在了work文件夹中,而该文件夹中有一个名text_files的文件夹用于存储程序文件操作的文本文件。那我们可以使用如下的代码:(相对路径)
with open('text_files//filename.txt') as file_object:
显示文件路径时,Windows系统使用反斜杠而不是斜杠,但在代码中依然可以使用斜杠
除了相对路径,我们还可以使用绝对路径,即直接告诉python文件在计算机中的准确位置。绝对路径通常比较长,因此不妨将其赋给一个变量,再将该变量传给open()
path = 'D:\\python_files\\text_files.txt'
with open(path) as file_object:
在使用反斜杠时,不能直接将路径复制过来了事。因为反斜杠本身是用来对字符进行转义的。所以在复制路径后,对每个反斜杠进行转义,即在其前面再加一个反斜杠
逐行读取
读取文件时,常常需要检查其中的每一行。要以每次一行的方式检查文件,可对文件对象使用for循环:
path = 'D:\\python_files\\text_files.txt'
with open(path) as file_object:
for line in file_object:
print(line)
这样打印出来的结果会有很多的空白行
3.14159
26535
89793
为何会有如此多的空白行?因为在这个文件中,每行的末尾都有一个看不见的换行符,而函数调用print()也会加上一个换行符,因此每行末尾都有两个换行符。要想消除这些多余的空白行,我们还是使用rstrip():
path = 'D:\\python_files\\text_files.txt'
with open(path) as file_object:
for line in file_object:
print(line.rstrip())
现在的输出又与文件内容相同了
3.14159
26535
89793
我们还可以将各行内容存在一个列表中,再在with代码块以外打印
path = 'D:\\python_files\\text_files.txt'
with open(path) as file_object:
lines = file_object.readlines()
for line in lines:
print(line.rstrip())
方法readlines()从文件中读取每一行,并将其存储在一个列表内。接下来该列表被赋给变量lines。后面使用一个简单的for循环打印lines中的各行,输出还是和文件内容完全一致。
使用文件中的内容
将文件读入到内存后,就能以任何方式使用这些数据了。下面使用刚刚那个圆周率值。
path = 'D:\\python_files\\text_files.txt'
with open(path) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.rstrip()
print(pi_string)
print(len(pi_string))
输出如下所示
3.14159 26535 89793
21
变量pi_string指向的字符串包含原来位于每行左边的空格,为删除这些空格,可使用strip()而非rstrip():
path = 'D:\\python_files\\text_files.txt'
with open(path) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
print(pi_string)
print(len(pi_string))
输出如下所示
3.141592653589793
17
这样就获得了一个字符串,其中包含准确到15小数的圆周率值,这个字符串长17字符,因为它还包含整数部分的3和小数点
读取文本文件时,python将其中所有文本都解读为字符串。如果读取的数,并要将其作为数值使用,就必须使用函数int()将其转换为整数或使用函数float()将其转换为浮点数
包含一百万位的大型文件
如果我们有一个文本文件,其中包含精确到小数点后一百万位的圆周率值,也可创建一个包含所有这些数字的字符串。在这里,我们只打印到小数点后50位,以免终端显示所有位数。
path = 'D:\\python_files\\pi_million.txt'
with open(path) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
print(f"{pi_string[:52]}...")
print(len(pi_string))
但是长度还是100,0002
圆周率值中包含你的生日吗
我们可以用一个程序检测圆周率中是否包含你的生日
path = 'D:\\python_files\\pi_million.txt'
with open(path) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
birthday = input("Enter your birthday: ")
if birthday in pi_string:
print("Your birthday appears in the first million digits of pi!")
else:
print("Your birthday does not appear in the first million digits of pi!")
2.写入文件
写入空文件
要将文本写入文件,我们在调用open()时需要提供另一个实参,告诉python你要写入打开的文件。
我们从下面的例子来体会一下
filename = 'programing.txt'
with open(filename, 'w') as file_object:
file_object.write("I love summer")
在本例中,调用open()提供了两个实参。第一个实参也是要打开的文件的名称,第二个实参告诉python,要以写入模式打开这个文件。打开文件时,可指定读取模式(‘r’),写入模式(‘w’),附加模式(‘a’),读写模式(‘r+’)。如果省略了模式实参,python将以默认的只读模式打开文件
如果要写入的文件不存在,函数open()将自动创建它。然而,以写入模式(‘w’)打开文件时要千万小心,因为如果指定的文件已经存在,python会先清空该文件的内容。
该代码中使用文件对象的方法write(),将一个字符串写入文件。这个程序没有终端输出,但如果打开文件programing.txt,将看到其中包含如下一行内容
I love summer
注意,python只能将字符串写入文本文件,要将数值数据存储到文本文件中,你必须先使用函数str()将其转换为字符串格式
写入多行
函数write()不会在写入的文本末尾添加换行符,因此如果你想要输入多行,你需要在每次的print()语句中加上换行符,如下所示:
filename = 'programing.txt'
with open(filename, 'w') as file_object:
file_object.write("I love summer.\n")
file_object.write("I love you.\n")
现在输出出现在不同的行中。
像显示到终端的输出一样,还可以使用空格、制表符和空行来设置这些输出的格式
附加到文件
如果要给文件添加内容而不是覆盖原有的内容,可以使用附加模式(‘a’)。以附加模式打开文件时,python不会在返回文件对象前清空文件的内容,而是将写入文件的行添加到文件末尾。如果指定的文件不存在,python将为你创建一个空文件。
filename = 'programing.txt'
with open(filename, 'a') as file_object:
file_object.write("Do you love me?\n")
file_object.write("Hhhhh\n")
在运行了上一块代码后运行该块代码,则现在文件中的内容如下所示
I love summer.
I love you.
Do you love me?
Hhhhh
3.异常
python使用称为异常的特殊对象来管理程序执行期间发生的错误。每当发生让python不知所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常的代码, 程序将继续运行;否则程序将停止并且显示traceback,其中包含有关异常的报告
异常是使用try-except代码块来处理的。try-except代码块让python执行指定的操作,同时告诉python告诉发生异常时该怎么办。使用try-except代码块时,即便出现异常,程序也将继续运行
处理ZeroDivisionError异常
运行以下代码
print(5/0)
python给出traceback
Traceback (most recent call last):
File "D:/jetbrains/python/sss.py", line 1, in <module>
print(5/0)
ZeroDivisionError: division by zero
在上述traceback中,ZeroDivisionError是个异常对象。python无法按你的要求做时,就会创建这种对象。下面让我们告诉python发生这种错误时怎么办。
使用try-except代码块
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")
将导致错误的代码行放在一个try代码块中。如果try代码块中的代码运行起来没有问题,python将跳过except代码块;如果try代码块中的代码导致了错误,python将查找与之匹配的except代码块并且运行其中的代码。
在本例中,try代码块中的代码引发了ZeroDivisionError异常,因此python查找指出了该怎么办的except代码块,并运行其中的代码。这样,将会有以下输出显示
You can't divide by zero!
如果后面还有代码,程序将继续运行
使用异常避免崩溃
发生错误时,如果程序还有工作尚未完成,妥善处理错误就尤其重要。如果程序能够妥善地处理无效输入,就能再提示用户提供有效输入,而不至于崩溃。
下面来创建一个只执行除法运算的简单计算器:
first = input("First number:")
second = input("Second number:")
answer = int(first)/int(second)
print(answer)
这个计算器会在第二个数为0时崩溃,那我们应该怎么改进呢?
else代码块
first = input("First number:")
second = input("Second number:")
try:
answer = int(first) / int(second)
except ZeroDivisionError:
print("You can't divide by zero!")
else:
print(answer)
这个示例还包含一个else代码块,依赖try代码块成功执行的代码都应放到else代码块中
try-except-else代码块的工作原理大致如下。python尝试执行try代码块中的代码,只有可能引发异常的代码才放到try语句中。仅在try代码块成功执行时才需要运行的代码应放在else代码块中。except代码块告诉python如果try代码块中的代码出错应该如何做。
处理FileNotFoundError异常
filename = 'alice.txt'
with open(filename, encoding='utf-8') as f:
contents = f.read()
相比于本章前面的文件打开方式,这里给参数encoding指定了值,在系统的默认编码与要读取文件使用的编码不一致时,必须这样做。
然而实际上我们并没有这样一个文件,因此它将引发一个异常
Traceback (most recent call last):
File "D:/jetbrains/python/sss.py", line 3, in <module>
with open(filename, encoding='utf-8') as f:
FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'
在本例中,这个错误是函数open()导致的,因此要处理这个错误,必须将try语句放在open前
filename = 'alice.txt'
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print("sorry, the file does not exist.")
这样将会有如下输出
sorry, the file does not exist.
分析文本
你可以分析包含整本书的文本文件,下面来提取《爱丽丝漫游奇境记》的文本,并尝试计算它共有多少个单词。我们将使用方法split(),他能根据一个字符串创建一个单词列表
filename = 'alice.txt'
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print("sorry, the file does not exist.")
else:
# 计算该文件大致包含多少个单词
words = contents.split()
num_words = len(words)
print(f"The file {filename} has about {num_words} words.")
我们将文件alice.txt移到正确的目录下让try代码块得以执行。对变量contents调用方法split(),以生成一个列表,其中包含这部童话中的所有单词。使用len()来确定这个列表的长度时,就能知道原始字符串大概包含多少个单词了
这个数会稍微大一点,因为使用的文本文件包含出版商提供的额外信息
使用多个文件
下面多分析几本书,在此之前,先将这个程序的大部分代码移到一个函数中
def count_words(filename):
"""计算一个文件大致包含多少个单词"""
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print("sorry, the file does not exist.")
else:
# 计算该文件大致包含多少个单词
words = contents.split()
num_words = len(words)
print(f"The file {filename} has about {num_words} words.")
filename = 'alice.txt'
count_words(filename)
现在我们可以将要分析的文件名称存储在一个列表中,然后对列表中的每个文件调用该函数
def count_words(filename):
"""计算一个文件大致包含多少个单词"""
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print("sorry, the file does not exist.")
else:
# 计算该文件大致包含多少个单词
words = contents.split()
num_words = len(words)
print(f"The file {filename} has about {num_words} words.")
filenames = ['alice.txt', 'siddhartha.txt', 'little_women.txt']
for filename in filenames:
count_words(filename)
静默失败
如果我们希望捕获到异常时程序保持静默,我们可以使用pass语句
def count_words(filename):
"""计算一个文件大致包含多少个单词"""
try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
pass
else:
# 计算该文件大致包含多少个单词
words = contents.split()
num_words = len(words)
print(f"The file {filename} has about {num_words} words.")
filenames = ['alice.txt', 'siddhartha.txt', 'little_women.txt']
for filename in filenames:
count_words(filename)
pass语句还充当了占位符,提醒你在程序的某个地方什么都没做,并且以后也许要在这里做些什么。
4.存储数据
很多程序都要求用户输入某种信息,用户关闭程序时,几乎总是要保存这些数据。一种简单的方式是使用模块json来接收数据。模块json让你能够将简单的python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据。你还可以使用json在python程序之间分享数据
更重要的是,JSON数据格式并非python专用。这让你能够将以JSON格式存储的数据与使用其他编程语言的人分享
JSON格式最初是为JavaScript开发的,但随后成为了一种常见格式,被包括python在内的众多语言采用。
使用json.dump()和json.load()
我们编写一个存储一组数的简短程序,再编写一个将这些数读取到内存中的程序。
函数json.dump()接受两个实参:要存储的数据,以及可用于存储数据的文件对象。下面演示如何使用json.dump()来存储列表:
import json
numbers = [2, 3, 5, 7, 11]
filename = 'number.json'
with open(filename, 'w') as f:
json.dump(numbers, f)
先导入模块json,再创建一个数字列表。接着指定要将该数字列表存储到哪个文件中,通常使用扩展名.json来指出文件存储的数据为JSON格式。以写入模式打开该文件,让json能将数据写入其中。最后使用函数json.dump()将数字列表存储到文件中。
下面再编写一个程序,使用json.load()将列表读取到内存中:
import json
filename = 'number.json'
with open(filename) as f:
numbers = json.load(f)
print(numbers)
打印结果与刚刚存入的数字列表相同
保存和读取用户生成的数据
来看一个例子:提示用户在首次运行程序时输入自己的名字,并在再次运行程序时记住他。
先存储用户名字
import json
username = input("What is your name:")
filename = 'username.json'
with open(filename, 'w') as f:
json.dump(username, f)
print(f"We'll remember you when you come back, {username}!")
现在再编写一个程序,向已经存储了名字的用户发出问候:
import json
filename = 'username.json'
with open(filename) as f:
username = json.load(f)
print(f"Welcome back, {username}!")
需要将这两个程序读入到一个程序中,这个程序运行时,先尝试从文件中获取用户名,如果用户名不存在,就提示用户输入用户名;否则打印问候消息
import json
# 如果以前存储用户名,就加载他
# 否则提示用户输入用户名并存储
filename = 'username.json'
try:
with open(filename) as f:
username = json.load(f)
except FileNotFoundError:
username = input("What is your name:")
with open(filename, 'w') as f:
json.dump(username, f)
print(f"We'll remember you when you come back, {username}!")
else:
print(f"Welcome back, {username}!")
重构
代码能够运行,但通过将其划分为一系列完成具体工作的函数,还可以改进。这样的过程称为重构。重构让代码更清晰、更易于理解、更容易扩展
要重构我们刚刚那个文件,可将其大部分逻辑放到一个或多个函数中,如下所示
import json
def greet_user():
"""问候用户,并指出其名字"""
filename = 'username.json'
try:
with open(filename) as f:
username = json.load(f)
except FileNotFoundError:
username = input("What is your name:")
with open(filename, 'w') as f:
json.dump(username, f)
print(f"We'll remember you when you come back, {username}!")
else:
print(f"Welcome back, {username}!")
greet_user()
但函数greet_user()所做的不仅仅是问候用户,还在存储了用户名的时候获取它、在没有用户名时提示用户输入。
下面我们再次对该函数重构,减少其任务
import json
def get_stored_username():
"""如果存储了用户名,就获取它"""
filename = 'username.json'
try:
with open(filename) as f:
username = json.load(f)
except FileNotFoundError:
return None
else:
return username
def get_new_username():
"""提示用户输入用户名并存储它"""
filename = 'username.json'
username = input("What is your name:")
with open(filename, 'w') as f:
json.dump(username, f)
return username
def greet_user():
"""问候用户,并指出其名字"""
username = get_stored_username()
if username:
print(f"Welcome back, {username}!")
else:
username = get_new_username()
print(f"We'll remember you when you come back, {username}!")
greet_user()
要编写出清晰而易于维护和扩展的代码,这种划分必不可少。
十、测试代码
1.测试函数
下面是一个简单的函数
def get_formatted_name(first, last):
"""生成整洁的姓名"""
full_name = f"{first} {last}"
return full_name.title()
如果我们编写一个程序来进行测试很烦琐。python提供了一种自动测试函数输出的高效方式。
单元测试和测试用例
python标准库中的模块unittest提供了代码测试工具。单元测试用于核实函数某个方面没有问题。测试用例是一组单元测试,它们一道核实函数在各种情形下的行为都符合要求。全覆盖的测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。
对于大型项目要进行全覆盖测试可能很难,通常最初只要针对代码的重要行为编写测试即可,等项目被广泛使用时再考虑全覆盖
可通过的测试
下面的测试用例只包含一个方法,它检查函数在给定名和姓时能否正常工作
import unittest
from sss import get_formatted_name
class NamesTestCase(unittest.TestCase):
"""测试sss.py"""
def test_first_last_name(self):
"""能处理像Liuyun Tian这样的姓名吗"""
formatted_name = get_formatted_name('liuyun', 'tian')
self.assertEqual(formatted_name, 'Liuyun Tian')
if __name__ == '__main__':
unittest.main()
首先导入了模块unittest和要测试的函数。接着我们创建了一个名为NamesTestCase的类,用于包含一系列对get_formatted_name()的单元测试。这个类必须继承unittest.TestCase类,这样python才知道如何运行你编写的测试。
NamesTestCase只包含一个方法,用于测试get_formatted_name()的一个方面。将该方法命名为test_first_last_name(),因为要核实的是只有名和姓的姓名能否被正确格式化。运行该文件时,所有以test_打头的方法都将自动运行。在这个方法中,调用了要测试的函数。在本例中,使用实参’liuyun’和’tian’调用该函数,并将结果赋给变量formatted_name
我们使用了unittest类最有用的方法之一:断言方法。断言方法核实得到的结果是否与期望的结果一致。
self.assertEqual(formatted_name, 'Liuyun Tian')
这行代码的意思是将formatted_name的值与字符串’Liuyun Tian’比较,看它们是否相等。
我们将直接运行这个文件。但需要指出的是,很多测试框架都会先导入测试文件再运行。导入文件时,解释器将同时执行它。if代码块检查特殊变量__name__
,这个变量是在程序执行时设置的。如果该文件被作为主程序执行,变量__name__
将被设置为'__main__'
。在这里我们调用unittest.main()来运行测试示例,如果该文件被测试框架导入,变量__name__
的值将不是'__main__'
,因此不会调用unittest.main()
得到输出如下所示
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
句点表明有一个测试通过了,接下来一行指出python运行了一个测试,消耗时间不到0.001秒。OK表示该测试样例中所有单元测试都已通过
未通过的测试
我们修改一下函数get_formatted_name
def get_formatted_name(first, middle, last):
"""生成整洁的姓名"""
full_name = f"{first} {middle} {last}"
return full_name.title()
再执行刚刚的测试样例,发现出错
E
======================================================================
ERROR: test_first_last_name (__main__.NamesTestCase)
能处理像Liuyun Tian这样的姓名吗
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:/jetbrains/python/python.py", line 9, in test_first_last_name
formatted_name = get_formatted_name('liuyun', 'tian')
TypeError: get_formatted_name() missing 1 required positional argument: 'last'
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (errors=1)
里面包含的信息很多:
-
E指出测试用例中有一个单元测试导致了错误
-
ERROR: test_first_last_name (__main__.NamesTestCase) 能处理像Liuyun Tian这样的姓名吗
告诉我们是哪里导致了错误
-
traceback指出函数调用
get_formatted_name('liuyun', 'tian')
有问题 -
运行了一个单元测试在0.001s内
-
运行测试用例共发生了1个错误
测试未通过时怎么做
测试未通过时不要修改测试,而是应该修改函数
def get_formatted_name(first, last, middle=''):
"""生成整洁的姓名"""
if middle:
full_name = f"{first} {middle} {last}"
else:
full_name = f"{first} {last}"
return full_name.title()
现在测试用例又能通过了
添加新测试
我们再添加一个测试,测试包含中间名的姓名
import unittest
from sss import get_formatted_name
class NamesTestCase(unittest.TestCase):
"""测试sss.py"""
def test_first_last_name(self):
"""能处理像Liuyun Tian这样的姓名吗"""
formatted_name = get_formatted_name('liuyun', 'tian')
self.assertEqual(formatted_name, 'Liuyun Tian')
def test_first_last_middle_name(self):
"""能处理像Liuyun Bin Tian这样的姓名吗"""
formatted_name = get_formatted_name(
'liuyun', 'tian', 'bin')
self.assertEqual(formatted_name, 'Liuyun Bin Tian')
if __name__ == '__main__':
unittest.main()
结果如下所示
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
方法名必须以test_打头,这样它才会在我们运行这个文件时自动运行
2.测试类
之前我们编写了针对单个函数的测试,下面来编写针对类的测试
各种断言方法
以下描述了六个常见的断言方法
方法 | 用途 |
---|---|
assertEqual(a, b) | 核实a == b |
assertNotEqual(a, b) | 核实a != b |
assertTrue(x) | 核实x为True |
assertFalse(x) | 核实x为False |
assertIn(item, list) | 核实item在list中 |
assertNotIn(item, list) | 核实item不在list中 |
一个要测试的类
来看一个帮助管理匿名调查的类
class AnonymousSurvey:
"""收集问卷答案"""
def __init__(self, question):
"""存储一个问题,并为存储答案做准备"""
self.question = question
self.responses = []
def show_question(self):
"""显示问卷"""
print(self.question)
def store_response(self, new_response):
"""存储单份问卷"""
self.responses.append(new_response)
def show_results(self):
"""显示收集到的所有问卷"""
print("Survey results:")
for response in self.responses:
print(f"-{response}")
这个类首先存储了一个调查问题,并创建了一个空列表,用于存储答案。这个类包含打印调查问题的方法,在答案列表中添加新答案的方法,以及将存储在列表中的答案都打印出来的方法
测试AnonymousSurvey类
import unittest
from sss import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""针对AnonymousSurvey类的测试"""
def test_store_single_response(self):
"""测试单个答案会被妥善地存储"""
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
my_survey.store_response('English')
self.assertIn('English', my_survey.responses)
if __name__ == '__main__':
unittest.main()
首先导入模块unittest和要测试的类。第一个测试方法验证:调查问题的单个答案被存储后,会包含在调查结果列表中。如果这个测试未通过,我们就能通过输出中的方法名得知,在存储单个答案方面存在问题
当我们运行这个程序时,测试通过了
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
下面来核实用户提供三个答案时,它们也将被妥善地存储
import unittest
from sss import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""针对AnonymousSurvey类的测试"""
def test_store_single_response(self):
"""测试单个答案会被妥善地存储"""
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
my_survey.store_response('English')
self.assertIn('English', my_survey.responses)
def test_store_three_response(self):
"""测试三个答案会被妥善地存储"""
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
responses = ['English', 'Chinese', 'Japanese']
for response in responses:
my_survey.store_response(response)
for response in responses:
self.assertIn(response, my_survey.responses)
if __name__ == '__main__':
unittest.main()
再次运行时,两个测试都通过了
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
但这些测试有些重复的地方。下面使用unittest的另一项功能来提高其效率。
方法setUp()
unittest.TestCase类包含的方法setUp()让我们只需创建这些对象一次,就可以在每个测试方法中使用。如果在TestCase类中包含了方法setUp(),python将先运行它,再运行各个以test_打头的方法。这样,在你编写的每个方法中,都可以使用在方法setUp()中创建的对象
下面使用setUp()来创建一个调查对象和一组答案
import unittest
from sss import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""针对AnonymousSurvey类的测试"""
def setUp(self):
"""创建一个调查对象和一组答案"""
question = "What language did you first learn to speak?"
self.my_survey = AnonymousSurvey(question)
self.responses = ['English', 'Chinese', 'Japanese']
def test_store_single_response(self):
"""测试单个答案会被妥善地存储"""
self.my_survey.store_response(self.responses[0])
self.assertIn(self.responses[0], self.my_survey.responses)
def test_store_three_response(self):
"""测试三个答案会被妥善地存储"""
for response in self.responses:
self.my_survey.store_response(response)
for response in self.responses:
self.assertIn(response, self.my_survey.responses)
if __name__ == '__main__':
unittest.main()
如果要扩展AnonymousSurvey,使其允许每位用户输入多个答案,这些测试将很有用。修改代码以接受多个答案后,可运行这些测试,确认存储单个答案或一系列答案的行为未受影响。
测试自己编写的类时,方法setUp()让测试方法编写起来更容易:可在setUp()方法中创建一系列实例并设置其属性,再在测试方法中直接使用这些实例。相比于在每个测试方法中都创建实例并设置属性,这要容易得多
注意,运行测试用例时,每完成一个单元测试,python都打印一个字符:测试通过时打印一个句点,测试引发错误时打印一个E,测试导致断言失败时则打印一个F。
以上为博主自己整理的python入门笔记。由于水平有限,难免出现低级错误,还请各位朋友多多指正。
表中的答案都打印出来的方法
测试AnonymousSurvey类
import unittest
from sss import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""针对AnonymousSurvey类的测试"""
def test_store_single_response(self):
"""测试单个答案会被妥善地存储"""
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
my_survey.store_response('English')
self.assertIn('English', my_survey.responses)
if __name__ == '__main__':
unittest.main()
首先导入模块unittest和要测试的类。第一个测试方法验证:调查问题的单个答案被存储后,会包含在调查结果列表中。如果这个测试未通过,我们就能通过输出中的方法名得知,在存储单个答案方面存在问题
当我们运行这个程序时,测试通过了
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
下面来核实用户提供三个答案时,它们也将被妥善地存储
import unittest
from sss import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""针对AnonymousSurvey类的测试"""
def test_store_single_response(self):
"""测试单个答案会被妥善地存储"""
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
my_survey.store_response('English')
self.assertIn('English', my_survey.responses)
def test_store_three_response(self):
"""测试三个答案会被妥善地存储"""
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
responses = ['English', 'Chinese', 'Japanese']
for response in responses:
my_survey.store_response(response)
for response in responses:
self.assertIn(response, my_survey.responses)
if __name__ == '__main__':
unittest.main()
再次运行时,两个测试都通过了
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
但这些测试有些重复的地方。下面使用unittest的另一项功能来提高其效率。
方法setUp()
unittest.TestCase类包含的方法setUp()让我们只需创建这些对象一次,就可以在每个测试方法中使用。如果在TestCase类中包含了方法setUp(),python将先运行它,再运行各个以test_打头的方法。这样,在你编写的每个方法中,都可以使用在方法setUp()中创建的对象
下面使用setUp()来创建一个调查对象和一组答案
import unittest
from sss import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""针对AnonymousSurvey类的测试"""
def setUp(self):
"""创建一个调查对象和一组答案"""
question = "What language did you first learn to speak?"
self.my_survey = AnonymousSurvey(question)
self.responses = ['English', 'Chinese', 'Japanese']
def test_store_single_response(self):
"""测试单个答案会被妥善地存储"""
self.my_survey.store_response(self.responses[0])
self.assertIn(self.responses[0], self.my_survey.responses)
def test_store_three_response(self):
"""测试三个答案会被妥善地存储"""
for response in self.responses:
self.my_survey.store_response(response)
for response in self.responses:
self.assertIn(response, self.my_survey.responses)
if __name__ == '__main__':
unittest.main()
如果要扩展AnonymousSurvey,使其允许每位用户输入多个答案,这些测试将很有用。修改代码以接受多个答案后,可运行这些测试,确认存储单个答案或一系列答案的行为未受影响。
测试自己编写的类时,方法setUp()让测试方法编写起来更容易:可在setUp()方法中创建一系列实例并设置其属性,再在测试方法中直接使用这些实例。相比于在每个测试方法中都创建实例并设置属性,这要容易得多
注意,运行测试用例时,每完成一个单元测试,python都打印一个字符:测试通过时打印一个句点,测试引发错误时打印一个E,测试导致断言失败时则打印一个F。
以上为博主自己整理的python入门笔记。由于水平有限,难免出现低级错误,还请各位朋友多多指正。
参考资料:《Python编程:从入门到实践(第二版)》