01 前言

因为备赛RoboMaster2020赛季,新出现的雷达站系统可能需要用YOLOv3,需要了解一些深度学习的知识,所以特别来学习一下Python语言,也为以后学习打下基础

24号前要改好YOLOv3的代码进而实现单类别多属性的训练,之前见到的Pytorch的实现其实好像不需要那么麻烦,但是配置环境一直配置不成功,苦恼

因为之前已经学过了些python,所以写博客记录一下

本次学习使用的环境是 windows+pycharm+python3.6


02 变量和简单数据类型

2.1 hello world

1
2
3
4
5
print("Hello Python world!")

message = "Hello world!"

print(message)

2.2 变量

1
2
3
message = "Hello world!"

print(message)

message就是变量,和C/C++中的差不多

2.3 字符串

字符串中如果遇到需要用’’或””符号时,字符串号可以用相反的单引号或双引号,也可以用转义字符

1
2
3
4
5
message = 'I told my friend,"Python is my favorite language."'
message2 = "I like 'python' language."

print(message)
print(message2)

2.3.1 使用方法修改字符串的大小写

1
2
3
4
5
6
name = "ada lovelace"

print(name) # ==================================
print(name.upper()) # .upper()是将字符串中的所有字母都大写
print(name.lower()) # .lower()是将字符串中的所有字母都小写
print(name.title()) # .title()是将字符串中的首字母大写

.upper()是将字符串中的所有字母都大写
.lower()是将字符串中的所有字母都小写
.title()是将字符串中的首字母大写

2.3.2 合并(拼接)字符串

1
2
3
4
5
6
7
first_name = "ada"
last_name = "love lace"

full_name = first_name + " " + last_name

print(full_name)
print("Hello," + full_name + "!")

2.3.3 使用制表符或换行符来添加空白

值得注意的是:在编程中空白泛指任何非打印字符,如空格、制表符和换行符
\n是换行符,\t是制表符,相当于Tab

1
2
print("language:\nPython\nC\nJavaScript")
print("language:\n\tPython\n\tC\n\tJavaScript")

2.3.4 删除空白

1
2
3
4
5
6
7
8
9
10
11
favorite_language = 'python '
print(favorite_language)
print(favorite_language.rstrip()) # .rstrip()是将字符串中末尾的空白删除(暂时删除),注意是末尾且暂时删除
# 要永久删除的话需要通过变量
favorite_language = favorite_language.rstrip()
print(favorite_language)
favorite_language = " python "
print(favorite_language)
print(favorite_language.rstrip()) # .rstrip()是删除右边(末尾)的空白字符
print(favorite_language.lstrip()) # .lstrip()是删除左边(首)的空白字符
print(favorite_language.strip()) # .strip()是同时删除左右(首尾)的空白字符

.rstrip()是删除右边(末尾)的空白字符
.lstrip()是删除左边(首)的空白字符
.strip()是同时删除左右(首尾)的空白字符

2.4 数字

没啥好说的,不同于C/C++,python能够在编译器中直接输出结果


03 列表简介

3.1 列表

1
2
3
4
5
bicycle = ['trek', 'cannondale', 'redline', 'specialized']
print(bicycle)
# 列表可以储存不同类型的元素
List = ['Tom', 2, 3.1]
print(List)

3.1.1 访问列表元素

1
print(bicycle[2])  # 直接用[]里加索引号进行访问

3.1.2 索引是从0而不是1开始

1
2
3
4
print(bicycle[0])
print(bicycle[1])
# 索引值为-1时为返回倒数第一个元素,-2为返回倒数第二个元素,以此类推
print(bicycle[-1])

索引值为-1时为返回倒数第一个元素,-2为返回倒数第二个元素,以此类推

3.1.3 使用列表中的各个值

1
2
message = "My first bike was a " + bicycle[1].title() + "."
print(message)

3.2 修改、添加和删除元素

3.2.1 修改列表元素

直接通过列表[索引号]来修改

1
2
3
4
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)
motorcycles[0] = 'ducati' # 直接通过列表[索引号]来修改
print(motorcycles)

3.2.2 在列表中添加元素

1
2
3
4
5
6
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)
motorcycles.append('ducati') # .append函数是在末尾添加一个指定元素,注意是末尾
print(motorcycles)
motorcycles.insert(2, 'chenai') # .insert(索引号, 元素)在指定索引值位置插入指定元素,而后元素索引值全部退后一个数
print(motorcycles)

.append函数是在末尾添加一个指定元素,注意是末尾
.insert(索引号, 元素)在指定索引值位置插入指定元素,而后元素索引值全部退后一个数

3.2.3 从列表中删除元素

1
2
3
4
5
6
7
8
9
10
11
12
13
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)

del motorcycles[0] # del 列表[索引值] 通过索引值直接删除列表中的指定元素,为永久删除,修改原列表
print(motorcycles)

motorcycles_pop = motorcycles.pop(-1) # .pop() 为删除列表中的指定索引值元素,该变量即为该元素,原列表中已删除
print(motorcycles)
print(motorcycles_pop)

motorcycles = ['honda', 'yamaha', 'suzuki']
motorcycles.remove('yamaha') # .remove() 为已知元素,而删除指定元素,而不是用索引值
print(motorcycles)

del 列表[索引值] 通过索引值直接删除列表中的指定元素,为永久删除,修改原列表
.pop() 为删除列表中的指定索引值元素,该变量即为该元素,原列表中已删除
.remove() 为已知元素,而删除指定元素,而不是用索引值

3.3 组织列表

3.3.1 使用方法sort()对列表进行永久性排序

1
2
3
4
5
6
cars = ['bmw', 'audi', 'toyota', 'subaru']
cars.sort() # .sort() 为进行顺序排序,由首字母排序或数字排序,注意是顺序排序
print(cars)

cars.sort(reverse=True) # .sort(reverse=True) 为逆序拍戏,注意是逆序排序
print(cars)

.sort() 为进行顺序排序,由首字母排序或数字排序,注意是顺序排序
.sort(reverse=True) 为逆序拍戏,注意是逆序排序

3.3.2 使用函数sorted()对列表进行临时排序

1
2
3
4
5
6
7
print("Here is the orighinal list:")
print(cars)

print("Here is the sorted list:")
print(sorted(cars)) # sorted() 函数为临时排序,非永久性排序

print(cars)

sorted() 函数为临时排序,非永久性排序

3.3.3 倒着打印列表

1
2
cars.reverse()
print(cars) # 倒着排序,永久性排序

.reverse() 倒着排序,永久性排序

3.3.4 确定列表的长度

1
print(len(cars))

len(list) 计算列表长度


04 操作列表

4.1 遍历整个列表

4.1.1 深入地研究循环

1
2
3
magicians = ['alice', 'david', 'carolina']
for magician in magicians: # for 变量 in 列表
print(magician)

4.1.2 在for循环中执行更多的操作

1
2
3
for magician in magicians:
print(magician.title() + ", that was a great trick!")
print("I can't wait to see your next trick, " + magician.title() + ".")

4.1.3 在for循环结束后执行一些操作

1
2
3
4
for magician in magicians:
print(magician.title() + ", that was a great trick!")
print("I can't wait to see your next trick, " + magician.title() + ".\n")
print("Thank you, everyone. That was a great magic show!")

for 变量 in 列表: 对列表进行遍历操作

4.2 缩进错误

python的缩进就相当于C/C++中的{},有着严格的要求

4.3 创建数字列表

4.3.1 使用函数range()

1
2
for value in range(1, 5):
print(value)

range(a, b) 函数创建数字集,从a开始创建,到b结束,注意是到b结束,就好比如rang(1, 5)到5的时候退出,而不输出5

4.3.2 使用range()创建数字列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
numbers = list(range(1, 6))  # 从1开始到5
print(numbers)

even_numbers = list(range(2, 11, 2)) # 从2开始到10,每间隔2
print(even_numbers)

squares = []
for value in range(1, 11):
square = value**2
squares.append(square)
print(squares)

for value in range(1, 11):
squares.append(value**2)
print(squares)

range(a, b, c) c为间隔数

4.3.3 对数字列表执行简单的统计计算

1
2
3
4
5
6
digits = list(range(1, 10))
digits.append(0)
print(digits)
print(min(digits))
print(max(digits))
print(sum(digits))

4.3.4 列表解析

1
2
squares = [value**2 for value in digits]
print(squares)

4.4 使用列表的一部分

4.4.1 切片

1
2
3
4
5
6
7
players = ['charles', 'martina', 'michael', 'florence', 'eli']
print(players[0:3])

print(players[:4])
print(players[3:])
print(players[-3:]) # 从倒数第三位到最后一位
print(players[2:-2])

list[a:b] 为列表切片,从列表的0索引开始到b-1,也就是上面的3就是索引值+1,可用负数索引

4.4.2 遍历切片

1
2
3
print("Here are the first three players on my team:")
for player in players[:3]:
print(player.title())

4.4.3 复制列表

1
2
3
4
players_copy = players
players_copy2 = players[:]
print(players_copy)
print(players_copy2)

4.5 元组

4.5.1 定义元组

1
2
3
dimensions = (200, 50)  # 元组不同于列表,元组用的是圆括号来定义
print(dimensions[0])
print(dimensions[1])

4.5.2 遍历元组中的所有值

1
2
3
dimensions = (200, 50)
for dimension in dimensions:
print(dimension)

4.5.3 修改元组变量

dimension[0] = 1;会报错,元组中的元素是不能改变的

1
2
dimensions = (500, 50)
print(dimensions)

元组中的元素是不能更改的,但是元组变量是可以更改的


05 if语句

5.1 初试if

python的if和C/C++中的if的语法基本上一样,没什么难度

5.2 条件测试

5.1 一个简单示例

1
2
3
4
5
6
cars = ['audi', 'bmw', 'subaru', 'toyota']
for car in cars:
if car == 'bmw':
print(car.upper())
else:
print(car.title())

5.2.1 检查是否相等

1
2
3
4
5
car = 'audi'
if car == 'audi': # 判断是否相等用==号
print("True")
else:
print("False")

5.2.2 检查是否相等时需要考虑大小写

1
2
3
4
5
6
7
car = 'bwm'
if car.upper() == 'BWM':
print("True")
elif car == 'Bwm':
print("NO, please try again.")
elif car == 'bwm':
print("False")

5.2.3 检查是否不相等

1
2
3
4
requested_topping = 'mushrooms'

if requested_topping != 'anchovies': # 判断相等用==,不相等用!=
print("Hold the anchovies!")

5.2.4 比较数字

1
2
3
4
5
6
7
8
9
age = 18
if age == 18:
print("True")
else:
print("False")

answer = 17
if age != 42:
print("That is not the correct answer. Please try again!")

5.2.5 检查多个条件

1
2
3
4
5
age_0 = 19
if (age_0 >= 18) and (age_0 <= 42):
print("YES")
elif (age_0 < 18) or (age_0 > 42):
print("NO")

5.2.6 检查特定值是否包含在列表中

1
2
requested_toppings = ['mushrooms', 'onions', 'pineapple']
print('mushrooms' in requested_toppings) # 判断某元素是否在列表中,直接使用 in

5.2.7 检查特定值是否不包含在列表中

1
2
3
4
banned_users = ['andrew', 'carolina', 'david']
user = 'marie'
if user not in banned_users: # 判断某元素是否不在列表中,直接用 not in
print(user.title() + ", you can post a response if you wish.")

5.2.8 布尔表达式

1
2
game_active = True
can_edit = False

5.3 if语句

5.3.1 简单的if语句

1
2
3
4
age = 19
if age >= 18:
print("You are old enough to vote!")
print("Have you registered to vote yet?")

5.3.2 if-else语句

1
2
3
4
5
6
7
age = 17
if age >= 18:
print("You are old enough to vote!")
print("Have you registered to vote yet?")
else:
print("Sorry, you are too young to vote.")
print("Please register to vote as soon as you turn 18!")

5.3.3 if-elif-else结构

1
2
3
4
5
6
7
age = 12
if age < 4:
print("Your admission cost.")
elif age < 18:
print("Your admission cost is $5.")
else:
print("Your admission cost is $10.")

5.3.4 使用多个elif代码块

1
2
3
4
5
6
7
8
9
10
age = 12
if age < 4:
price = 0
elif age < 18:
price = 5
elif age < 65:
price = 10
else:
price = 5
print("Your admission cost is $" + str(price) + ".")

5.4 使用if语句处理列表

5.4.1 检查特殊元素

1
2
3
4
5
6
7
8
9
requested_toppings = ['mushrooms', 'green peppers', 'extra cheese']

for requested_topping in requested_toppings:
if requested_topping == 'green peppers':
print("Sorry, we are out of green peppers right now.")
else:
print("Adding " + requested_topping + ".")

print("\nFinished making your pizza!")

5.4.2 确定列表不是空的

1
2
3
4
5
6
7
8
requested_toppings = []

if requested_toppings: # 如果列表不为空(有至少一个元素)
for requested_topping in requested_toppings:
print("Adding " + requested_topping + ".")
print("\nFinished making your pizza!")
else: # 如果列表为空则
print("Are you sure you want a plain pizza.")

直接用 if 列表:就可以判断是否为空,如果为空则返回False,不为空则返回True

5.4.3 使用多个列表

1
2
3
4
5
6
7
8
9
10
11
available_toppings = ['mushrooms', 'olives', 'green peppers',
'pepperoni', 'pineapple', 'extra cheese']
requested_toppings = ['mushrooms', 'french fries', 'extra cheese']

for requested_topping in requested_toppings:
if requested_topping in available_toppings: # 判断列表元素是否在另一个列表
print("Adding " + requested_topping + ".")
else:
print("Sorry, we don't have " + requested_topping + ".")

print("\nFinished making your pizza!")

06 字典

6.1 一个简单的字典

1
2
3
4
5
# ======================================================================================================================
alien_0 = {'color': 'green',
'points': 5}
print(alien_0['color'])
print(alien_0['points'])

6.2 使用字典

6.2.1 访问字典中的值

1
2
3
4
5
6
alien_0 = {'color': 'green'}
print(alien_0['color'])
alien_0 = {'color': 'green',
'points': 5}
new_points = alien_0['points']
print("You just earned " + str(new_points) + ' points!')

直接用{}来创建字典
里面的’键’: ‘值’称为键值对,键为名字,值可以是任何类型的,比如说整性或者是列表甚至可以是字典,键值对是一一对应的

6.2.2 添加键值对

1
2
3
4
5
6
alien_0 = {'color': 'green',
'points': 5}
print(alien_0)
alien_0['x_position'] = 0
alien_0['y_position'] = 25
print(alien_0)

利用 字典名[键] = 值 来向字典中添加键值对

6.2.3 先创建一个空字典

1
2
3
4
alien_0 = {}
alien_0['color'] = 'green'
alien_0['points'] = 5
print(alien_0)

6.2.4 修改字典中的值

1
2
3
4
5
alien_0 = {'color': 'green'}
print("The alien is " + alien_0['color'] + '.')

alien_0['color'] = 'yellow'
print("The alien is " + alien_0['color'] + '.')

直接通过 字典[键] = 想要修改的值 就行了,类似与列表,列表[索引值] = 想要修改的值

6.2.5 删除键值对

1
2
3
4
5
alien_0 = {'color': 'green',
'points': 5}
print(alien_0)
del alien_0['points']
print(alien_0)

通过 del 字典[键] 的方式,可以直接删除键值对,其他键值对不会受到影响,删除的键值对会永久消失

6.2.6 由类似对象组成的字典

1
2
3
4
5
6
7
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python'
}
print("Sarah's favorite language is " + favorite_languages['sarah'] + '.')

6.3 遍历字典

6.3.1 遍历所有的键值对

1
2
3
4
5
6
7
8
9
user_0 = {
'username': 'efermi',
'first': 'enrico',
'last': 'fermi'
}

for key, value in user_0.items():
print("\nKey:" + key)
print("Value:" + value)

遍历字典,需要定义两个变量,一个用于储存他的键,一个储存他的值

并且遍历时,必须要在字典后加.items() 键值对的意思

6.3.2 遍历字典中的所有键

1
2
3
4
5
6
7
8
9
10
favorite_language = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python'
}
for name in favorite_language.keys():
print(name.title())
for name in favorite_language:
print(name.title())

单纯遍历字典中的所有键的时候

用.keys(),就会遍历所有的键,而不去遍历键值对

其实它默认也是遍历所有的键,所以加.keys()和不加的效果是一样的,但是遍历键值对的时候一定要用.items()

1
2
3
4
5
6
7
8
9
10
11
12
favorite_language = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python'
}
friends = ['phil', 'sarah']
for name in favorite_language.keys():
print(name.title())

if name in friends:
print(" Hi " + name.title() + ", I see your favorite language is " + favorite_language[name].title() + "!")

6.3.3 按顺序遍历字典中的所有键

1
2
3
4
5
6
7
8
9
favorite_language = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python'
}

for name in sorted(favorite_language.keys()):
print(name.title() + ", thank you for taking the poll.")

6.3.4 遍历字典中的所有值

1
2
3
4
5
6
7
8
9
favorite_language = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python'
}
print("The following languages have been mentioned:")
for language in favorite_language.values():
print(language.title())

遍历字典中的所有值的时候,需要用到.value()
要剔除掉字典中的重复项,可使用集合(set)集合类似于列表,单每个元素都必须独一无二

1
2
for language in set(favorite_language.values()):
print(language.title())

set(字典),可以剔除掉字典中的重复的项

6.4 嵌套

6.4.1 字典列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
alien_0 = {'color': 'green', 'points': 5}
alien_1 = {'color': 'yellow', 'points': 10}
alien_2 = {'color': 'red', 'points': 15}

aliens = [alien_0, alien_1, alien_2] # 在列表中储存字典

print(aliens)

for alien in aliens:
print(alien)

# 创建一个用于储存外星人的列表
aliens = []
# 创建30个蓝白色的外星人
for alien_number in range(30):
new_alien = {'color': 'green', 'points': 5, 'speed': 'slow'}
aliens.append(new_alien)
# 显示前五个外星人
for alien in aliens[:5]:
print(alien)
print("...")
# 显示创建了多少个外星人
print("Total number of aliens: " + str(len(aliens)))

for alien in aliens[:3]:
if alien['color'] == 'green':
alien['color'] = 'yellow'
alien['points'] = 10
alien['speed'] = 'medium'

for alien in aliens[:5]:
print(alien)
print("...")

6.4.2 在字典中储存列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pizza = {
'crust': 'thick',
'toppings': ['mushrooms', 'extra cheese']
}
print("You ordered a " + pizza['crust'] + '-crust pizza ' + "with the following toppings:")

for topping in pizza['toppings']:
print("\t" + topping)

favorite_language = {
'jen': ['python', 'ruby'],
'sarah': ['c'],
'edward': ['ruby', 'go'],
'phil': ['python', 'haskell']
}
for name, languages in favorite_language.items():
print("\n" + name.title() + "'s favorite languages are:")
for language in languages:
print("\t" + language.title())

6.4.3 在字典中储存字典

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
users = {
'aeinstein': {
'first': 'albert',
'last': 'einstein',
'location': 'princeton',
},
'mcurie': {
'first': 'marie',
'last': 'curie',
'location': 'paris',
},
}
for username, user_info in users.items():
print("\nUsername: " + username)
full_name = user_info['first'] + " " + user_info['last']
location = user_info['location']

print("\tFull name: " + full_name.title())
print("\tLocation: " + location.title())

07 用户输入和while循环

7.1 函数input()的工作原理

函数input()让程序暂停运行,等待用户输入一些文本。获取用户输入后,python将其存储在一个变量中,以方便你使用

例如,下面的程序让用户输入一些文本,再将这些文本呈现给用户:

1
2
message = input("Tell me something, and I will repeat it back to you.")
print(message)

函数input()接受一个参数给message

7.1.1 编写清晰的程序

每当你使用函数input()时,都应指定清晰易懂的提示,准确地指出你希望用户提供什么样的信息——任何指出用户该输入何种信息都行,如下:

1
2
name = input("Please enter your name: ")
print("Hello, " + name + "!")

有时候,提示可能超过一行,例如,你可能需要指出获取特定输入的原因
1
2
3
4
5
prompt = "If you tell us who you are, we can personalize the messages you see."
prompt += "\nWhat is your first name?"

name = input(prompt)
print("\nHello, " + name + "!")

这个示例演示了一种创建多行字符串的方式。第一行将消息前半部分储存在prompt中,再第二行中,运算符+=在储存在prompt中的字符串末尾附加一个字符串

7.1.2 使用int()来获取数值的输入

使用函数input()时,python将用户输入解读为字符串。
所以,如果我们运行下面这个程序它会报错

1
2
3
age = input(请输入一个数字)
age += 1
print(age)

报错

1
2
3
4
Traceback (most recent call last):
File "D:/PyCharm项目/Python编程从入门到实践/py_2.4_数字.py", line 2, in <module>
age += 1
TypeError: can only concatenate str (not "int") to str

因为python默认我们输入的时字符串,我们输入的字符串是不可以与数字相加的,所以我们需要用int来作转换
1
2
3
age = int(input())
age += 1
print(age)

7.1.3 求模运算符

处理数值信息时,求模运算符(%)是一个非常有用的工具,它将两个数相除并返回余数:

1
2
3
4
5
6
7
8
>>> 4 % 3
1
>>> 5 % 3
2
>>> 6 % 3
0
>>> 7 % 3
1

这和在C/C++中的规则是一样的,常用于判断一个数字是奇数还是偶数

7.2 while循环简介

for循环用于针对集合中的每一个元素的代码块,而while循环不断地运行,知道指定的条件不满足为止

7.2.1 使用while循环

1
2
3
4
number = 1
while number <= 5:
print(number)
number += 1

out:

1
2
3
4
5
1
2
3
4
5

7.2.2 让用户选择何时退出

1
2
3
4
5
6
7
8
9
prompt = "\nTell me something, and I will repeat it back to you: "
prompt += "\n Enter 'quit' to end the program."

message = ""
whlile message != 'quit':
message = input(prompt)

if message != 'quit'
print(message)

所以,当用户输入quit时,就会主动退出程序

7.2.3 使用标志

我们常常在算法中也使用这种思维方式,就是先设定一个标志,或者说旗帜,任何再作判断

1
2
3
4
5
6
7
8
active = True
while active:
message = input()

if message == 'quit':
active = False
else:
print(message)

7.2.4 使用break退出循环

这跟C/C++中的一样,使用break可以直接退出循环continue继续循环等

1
2
3
4
5
6
active = True
while active:
message = input()

if message = '1'
breack

7.2.5 在循环中使用continue

和C/C++使用规则应该是一样的

1
2
3
4
5
6
7
number = 0
while number < 10:
number += 1
if num % 2 == 0:
continue

print(number)

out:
1
2
3
4
5
1
3
5
7
9

7.3 使用while循环来处理列表和字典

for循环是一种遍历列表的有效方式,但在for循环中不应修改列表,否则将导致python难以跟踪其中的元素。要在遍历列表的同时对其进行修改,可使用while循环。通过while循环同列表和字典结合起来使用,可收集,储存并组织大量的输入,供以后查看和显示

7.3.1 在列表之间移动元素

如果while + 列表则列表如果不为空则为True,为空则为False

1
2
3
4
5
6
7
unconfirmed_users = ['alice', 'brian', 'condace']
confirmed_users = []

while unconfirmed_users:
current_user = unconfirmed_user.pop()

confirmed_users.append(current_user)

这样,unconfirmed_users中就没有了元素,全部转移到confirmed_users中了,但是如果是用for in的话
1
2
3
4
5
unconfirmed_users = ['alice', 'brian', 'condace']
confirmed_users = []

for currrent_user in unconfirmed_users:
confirmed_users.append(current_user)

则没删除原来列表中的元素,当然如果:
1
2
3
4
5
6
7
unconfirmed_users = ['alice', 'brian', 'condace']
confirmed_users = []

for current_user in unconfirmed_users:
current_user = unconfirmed_user.pop()

confirmed_users.append(current_user)

也行,但是很奇怪

7.3.2 删除包含特定值的所有列表元素

之前我们删除列表中的特定值用的是remove()函数,但是,它只能删除里面的第一次出现的特定值,没办法删除所有的特定值,如果要删除所有的特定值我们就需要用到while

1
2
3
4
5
6
7
pets = ['dog', 'cat', 'dog', 'goldfish', 'cat', 'rabbit', 'cat']
print(pets)

while 'cat' in pets:
pets.remove('cat')

print(pets)

这和for in实现是一样的

1
2
3
4
5
6
7
8
pets = ['dog', 'cat', 'dog', 'goldfish', 'cat', 'rabbit', 'cat']
print(pets)

for cat in pets:
if cat == "cat":
pets.remove(cat)

print(pets)

7.3.3 使用用户输入来填充字典

可使用while循环提示用户输入任意数量的信息。下面来创建一个调查程序,其中输入的循环每次执行时都会提示输入调查者的名字和回答。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
responses = {}

while True:
name = input("\nWhat is your name?")
response = input("Which mountiain would you lick to climb someday?")
responses[name] = response

repeat = input("Would you like to let another person respond?(yes/no)")

if repeat == 'yes':
continue
else:
break

print(responses)

08 函数

8.1 定义函数

def定义的就是函数

1
2
3
4
5
def greet_user():
print("Hello!")


greet_user()

def 函数名():后面用冒号结尾,函数与下面主题部分空两行作为规定
函数必须在调用主体前面,就和C/C++中的一样的道理

8.1.1 向函数传递信息

def 函数名(形参):这和C/C++中不一样,def会自己识别类型不需要自己判读

1
2
3
4
5
6
def greet_user(username):
print("Hello " + username + '!')


name = input()
greet_user(name)

8.1.2 实参和形参

前面定义函数greet_user()时,要求给变量username指定一个值。调用这个函数并提供这种信息(人名)时,它将打印相应的问候语
在函数greet_user()的定义中,变量username时一个形参——函数完成其工作所需的一项信息。在代码greet_user('name')中,值name是一个实参。实参是调用函数时,传递给函数的信息。我们调用函数时,将要让函数使用的信息放在括号内。在greet_user(name)中,将实参name传递给函数greet_user(),这个值被储存在实参username中。

8.2 传递实参

鉴于函数定义中可能包含多个形参,因此函数调用中也可能包含多个实参。向函数传递实参的方式很多,可使用位置实参,这要求实参的顺序与形参的顺序相同;也可使用关键词实参,其中每个实参都由变量名和值组成;还可使用列表和字典。

8.2.1 位置实参

1
2
3
4
5
6
7
def student(name, age):
print("My name is " + name + ", and " + str(age) + " years old.")


username = input()
userage = input()
student(username, userage)

这里name对应usernameage对应userage,调用函数时要一一对应,不可反转,位置很重要

8.2.2 关键字实参

关键字实参 时传递给函数的名称-值对。直接在实参中将名称和值关联起来,因此就不会混淆顺序

1
2
3
4
5
6
7
8
def student(name, age):
print("My name is " + name + ", and " + str(age) + " years old.")


username = input()
userage = input()
student(name=username, age=userage)
student(age=userage, name=username)

这样无论实参中顺序怎么样,都不会混淆

8.2.3 默认值

编写函数时,可给每个形参指定默认值。在调用函数中给形参提供实参时,python将使用指定的实参值;否则,将使用形参的默认值

1
2
3
4
5
6
def student(name='lala', age=11):
print("My name is " + name + ", and " + str(age) + " years old.")


student()
student('qwqw', 22)

out:
1
2
My name is lala, and 11 years old.
My name is qwqw, and 22 years old.

8.3 返回值

函数并非总是直接显示输出,相反,它可以处理一些数据,并返回一个或一组值。函数返回的值被称为返回值。在函数中,可使用return语句将值返回掉调用函数的代码行中。返回值让你能够将程序的大部分繁重工作移到函数中去完成,从而简化主程序

8.3.1 返回简单值

1
2
3
4
5
6
def get_formatted_name(first_name, last_name):
full_name = first_name + last_name
return full_name.title()


musican = get_formatted_name('jimi', 'hendeix')

8.3.2 让实参变成可选的

有时候,需要让实参变成可选的,这样使用函数的人就只需在必要时才提供额外的信息。可使用默认值来让实参变成可选的。

1
2
3
4
5
6
7
8
9
def get_formatted_name(first_name, last_name, middle_name=''):
if middle_name:
full_name = first_name + middle_name + last_name
else:
full_name = first_name + last_name
return full_name.title()


musican = get_formatted_name('jimi', 'hendeix')

8.3.3 返回字典

函数可返回任何类型的值,包括列表和字典等效复杂的数据结构。例如,下面的函数接受姓名的组成部分,并返回一个表示人的字典

1
2
3
4
5
6
def build_person(first_name, last_name):
person = {'first' = first_name, 'last' = last_name}
return person


musican = build_person('jimi', 'hendrix')

8.3.4 结合使用函数和while循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def get_formatted_name(first_name, last_name):

full_name = first_name + last_name
return full_name


while True:
print("\nPlease tell me your name: ")
print("(enter 'q' at any time to quit)")

f_name = input("First name: ")
if f_name == 'q':
break

l_name = input("Last name: ")
if l_name == 'q':
break

formatted_name = get_formatted_name(f_name, l_name)
print("\nHello, " + formatted_name + '!')

8.4 传递列表

将列表传递给函数后,函数就能直接访问其内容

1
2
3
4
5
6
7
8
9
def greet_users(names):

for name in names:
msg = "Hello, " + name.title() + "!"
print(msg)


usernames = ['hannah', 'ty', 'margot']
greet_users(usernames)

我们将greet_users()定义成接受一个名字列表,并将其存储在形参names中。这个函数遍历受到的列表,并对其中的每一个元素进行操作。

8.4.1 在函数修改列表

将列表传递给函数后,函数就可对其进行修改。在函数中对这个列表所做的任何修改都是永久性的,这能够高效地处理大量的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def print_models(unprinted_designs, completed_models):

while unprinted_designs:
current_design = unprinted_design.pop()

print("Printing model: " + current_design)
completed_models.append(current_design)


def show_conpleted_models(completed_models):

print("\nThe following models have been printed: ")
for completed_model in completed_models:
print(completed_model)


unprinted_designs = ['iphone case', 'robot pendant', 'dodecahedron']
completed_models = []

print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)

8.4.2 禁止函数修改列表

有时候,需要禁止函数修改列表,这个时候我们可以使用前面学到的切片的思想把实参的副本传递给函数

8.5 传递任意数量的实参

有时候,我们不能预先知道我们需要传递多少个实参

1
2
3
4
5
6
7
def make_pizza(*toppings):

print(toppings)


make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')

形参名*toppings中的星号让python创建一个名为toppings的空元组,并收到的所有值都封装到这个元组中。函数体内的print语句通过生成输出来证明python能够处理使用一个值调用函数的情形,也能处理使用三个值来调用函数的情形。
1
2
3
4
5
6
7
8
9
def make_pizza(*toppings):

print("\nMaking a pizza with the following toppings.")
for topping in toppings:
print("- " + topping)


make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')

8.5.1 结合使用位置实参和任意数量实参

让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。python先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中

1
2
3
4
5
6
7
8
9
def make_pizza(size, *toppings):

print("\nMaking a " + str(size) + " -inch pizza with the following toppings:")
for topping in toppings:
print("- " + topping)


make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')

8.5.2 使用任意数量的关键字实参

有时候,需要接受任意数量的实参,但预先不知道传递给函数的会是什么样的信息。这种情况下,可将函数编写成能够接受任意数量的键-值对——调用语句提供了多少就接受多少

1
2
3
4
5
6
7
8
9
10
11
12
13
def build_profile(first, last, **user_info):

profile = {}
profile['first_name'] = first
profile['last_name'] = last
for key, value in user_info.items():
profile[key] = value

return profile


user_profile = build_profile('albert', 'einstein', location='princeton', field='physics')
print(user_profile)

函数build_profile()的定义要求提供名和姓,同时允许用户根据需要提供任意数量的名称-值对。形参**user_info中的两个星号让python创建一个名为user_info的空字典,并将收到的所有名称-值对都封装到这个字典中

8.6 将函数存储在模块中

函数的优点之一是,使用它们可将代码块和主程序分离。通过给函数指定描述性名称,可让主程序容易理解得多。还可以更进一步,将函数储存在被称为模块得独立文件中,再将模块导入到主程序中。import语句允许在当前运行的程序文件中使用模块中的代码
通过将函数存储在独立的文件中,可隐藏程序代码的细节,将重点放在程序的高层逻辑上。这还能让你在众多不同的程序中重用函数。将函数存储在独立文件中后,可与其他程序员共享这些文件而不是整个程序,知道如何导入函数还能让你使用其他程序员编写的函数库

8.6.1 导入整个模块

先创建一个模块文件pizza.py

1
2
3
4
5
def make_pizza(size, *toppings):
"""概述要制作的比萨"""
print("\nMaking a " + str(size) + "-inch pizza with the following toppings:")
for topping in toppings:
print(topping)

如何在主程序中调用这个模块
1
2
3
import pizza

pizza.make_pizza(17, 'mushrooms')

通过import调用以后,再调用模块里面的函数需要用一个点号来访问,然后跟函数名

8.6.2 导入特定的函数

刚刚我们介绍的是调用整个模块,现在是调用这个模块里的一部分
这个时候我们需要用到from xxx import xxx来调用xxx模块中的xxx函数,就比如我们刚刚的pizza.py中有很多个函数,而我们不需要全部导入,我们只需要导入一部分,则
from pizza import make_pizza
同理,多个函数的话用逗号隔开
from module_name import function1, function2, function3

8.6.3 使用as给函数指定别名

如果导入的函数的名称可能与程序现有的名称有冲突,或者函数名称太长,可指定简短而独一无二的别名——函数的另一个自定义名称
from pizza import make_pizza as mp
然后以后调用就直接pizza.mp就可以了

8.6.4 使用as给模块指定别名

之前学tensorflow的时候,就经常会将其指为tf
import tensorflow as tf
然后调用就直接用tf调用其模块内的函数就可以了,就不用敲一大串的tensorflow

8.6.5 导入模块中的所有函数

使用星号(*)运算符可让python导入模块中的所有函数
from pizza import *
import语句中的星号让python将模块pizza中的每一个函数都复制到这个程序文件中。由于导入了每一个函数,可以荣光名称来调用每一个函数,而无需通过点号来访问

1
2
3
import pizza

pizza.make_pizza(17, 'mushrooms')

区别于
1
2
3
from pizza import *

make_pizza(17, 'mushrooms')

一个需要通过点号访问,一个不需要


09 类

面向对象编程是最有效的软件编写方法之一。在面向对象编程中,编写表示现实世界中的实物和情景的类,并基于这些类来创建对象。编写类时,定义一大类对象都有的通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。
根据类来创建对象被称为实例化,这让你能够使用类的实例

9.1 创建和使用类

使用类几乎可以模拟任何东西。下面来编写一个表示小狗的类Dog——它表示的不是特定的小狗,而是任何小狗

9.1.1 创建Dog类

根据Dog类创建的每个实例都将储存名字和年龄。我们赋予了每条小狗蹲下(sit())和打滚(roll_over())的能力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Dog():
"""一次模拟小狗的简单尝试"""

def __init__(self, name, age):

self.name = name
self.age = age

def sit(self):

print(self.name.title() + " is now sitting.")

def roll_over(self):

print(self.name.title() + " rolled over!")

在这里我们定义了一个名叫Dog的类,根据python中的约定,首字母大写的名称称为类。我们创建的这个类括号里没用东西,是空的,因为我们要从空白创建这个类。class Dog():下面是对这个类的描述,这跟定义函数的描述规则是一样的

方法init()

类中的函数称为方法;前面学到的有关函数的一切都使用于这个方法,就目前而言,唯一重要的差别是调用方法的方式。刚刚的代码中__init__()是一个特殊的方法,每当你根据Dog类创建新实例时,python
都会自动运行它。在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免python默认方法和普通方法发生名称冲突
我们将方法__init__()定义成了包含三个形参:self、name和age。在这个方法的定义中,形参self必不可少,还必须位于其他形参的前面,因为python调用这个__init__()方法来创建Dog实例时,将自动传入实参self。每个与类相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。我们创建Dog实例时,python将调用Dog类的方法__init__()。我们将通过实参Dog()传递名字和年龄,self会自动传递,因此我们不需要传递它。每当我们根据Dog类创建实例时,都只需给最后两个形参(name和age)提供值
类中的函数中的变量前缀都有self,以self为前缀的变量都可供类中的所有方法使用,我们还可以通过类的任何实例来访问这些变量。self.name = name获取储存在形参中name中的值,并将其储存到变量name中,然后该变量被关联到当前创建的实例。self.age = age的作用与此类似,像这样可通过实例访问的变量称为属性
Dog类还定义了另外两种方法:sit()roll_over()。由于这些方法不需要额外的信息,如年龄或名字,因此它们只有一个形参self。我们后面将创建的实例能够访问这些方法,换句话说,它们都会蹲下和打滚。

9.1.2 根据类创建实例

可将类视为有关如何创建实例的说明。Dog类是一系列说明,让python知道如何创建表示特定小狗的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Dog:
"""一次模拟小狗的简单尝试"""

def __init__(self, name, age):

self.name = name
self.age = age

def sit(self):

print(self.name.title() + " is now sitting.")

def roll_over(self):

print(self.name.title() + " rolled over!")


my_dog = Dog('wille', 6)

print("My dog's name is " + my_dog.name.title() + ".")
print("My dog is " + str(my_dog.age) + " years old.")

这里使用的是前一个示例编写的Dog类。在这,让python创建一条名为’wille’、年龄为6的小狗。遇到这行代码时,python使用实参‘while’和6调用Dog类中的方法__init__()。方法__init__()创建一个表示特定小狗的实例,并使用我们提供的值来设置属性name和age。方法__init__()并未显式地包含return语句,但python自动返回一个表示小狗的实例。将这个实例储存在my_dog中。在这里,命名约定很有用:我们通常认为首字母大写的名称(如Dog)指的是类,而小写的名称(如my_dog)指的是根据类创建的实例。

1、访问属性

要访问实例的属性,可使用句点表示法。在这我们编写了my_dog.name
句点表示法在python中很常用,这种语法演示了python如何获悉属性的值。在这里,python先找到实例my_dog,在查找这个实例相关联的属性name。在Dog类中引用这个属性时,使用的是self.name。在上面print语句中使用同样的方法来获取属性age的值。在前面的第一条print语句中,my_dog.name.title()将my_dog的属性name的值’wille’改为首字母大写的

1
2
My dog's name is Wille.
My dog is 6 years old.

2、调用方法

根据Dog类创建实例后,就可以使用句点表示法来调用Dog类中定义的任何方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Dog:
"""一次模拟小狗的简单尝试"""

def __init__(self, name, age):

self.name = name
self.age = age

def sit(self):

print(self.name.title() + " is now sitting.")

def roll_over(self):

print(self.name.title() + " rolled over!")


my_dog = Dog('wille', 6)
my_dog.sit()
my_dog.roll_over()

要调用方法,可指定实例的名称和调用的方法,并用句点分隔它们。
out:
1
2
Wille is now sitting.
Wille rolled over!

这种语法很有用。如果给属性和方法指定了合适的描述性名称,如name、age、sit()和roll_over(),即便是从未见过的代码块,我们也能轻松地推断出它是做什么的

3、创建多个实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Dog:
"""一次模拟小狗的简单尝试"""

def __init__(self, name, age):

self.name = name
self.age = age

def sit(self):

print(self.name.title() + " is now sitting.")

def roll_over(self):

print(self.name.title() + " rolled over!")


my_dog = Dog('wille', 6)
your_dog = Dog('lucy', 3)

print("My dog's name is " + my_dog.name.title() + ".")
print("My dog is " + str(my_dog.age) + " years old.")
my_dog.sit()
print("Your dog's name is " + your_dog.name.title() + ".")
print("Your dog is " + str(your_dog.age) + " years old.")
your_dog.sit()

在这里我们创建了两条小狗

1
2
3
4
5
6
My dog's name is Wille.
My dog is 6 years old.
Wille is now sitting.
Your dog's name is Lucy.
Your dog is 3 years old.
Lucy is now sitting.

9.2 使用类和实例

可以使用类来模拟现实世界中的很多情景。类编写好后,大部分时间都将花在使用根据类创建的实例上

9.2.1 Car类

下面编写一个表示汽车的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Car:
"""一次模拟汽车的简单尝试"""

def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year

def get_descriptive_name(self):
"""返回整洁的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()


my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())

out:
1
2016 Audi A4

9.2.2 给属性指定默认值

类中的每个属性都必须有初始值,哪怕这个值是0或空字符串。在有些情况下,如设置默认值时,在方法__init__()内指定这种初始值时可行的;如果你对某个属性这样做了,就无需包含为它提供初始值的形参
下面添加一个名为odometerreading的属性,其初始值总是为0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Car:
"""一次模拟汽车的简单尝试"""

def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0

def get_descriptive_name(self):
"""返回整洁的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()

def read_odometer(self):
"""打印一条指出汽车里程的消息"""
print("This car has " + str(self.odometer_reading) + " miles on it.")


my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()

现在。当python调用方法`_init
()`来创建实例时,将像前一个示例哦一样以属性的方式存储制造商、型号和生产年份。接下来,python将创建一个名为odometer_reading的属性,并将其初始值设置为0

9.2.3 修改属性的值

可以以三种不同的方式修改属性的值:直接通过实例进行修改;通过方法进行设置;通过方法进行递增(增加特定的值)

1、直接修改属性的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Car:
"""一次模拟汽车的简单尝试"""

def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0

def get_descriptive_name(self):
"""返回整洁的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()

def read_odometer(self):
"""打印一条指出汽车里程的消息"""
print("This car has " + str(self.odometer_reading) + " miles on it.")


my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()
my_new_car.odometer_reading = 23
my_new_car.read_odometer()

out:

1
2
3
2016 Audi A4
This car has 0 miles on it.
This car has 23 miles on it.

我们使用句点表示法来直接访问并设置汽车的属性odometer_reading。这行代码让python在实例my_new_car中找到属性odometer_reading,并将该属性的值设置为23
有时候需要像这样直接访问属性,但其他时候需要编写对属性进行更新的方法

2、通过方法修改属性的值

如果有替你更新属性的方法,将大有裨益。这样就无需直接访问属性,而可将值传递给一个方法,由它在内部进行更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Car:
"""一次模拟汽车的简单尝试"""

def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0

def get_descriptive_name(self):
"""返回整洁的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()

def read_odometer(self):
"""打印一条指出汽车里程的消息"""
print("This car has " + str(self.odometer_reading) + " miles on it.")

def update_odometer(self, mileage):
"""将里程表读数设置为指定的值"""
self.odometer_reading = mileage


my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())

my_new_car.update_odometer(23)
my_new_car.read_odometer()

对Car类所做的唯一修改是在class中添加了方法update_odometer()。这个方法接受一个里程值,并将其存储到self.odometer_reading中。然后调用update_odometer(),并向它提供了实参23

3、通过方法对属性的值进行递增

有时候需要将属性值递增特定的量,而不是将其设置为全新的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Car:
"""一次模拟汽车的简单尝试"""

def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0

def get_descriptive_name(self):
"""返回整洁的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()

def read_odometer(self):
"""打印一条指出汽车里程的消息"""
print("This car has " + str(self.odometer_reading) + " miles on it.")

def update_odometer(self, mileage):
"""将里程表读数设置为指定的值"""
self.odometer_reading = mileage

def increment_odometer(self, miles):
"""将里程表读书增加指定的量"""
self.odometer_reading += miles


my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())

my_new_car.update_odometer(23)
my_new_car.read_odometer()

my_new_car.increment_odometer(389)
my_new_car.read_odometer()

out
1
2
3
2016 Audi A4
This car has 23 miles on it.
This car has 412 miles on it.

9.3 继承

编写类时,并非总是要从空白开始。如果要编写的类时另一个现成类的特殊版本,可使用继承。一个类继承另一个类时,它将自动获得类一个类的所有属性和方法;原有的类称为父类,而新类称为子类。子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法

9.3.1 子类的方法init()

创建子类实例时,python首先需要完成的任务时给父类的所有属性赋值。为此,子类的方法__init__()需要父类施以援手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Car:
"""一次模拟汽车的简单尝试"""

def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0

def get_descriptive_name(self):
"""返回整洁的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()

def read_odometer(self):
"""打印一条指出汽车里程的消息"""
print("This car has " + str(self.odometer_reading) + " miles on it.")

def update_odometer(self, mileage):
"""将里程表读数设置为指定的值"""
self.odometer_reading = mileage

def increment_odometer(self, miles):
"""将里程表读书增加指定的量"""
self.odometer_reading += miles


class ElectricCar(Car):
"""电动汽车的独特之处"""

def __init__(self, make, model, year):
"""初始化父类的属性"""
super().__init__(make, model, year)


my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())

首先Car类的代码。创建子类时,父类必须包含在当前文件中,且位于子类前面。在29行,我们定义了子类ElectricCar。定义子类时,必须在括号内指定父类的名称。方法__init__()接受创建Car实例所需的信息。
34行的super()时一个特殊的函数,帮助python将父类和子类关联起来。这行代码让python调用ElectricCar的父类的方法__init__(),让ElectricCar实例包含父类的所有属性。父类也成为超类(superclass)

9.3.2 Python2.7中的继承

不用Python2.7

9.3.3 给子类定义属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Car:
"""一次模拟汽车的简单尝试"""

def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0

def get_descriptive_name(self):
"""返回整洁的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()

def read_odometer(self):
"""打印一条指出汽车里程的消息"""
print("This car has " + str(self.odometer_reading) + " miles on it.")

def update_odometer(self, mileage):
"""将里程表读数设置为指定的值"""
self.odometer_reading = mileage

def increment_odometer(self, miles):
"""将里程表读书增加指定的量"""
self.odometer_reading += miles


class ElectricCar(Car):
"""电动汽车的独特之处"""

def __init__(self, make, model, year):
"""初始化父类的属性"""
super().__init__(make, model, year)
self.battery_size = 70

def describe_battery(self):
"""打印一条描述电瓶容量的消息"""
print("This car has a " + str(self.battery_size) + "-kWh battery.")


my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battery()

直接在后面加属性和方法即可

9.3.4 重写父类的方法

对于父类的方法,只要它不符合子类模拟的实物的行为,都可对其进行重写。为此,可在子类中定义一个这样的方法,即它与重写的父类方法同名。这样,python将不会考虑这个父类方法,而只关注在子类中定义的相应的方法

1
2
3
4
5
6
7
8
9
10
11
class Car:

def aa(self):

print("a")

class Car_2(Car):

def aa(self):

print("b")

9.3.5 将实例用作属性

使用代码模拟实物时,你可能会发现自己给类添加的细节添加越来越多:属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分作为一个独立的类提取出来。你可以将大类拆分成多个协同工作的小类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Car:
"""一次模拟汽车的简单尝试"""

def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0

def get_descriptive_name(self):
"""返回整洁的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()

def read_odometer(self):
"""打印一条指出汽车里程的消息"""
print("This car has " + str(self.odometer_reading) + " miles on it.")

def update_odometer(self, mileage):
"""将里程表读数设置为指定的值"""
self.odometer_reading = mileage

def increment_odometer(self, miles):
"""将里程表读数增加指定的量"""
self.odometer_reading += miles


class Battery():
"""一次模拟电动汽车电瓶的简单尝试"""

def __init__(self, battery_size=70):
"""初始化电瓶的属性"""
self.battery_size = battery_size

def describe_battery(self):
"""打印一条描述电瓶容量的消息"""
print("This car has a " + str(self.battery_size) + "-kWh battery.")


class ElectricCar(Car):
"""电动汽车的独特之处"""

def __init__(self, make, model, year):
"""初始化父类的属性"""
super().__init__(make, model, year)
self.battery = Battery()


my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()

这看似做了很多额外的工作,但现在我们想多详细地描述电瓶都可以,且不会导致ElectricCar类混乱不堪

9.4 导入类

随着不断地给类增加功能功能,文件可能变得很长,即便你妥善地使用了继承亦如此。为遵循python的总体理念,应让文件尽可能整洁。为在这方面提供帮助,python允许你将类存储在模块中,然后在主程序中导入所需的模块。

9.4.1 导入单个类

这里我们导入刚刚我们创建的car.py中的Car类

1
2
3
4
5
6
7
from car import Car

my_new_car = Car('audi', 'a4', 2018)
print(my_new_car.get_descriptive_name())

my_new_car.odometer_reading = 23
my_new_car.read_odometer()

out:
1
2
2018 Audi A4
This car has 23 miles on it.

这里我们import了car模块导入了里面的Car类,这样我们就可以使用Car类了,就像它是在这个文件中定义的一样
导入类是一种有效的编程方式。如果在这个程序中包含了整个Car类,它该有多长啊!通过将这个类移到一个模块中,并导入该模块,依然可以使用其所有功能,但主程序文件变得整洁而易于阅读了。这还能让大部分逻辑储存在独立的文件中;确定类像你希望的那样工作后,就可以不管这些文件而专注主程序的高级逻辑了

9.4.2 在一个模块中存储多个类

虽然同一个模块中的类之间应存在某种相关性,但可根据需要在一个模块中存储任意数量的类

1
2
3
4
5
from car import ElectricCar

my_tesla = ElectricCar('tesla', 'model s', '2016')
my_tesla.battery.describe_battery()
print(my_tesla.get_descriptive_name())

out:
1
2
This car has a 70-kWh battery.
2016 Tesla Model S

9.4.3 从一个模块中导入多个类

可根据需要在程序文件中导入任意数量的类

1
2
3
4
5
6
7
from car import ElectricCar, Car

my_tesla = ElectricCar('tesla', 'model s', '2016')
my_tesla.battery.describe_battery()
my_beetle = Car('volswagen', 'beetle', 2016)
my_beetle.update_odometer(32)
my_beetle.read_odometer()

9.4.4 导入整个模块

这种导入方法很简单,代码也易于阅读,我们只需要用句点号访问即可

1
2
3
4
5
6
7
import car

my_tesla = car.ElectricCar('tesla', 'model s', '2016')
my_tesla.battery.describe_battery()
my_beetle = car.Car('volswagen', 'beetle', 2016)
my_beetle.update_odometer(32)
my_beetle.read_odometer()

9.4.5 导入模块中的所有类

要导入模块中的每一个类,可使用下面的语法
from module_name import *
不推荐这种导入方式,原因有二。首先,如果只要看一下文件开头的import语句,就能清楚地知道程序使用了那些类,但这种导入方式没有明确的指出你使用了模块中的哪些类
如果需要导入很多类,最好直接导入整个模块,然后通过点来访问

9.4.6 在一个模块中导入另一个模块

有时候,需要将类分散到多个模块中,以免模块太大,或在同一个模块中储存不相关的类。将类存储在多个模块中时,你可能会发现一个模块中的类依赖于另一个模块中的类。在这种去看看下,可在前一个模块中导入必要的类
my_eiectric_car.py

1
from car import Car, ElectricCar, Battery

导入了car中的三个类,然后在创建一个xx.py
1
2
3
import my_eiectric_car

a = my_eiectric_car.Car('')

可直接使用,但是在上一个py模块中不可以直接导入所有的类,可以from car import *,但不可以import car,可以导入所有类,但不能导入所有模块,不然后面会读取不到


10 文件和异常

至此,已经掌握了编写组织有序而易于使用的程序所需的基本技能,该考虑让程序目标更明确、用途更大了
学习处理文件和保存数据可让程序使用起来更容易:用户将能够选择输入什么样的数据,以及在什么时候输入

10.1 从文件中读取数据

文本文件可存储的数据量多得难以置信:天气数据、交通数据、社会经济数据、文学作品等。每当需要分析或修改存储在文件中的信息时,读取文件都很有用,对数据分析应用程序来说尤其如此
要使用文本文件中的信息,首先需要将信息读取到内存中。为此,可以一次性读取文件的全部内容,也可以以每次一行的方式逐步读取

10.1.1 读取整个文件

要读取文件,需要一个包含几行的文本的文件。下面首先来创建一个文件(必须创建在pycharm读取的那个文件夹),它包含精确到小数点后30位的圆周率值,且在小数点后每10位除都换行

1
2
3
3.1415926535
8979323846
2643383279

1
2
3
with open('pi_digits.txt') as file_object:
contents = file_object.read()
print(contents)

在这个程序中,第一行代码做了大量的工作。首先是函数open()。要以任何方式使用文件——哪怕仅仅是打印其内容,都得先打开文件,这样才能访问它。函数open()接受一个参数:要打开的文件的名称。python在当前执行的文件所在的目录中查找指定的文件
关键词with在不需要访问文件后将其关闭。在这个程序中,注意到我们调用了open(),但没有调用close();我们也可以调用open()heclose()来打开和关闭文件,但这样做时,如果程序存在bug,导致close()语句未执行,文件将不会关闭。这看似问不足道,但未妥善地关闭文件可能会导致数据丢失或受损。如果在程序中过早地调用close(),会发现需要使用文件时它已关闭(无法访问),这会导致更多哦的错误。并非在任何情况下都能轻松确定关闭的恰当时机,但通过使用前面所示的结构,可让python去确定:我们只管打开文件,并在需要时使用它,python自会在合适的时候自动将其关闭
有了表示pi_digits.txt的文件对象后,我们使用方法read()读取这个文件的全部内容,并将其作为一个长长的字符串存储在变量contents中。这样,通过打印contents的值,就可可以将这个文本文件的全部内容显示出来:
1
2
3
4
3.1415926535
8979323846
2643383279


因为read()到达文件末尾时返回一个空字符串,而将这个空字符串显示出来时就是一个空行。要删除末尾的空行,可在print语句中使用rstrip()
1
2
3
with open('pi_digits.txt') as file_object:
contents = file_object.read()
print(contents.rstrip())

out:
1
2
3
3.1415926535
8979323846
2643383279

10.1.2 文件路径

当将类似的pi_digits.txt这样的简单文件名传递给函数open()时,python将在当前执行的文件所在的目录中查找文件
根据组织文件的方式,有时可能要打开不在程序文件所属目录中的文件。

1
2
3
4
file_path = 'D:\PyCharm项目/pi_digitst.txt'
with open(file_path) as file_object:
context = file_object.read()
print(context.rstrip())

这种路径叫绝对路径

10.1.3 逐行读取

读取文件时,常常需要检查其中的每一行:我们可能要在文件中查找特定的信息,或者要以某种方式修改文件中的文本

1
2
3
4
5
file_path = 'D:\PyCharm项目/pi_digitst.txt'

with open(file_path) as file_object:
for line in file_object:
print(line)

out:
1
2
3
4
5
6
3.1415926535

8979323846

2643383279


我们可以直接通过for循环来读取文件,因为会多出一行空白字符串,我们可以通过.rstrip()去除掉右边的空字符

10.1.4 创建一个包含文件各行内容的列表

要使用关键词with时,open()返回的文件对象只在with代码块内可用。如果要在with代码块外访问文件的内容,科在with代码块内将文件的隔行储存在一个列表中,并在with代码块外使用该列表:我们可以立即处理文件的各个部分,也可以推迟到程序后面再处理

1
2
3
4
5
6
7
file_path = 'D:\PyCharm项目/pi_digitst.txt'

with open(file_path) as file_object:
lines = file_object.readlines()
print(lines)
for line in lines:
print(line.rstrip())

第四行处的readlines()从文件中读取每一行,并将其储存再一个列表中;接下来,该列表被存储到变量lines中;在with代码块外,我们依然可以使用这个变量

10.1.5 使用文件的内容

将文件读取到内存后,就可以以任何方式使用这些数据了

1
2
3
4
5
6
7
8
9
10
11
12
13
file_path = 'D:\PyCharm项目/pi_digitst.txt'

with open(file_path) as file_object:
lines = file_object.readlines()
print(lines)
for line in lines:
print(line.rstrip())

string = ''
for line_1 in lines:
string += line_1.strip()
print(string)
print(len(string))

out:
1
2
3
4
5
6
['3.1415926535\n', '  8979323846\n', '  2643383279']
3.1415926535
8979323846
2643383279
3.141592653589793238462643383279
32

10.2 写入文件

保存数据的最简单的方式之一是将其写入到文件中。通过将输出写入文件,即便关闭包含程序输出的终端窗口,这些输出也依然存在:我们可以在程序结束运行后查看这些输出,可与别人分享输出文件,还可以编写程序来将这些输出读取到内存中并进行处理

10.2.1 写入空文件

要将文本写入文件,我们在调用open()时需要提供另一个实参,告诉Python我们要写入打开的文件

1
2
3
4
filename = 'programming.txt'

with open(filename, 'w') as file_object:
file_object.write("I love programming")

在这个示例中,调用了open()时提供了两个实参,第一个实参也是要打开的文件的名称;第二个实参'w'告诉python,我们要以写入模式打开这个文件。打开文件时,可指定读取模式(‘r’)、写入模式(‘w’)、附加模式(‘a’)或让我们能够读取和写入的模式(‘r+’)。如果我们省略了模式实参,python将以默认的制度模式打开文件
如果我们要写入的文件不存在,函数open()将自动创建它。然而,以写入(‘w’)模式打开文件时千万要小心,因为如果指定的文件已经存在,python将在返回文件对象前清空该文件
在第四行我们使用文件对象的方法write()将一个字符串写入文件。这个程序没有终端输出出,但如果我们打开文件,将看到:
1
I love programming

注意 python只能将字符串写入文本文件。要将数值数据存储到文本文件中,必须先使用函数str()将其转换成字符串格式

10.2.2 写入多行

函数write()不会在我们写入的文本末尾添加换行符,因此如果我们写入多行没有指定换行符,文件看起来可能不会是我们希望看见的那样

1
2
3
4
5
filename = 'programming.txt'

with open(filename, 'w') as file_object:
file_object.write("I love programming")
file_object.write("I love creating new games.")

programming.txt:
1
I love programmingI love creating new games.

要让每个字符串单独占一行,需要在write()语句中包含换行符
1
2
3
4
5
filename = 'programming.txt'

with open(filename, 'w') as file_object:
file_object.write("I love programming\n")
file_object.write("I love creating new games.")

programming.txt:
1
2
I love programming
I love creating new games.

10.2.3 附加到文件

如果要给文件添加内容,而不是覆盖原有的内容,可以附加模式打开文件。我们以附加模式打开文件时,python不会在返回文件对象前清空文件,而我们写入到文件的行都将添加到文件末尾。如果指定的文件不存在,python将为我们创建一个空文件

1
2
3
4
5
filename = 'programming.txt'

with open(filename, 'a') as file_object:
file_object.write("I also love finding meaning in large datasets.\n")
file_object.write("I love creating apps that can run in a browser.\n")

programming.txt:
1
2
3
4
5
I love programming
I love creating new games.
I also love finding meaning in large datasets.
I love creating apps that can run in a browser.

10.3 异常

python使用被称为异常的特殊对象来管理程序执行期间发生的错误。每当发生让python不知所措的错误时,它都会创建一个异常对象。如果我们编写了处理该异常的代码,程序将继续运行;如果我们未对异常进行处理,程序将停止,并显示一个trackback,其中包含有关异常的报告
异常时使用try-except代码块处理的。try-except代码块让python执行指定的操作,同时告诉python发生异常时怎么办。使用了try-except代码块时,即便出现异常,程序也将继续运行:显示你友好的错误消息,而不是令用户迷惑的trackback

10.3.1 处理ZeroDivisionError异常

print(5/0)
out:

1
2
3
4
Traceback (most recent call last):
File "D:/PyCharm项目/Python编程从入门到实践/模块/__init__.py", line 1, in <module>
print(5/0)
ZeroDivisionError: division by zero

在上述traceback中,ZeroDivisionError是一个异常对象。python无法按照我们的要求做时,就会创建这种对象。在这种情况下,python将停止运行程序,并指出引发了那种异常,而我们可根据这些信息对程序进行修改

10.3.2 使用try-except代码块

当我们任务可能发生错误时,可编写一个try-except代码块来处理可能引发的异常

1
2
3
4
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")

我们将导致错误的代码行print(5/0)放在了一个try代码块中。如果try代码块中的代码运行起来没有问题,python将跳过except代码块;如果try代码块中的代码导致了错误,python将查找这样的except代码块,并运行其中的代码,即其中指定的错误与引发的错误相同
在这个示例代码中,try代码快引发的ZeroDivisionError异常,因此python指出了该如何解决问题的except代码块,并运行其中的代码。这样,用户看到的是一条友好的错误信息,而不是traceback:
You can't divide by zero!

10.3.3 使用异常避免崩溃

发生错误时,如果程序还有工作没有完成,妥善地处理错误就尤其重要。这种情况经常会出现在要求用户提供输入的程序中;如果程序能够妥善地处理无效输入,就能再提示用户提供有效的输入而不是崩溃

1
2
3
4
5
6
7
8
9
10
11
12
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")

while True:
fist_number = input("\nFirst number: ")
if fist_number == 'q':
break
second_number = input("Second number: ")
if second_number == 'q':
break
answer = int(fist_number) / int(second_number)
print(answer)

当输入sencond_number = 0时,则会引起程序崩溃
1
2
3
4
5
6
7
8
9
Give me two numbers, and I'll divide them.
Enter 'q' to quit.

First number: 5
Second number: 0
Traceback (most recent call last):
File "D:/PyCharm项目/Python编程从入门到实践/模块/__init__.py", line 11, in <module>
answer = int(fist_number) / int(second_number)
ZeroDivisionError: division by zero

10.3.4 else代码块

通过将可能引发错误的代码放在try-except代码块中,可提高这个程序的低于错误的能力。错误执行除法运算的代码行导致的,因此我们需要将它放到try-except代码块中。这个示例还包含一个else代码块;依赖于try代码块成功执行的代码都应放到else代码块中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")

while True:
fist_number = input("\nFirst number: ")
if fist_number == 'q':
break
second_number = input("Second number: ")
try:
answer = int(fist_number) / int(second_number)
except ZeroDivisionError:
print("You can't divide by 0!")
else:
print(answer)

10.3.5 处理FileNotFoundError 异常

使用文件时,一种常见的问题时找不到文件:我们要查找的文件可能在其他地方、文件名可能不确定或者这个文件根本就不存在。对于所有这种情形,都可使用try-except代码块以直观的方式进行处理

1
2
3
4
5
6
7
filename = 'alice_txt'

try:
with open(filename) as f_obj:
context = f_obj.read()
except FileNotFoundError:
print("Sorry, the file " + filename + " does not exist.")

out:
Sorry, the file alice_txt does not exist.

10.4 存储数据

很多程序都要求用户输入某种信息,如果让用户存储游戏首选项或提供要可视化的数据。不管专注的是什么,程序都把用户提供的信息存储在列表和字典等数据结构中。用户关闭程序时,我们几乎总是要保存用户提供的信息;一种简单的方式是使用模块json来存储数据
模块json让我们能够简单的python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据。我们还可以使用json在python程序之间分享数据。更重要的是,JSON数据格式并非python专用的,这让我们能够将以JSON格式存储的数据与使用其他编程语言的人分享

注意 JSON(JavaScript Object Notation)格式最初是为JavaScript开发的,但随后成立一种常见的格式,背包括python在内的众多语言采用

10.4.1 使用json.dump()和json.load()

我们来编写一个存储一组数字的简短程序,再编写一个将这些数字读取到内存中的程序。第一个程序将使用json.dump()来存储这组数字,而第二个程序将使用json.load()
函数json.dump()接受两个实参:要存储的数据以及可用于存储数据的文件对象

1
2
3
4
5
6
7
import json

numbers = [2, 3, 5, 7, 11, 13]

filename = 'numbers.json'
with open(filename, 'w') as f_obj:
json.dump(numbers, f_obj)

我们先导入模块json,再创建一个数字列表。再第四行,我们指定了要将该数字列表存储到其中文件的名称。通常使用文件拓展名.json来指出文件存储数据为JSON格式。接下来,我们以写入模式打开这个文件,让json能够将数据写入其中。在json.dump()将数字列表存储在文件number.json
这个程序没有输出,但我们可以打开文件number.json,看看其内容,数据的存储格式与python中一样
[2, 3, 5, 7, 11, 13]
下面再编写一个程序,使用json.load()将这个列表读取到内存中:
1
2
3
4
5
6
7
import json

filename = 'numbers.json'
with open(filename,) as f_obj:
numbers = json.load(f_obj)

print(numbers)

在第三行,我们确保读取的是前面写入的文件。这次我们读取方式是打开这个文件,因为python只需读取这个文件。在第五行我们使用函数json.load()加载存储在numbers.json中的信息,并将其存储到变量numbers中
[2, 3, 5, 7, 11, 13]
这是一种在程序之间共享数据的简单方式

10.4.2 保存和读取用户生成的数据

对于用户生成的数据,使用json保存它们大有裨益,因为如果不以某种方式进行存储,等程序停止运行时用户的信息将丢失。

1
2
3
4
5
6
7
8
import json

username = input("What is your name? ")

filename = 'username.json'
with open(filename, 'w') as f_obj:
json.dump(username, f_obj)
print("We'll remember you when you come back, " + username + "!")

这里我们创建了一个username.json来存储用户的姓名信息,防止丢失
1
2
3
4
5
6
7
import json

filename = 'username.json'

with open(filename) as f_obj:
username = json.load(f_obj)
print("Welcome back , " + username + "!")

我们将这两个程序合并成一个程序
1
2
3
4
5
6
7
8
9
10
11
12
13
import json

filename = 'username.json'
try:
with open(filename) as f_obj:
username = json.load(f_obj)
except FileNotFoundError:
username = input("What is your name? ")
with open(filename, 'w') as f_obj:
json.dump(username, f_obj)
print("We'll remember you when you come back, " + username + "!")
else:
print("Welcome back , " + username + "!")

10.4.3 重构

我们经常会遇到这样的情况:代码能够正确地运行,但可做进一步的改进——将代码划分为一系列完成具体工作的函数。这样的过程被称为重构。重构让代码更清晰、更易于理解、更容易扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import json


def greet_user():
"""问候用户,并指出其名字"""
filename = 'username.json'
try:
with open(filename) as f_obj:
username = json.load(f_obj)
except FileNotFoundError:
username = input("What is your name? ")
with open(filename, 'w') as f_obj:
json.dump(username, f_obj)
print("We'll remember you when you come back, " + username + "!")
else:
print("Welcome back , " + username + "!")


greet_user()

考虑到现在使用了一个函数,我们删除了注释,转而使用一个文档字符串来指出程序是做什么的。这个程序更清晰些,但函数greet_user()所做的不仅仅是问候用户,还在存储了用户名时获取它,而在没有存储名时存储用户名时提示用户输入一个
下面来重构greet_user(),让他不执行这么多任务。为此,我们首先将获取存储的用户名的代码移到另一个函数中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import json


def get_stored_username():
"""如果存储了与用户名,就获取它"""
filename = 'username.json'
try:
with open(filename) as f_obj:
username = json.load(f_obj)
except FileNotFoundError:
return None
else:
return username


def greet_user():
"""问候用户,并指出其名字"""
username = get_stored_username()
if username:
print("Welcome back, " + username + "!")
else:
username = input("What is your name? ")
filename = 'username.json'
with open(filename, 'w') as f_obj:
json.dump(username, f_obj)
print("We'll remember you when you come back, " + username + "!")


greet_user()

要编写出清晰而易于维护和扩展的代码,这种划分工作必不可少


11 测试代码

编写函数或类时,还可为其编写测试。通过测试,可确定代码面对各种输入都能够按要求的那样工作
在本章中,我们将学习如何使用python模块unittest中的工具来测试代码

11.1 测试函数

要学习测试,得有要测试的代码
name_function.py

1
2
3
4
def get_formatted_name(first, last):
"""生成整洁的姓名"""
full_name = first + ' ' + last
return full_name.title()

首先编写一个name_function.py模块
1
2
3
4
5
6
7
8
9
10
11
12
13
from name_function import get_formatted_name

print("Enter 'q' at any time to quit.")
while True:
first = input("\nPlease give me a first name: ")
if first == 'q':
break
last = input("Please give me a last name: ")
if last == 'q':
break

formatted_name = get_formatted_name(first, last)
print("\tNeatly formatted name: " + formatted_name + '.')

11.1.1 单元测试和测试用例

python标准库中的模块unittest提供了代码测试工具。单元测试用于核实函数的某个方面没有问题;测试用例是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求。良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情形的测试。全覆盖式测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。对于大型项目,要实现全覆盖可能很难。通常,最初只要针对代码的重要行为编写测试即可,等项目被广泛使用时再考虑全覆盖

11.1.2 可通过的测试

创建测试用例的语法需要一段时间才能习惯,但测试用例创建后,再添加针对函数的单元测试就很简单了。要为函数编写测试用例,可先导入模块unittest以及要测试的函数,再创建一个继承unittest.TestCase的类,并编写一系列方法对函数行为的不同方面进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import unittest
from name_function import get_formatted_name


class NameTestCase(unittest.TestCase):
"""测试name_function.py"""

def test_first_last_name(self):
"""能够正确处理像Janis Joplin这样的姓名吗?"""
format_name = get_formatted_name('janis', 'joplin')
self.assertEqual(format_name, 'janis Joplin')


unittest.main()

首先我们导入了模块unittest和要测试的函数get_formatted_name()。再第五行,我们创建了一个名为NamesTestCase的类,用于包含一系列针对get_formatted_name()的单元测试。我们可以随便给类命名,但最好让它看起来与要测试的函数相关,并包含字样Test。这个类必须继承unittest.TestCase类,这样python才知道如何运行我们编写的测试
NameTestCase只包含一个方法,用于测试get_formattted_name()的一个方面。我们将这个方法命名为test_first_last_name(),因为我们要核实的是只有名和姓的姓名能否被正确地格式化。我们运行test_name_function.py时,所有以test_打头的犯法都将自动运行
self.asserEqual()行,我们使用了unittest类最有用的功能之一:一个断言方法。断言方法用来核实得到的结果是否与期望的结果一致。到这里,我们知道get_formatted_name()应返回这样的姓名,即名和姓首字母大写,且它们之间隔有一个空格,因此我们期望的formatted_name的值为Janis Joplin。为检查是否确实如此,我们调用unittest的方法assertEqual(),并向它传递formatted_name和’Janis Joplin’。代码行self.assertEqual(formatted_name, 'Janis Joplin')的意思是说,“将formatted_name的值同字符串’Janis Joplin’进行比较”
out:
1
2
3
4
5
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

第一行句点表明有一个测试通过了。接下来的一行指出python运行了一个测试消耗了不到0.001秒。最后的ok表明该测试用例中的所有单元都通过了

11.1.3 不能通过的测试

name_function.py

1
2
3
4
def get_formatted_name(first, middle, last):
"""生成整洁的姓名"""
full_name = first + ' ' + middle + ' ' + last
return full_name.title()

out:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
E
======================================================================
ERROR: test_first_last_name (__main__.NameTestCase)
能够正确处理像Janis Joplin这样的姓名吗?
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:/PyCharm项目/Python编程从入门到实践/模块/__init__.py", line 10, in test_first_last_name
format_name = get_formatted_name('janis', 'joplin')
TypeError: get_formatted_name() missing 1 required positional argument: 'last'

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

其中包含信息很多,因为测试未通过时,需要让我们知道的事情可能有很多。第一行输出只有一个E,它指出测试用例中有一个单元测试导致了错误。接下来,我们看到NameTestCase中的test_first_last_name导致了错误。测试用例包含众多单元测试时,知道那个测试未通过至关重要。在下面我们看到了一个标准的tracebacck,它指出函数调用get_formatted_name('janis', 'joplin')有问题,因为它缺少一个必不可少的位置实参

11.2 测试类

11.2.1 各种断言方法

python在unittest.TestCase类中提供了很多断言方法。前面说过,断言方法检查你认为应该满足的条件是否确实满足。如果条件确实满足,我们对程序行为的假设就得到了确认,我们就可以确信其中没有错误
下面六个断言方法

1
2
3
4
5
6
7
8
9
10
——————————————————————————————————————————————————————————————————————————————
方法 用途
------------------------------------------------------------------------------
assertEqual(a, b) 核实 a == b
assertNotEqual(a, b) 核实 a != b
assertTrue(x) 核实x为True
assertFalse(x) 核实x为False
assertIn(item, list) 核实item在list
asserNotIn(item, list) 核实item不在list
——————————————————————————————————————————————————————————————————————————————

11.2.2 一个要测试的类

类的测试与函数的测试相思——我们所做的大部分工作都是测试类中方法的行为,但存在一些不同之处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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_result(self):
"""显示收集到的所有答卷"""
print("Survey results: ")
for response in self.responses:
print("- " + response)

这个类首先存储一个你指定的调查问题,并创建一个空列表,用于存储答案。这个类包含打印调查问题的方法、在答案列表中添加答案的方法以及存储在列表中的答案都打印查出来的方法
为证明AnonymousSurvey类能够正确地工作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from survey import AnonymousSurvey

# 定义一个问题,并创建一个表示调查AnonymousSurvey对象
question = "What language did you first learn to speak? "
my_survey = AnonymousSurvey(question)

# 显示问题并存储答案
my_survey.show_question()
print("Enter 'q' at any time to quit.\n")
while True:
response = input("Language: ")
if response == 'q':
break
my_survey.store_response(response)

# 显示调查结果
print("\nThank you to everyone who participated in the survey!")
my_survey.show_result()

out:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
What language did you first learn to speak? 
Enter 'q' at any time to quit.

Language: C
Language: Java
Language: Python
Language: PhP
Language: q

Thank you to everyone who participated in the survey!
Survey results:
- C
- Java
- Python
- PhP

11.2.3 测试AnonymousSurvey类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import unittest
from survey import AnonymousSurvey


class TestAnonymousSurvey(unittest.TestCase):
"""针对AnonymousSurvey类的测试"""

def test_store_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)


unittest.main()

out:

1
2
3
4
5
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

11.2.4 方法setUp()

在前面的test_survey.py中,我们在每个测试方法中创建了一个AnonymousSurvey实例,并在每个方法中都创建了答案。unittest.TestCase类包含方法setUp(),python将先运行它,再运行各个以test_打头的方法。这样我们再白那些的每个测试方法中都可使用在方法setUp()中创建的对象