python编程从入门到实践

第二章 变量和简单数据类型

字符串

常用方法

1
2
3
title() #首字母大写
upper() #字母全部大写
lower() #字母全部小写

f字符串

要在字符串中插入变量的值,可在前引号前加上字母 f,再将要插入的变量放在花括号内。

1
2
3
4
first_name = "ada" 
last_name = "lovelace"
full_name = f"{first_name} {last_name}"
print(full_name)

format()方法

1
full_name = "{} {}".format(first_name, last_name)

删除空白

1
2
3
rstrip() #删除字符串末尾的空白
lstrip() #删除字符串开头的空白
strip() #删除字符串两边的空白

下划线

书写很大的数时,可使用下划线将其中的数字分组,使其更清晰易读

1
universe_age = 14_000_000_000 #Python 不会打印其中的下划线

同时给多个变量赋值

1
x, y, z = 0, 0, 0 

常量

常量命名时字母全部大写

Python之禅

1
import this

第三章 列表简介

列表简介

列表由一系列按特定顺序排列的元素组成。

在 Python 中,用方括号([])表示列表,并用逗号分隔其中的元素。

访问列表

1
2
bicycles = ['trek', 'cannondale', 'redline', 'specialized'] 
print(bicycles[0])

索引从0开始

没什么好解释的,懂得都懂 :joy:

修改、添加和删除元素

修改

1
2
3
4
motorcycles = ['honda', 'yamaha', 'suzuki'] 
print(motorcycles)
motorcycles[0] = 'ducati'
print(motorcycles)

添加

append:在末尾插入

1
motorcycles.append('ducati') 

insert():在任意位置插入

1
2
3
motorcycles = ['honda', 'yamaha', 'suzuki'] 
motorcycles.insert(0, 'ducati')
print(motorcycles)

删除

del:删除任意位置元素

1
2
3
4
motorcycles = ['honda', 'yamaha', 'suzuki'] 
print(motorcycles)
del motorcycles[1]
print(motorcycles)

pop():删除列表末尾的元素,并让你能够接着使用它。

1
2
3
4
5
motorcycles = ['honda', 'yamaha', 'suzuki'] 
print(motorcycles)
popped_motorcycle = motorcycles.pop()
print(motorcycles)
print(popped_motorcycle)

pop()也可以删除任意位置元素

1
2
3
motorcycles = ['honda', 'yamaha', 'suzuki'] 
first_owned = motorcycles.pop(0)
print(f"The first motorcycle I owned was a {first_owned.title()}.")

remove:根据值删除元素 (只删除第一个指定的值)

1
2
3
4
motorcycles = ['honda', 'yamaha', 'suzuki', 'ducati'] 
print(motorcycles)
motorcycles.remove('ducati')
print(motorcycles)

组织列表

永久排序

sort:按字母顺序排列
1
2
3
cars = ['bmw', 'audi', 'toyota', 'subaru'] 
cars.sort()
print(cars)
sort:按字母顺序相反顺序排列
1
2
3
cars = ['bmw', 'audi', 'toyota', 'subaru'] 
cars.sort(reverse=True)
print(cars)

临时排序

sorted:调用函数 sorted()后,列表元素的排列顺序并没有变。如果要按与字母顺序 相反的顺序显示列表,也可向函数 sorted()传递参数 reverse=True。

倒着打印列表

reverse():反转列表元素的排列顺序

1
2
3
4
cars = ['bmw', 'audi', 'toyota', 'subaru'] 
print(cars)
cars.reverse()
print(cars)

确定列表的长度

len():可快速获悉列表的长度

1
2
cars = ['bmw', 'audi', 'toyota', 'subaru'] 
len(cars)

使用列表时避免索引错误

索引从0开始

索引-1 总是返回最后一个列表元素

第四章 操作列表

遍历整个列表

for循环

1
2
3
4
5
magicians = ['alice', 'david', 'carolina'] 
for magician in magicians:
print(f"{magician.title()}, that was a great trick!")
print(f"I can't wait to see your next trick, {magician.title()}.\n")
print("Thank you, everyone. That was a great magic show!")

避免缩进错误

Python 根据缩进来判断代码行与前一个代码行的关系。

忘记缩进

忘记缩进额外的代码行

不必要的缩进

循环后不必要的缩进

遗漏了冒号

for 语句末尾的冒号告诉 Python,下一行是循环的第一行。

创建数值列表

使用函数 range()

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

只会打印从1到4的数,到第二个值时停止

使用 range()创建数字列表

要创建数字列表,可使用函数 list()将 range()的结果直接转换为列表。如果将 range()作 为 list()的参数,输出将是一个数字列表。

1
2
numbers = list(range(1, 6)) 
print(numbers)
1
2
even_numbers = list(range(2, 11, 2)) #从 2 开始数,然后不断加 2,直到达到或超过终值
print(even_numbers)

**表示乘方运算

1
square = value ** 2 #相当于平方

简单统计计算

1
2
3
min()
max()
sum()

列表解析

列表解析将 for 循环和创建新元素的代码合并成一行,并自动附加新元素。

1
2
3
squares = [value**2 for value in range(1, 11)] 
print(squares)
#结果是:[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

使用列表的一部分

切片

要创建切片,可指定要使用的第一个元素和最后一个元素的索引。

与函数 range()一样, Python 在到达第二个索引之前的元素后停止。

1
2
players = ['charles', 'martina', 'michael', 'florence', 'eli'] 
print(players[0:3]) #结果是三个

如果没有指定第一个索引,Python 将自动从列表开头开始。

1
2
players = ['charles', 'martina', 'michael', 'florence', 'eli'] 
print(players[:4])

让切片终止于列表末尾。

1
2
players = ['charles', 'martina', 'michael', 'florence', 'eli'] 
print(players[2:]) #返回从第三个元素到列表末尾的所有元素

负数索引返回离列表末尾相应距离的元素,因此你可以输出列表末尾的任意切片。例如,如果要 输出名单上的最后三名队员,可使用切片 players[-3:]

1
2
players = ['charles', 'martina', 'michael', 'florence', 'eli'] 
print(players[-3:])

可在表示切片的方括号内指定第三个值。这个值告诉 Python 在指定范围内每隔多少元素 提取一个。

遍历切片

如果要遍历列表的部分元素,可在 for 循环中使用切片。

复制列表

要复制列表,可创建一个包含整个列表的切片,方法是同时省略起始索引和终止索引([:])。 这让 Python 创建一个始于第一个元素、终止于最后一个元素的切片,即整个列表的副本。

1
2
my_foods = ['pizza', 'falafel', 'carrot cake'] 
friend_foods = my_foods[:]
1
2
3
4
5
my_foods = ['pizza', 'falafel', 'carrot cake'] 
# 这行不通:
 friend_foods = my_foods
my_foods.append('cannoli')
friend_foods.append('ice cream')

这里将 my_foods 赋给 friend_foods,而不是将 my_foods 的副本赋给 friend_foods(见)。 这种语法实际上是让 Python 将新变量 friend_foods 关联到已与 my_foods 相关联的列表,因此这 两个变量指向同一个列表。

元组

Python 将不能修改的值称为不可变的,而不可变的列表被称为元组。

定义元组

元组看起来很像列表,但使用圆括号而非中括号来标识。

1
dimensions = (200, 50)

遍历元组中的所有值

采用for循环

修改元组变量

虽然不能修改元组的元素,但可以给存储元组的变量赋值。

1
2
3
4
5
6
7
8
9
dimensions = (200, 50) 
print("Original dimensions:")
for dimension in dimensions:
print(dimension)

dimensions = (400, 100)
print("\nModified dimensions:")
for dimension in dimensions:
print(dimension)

设置代码格式

缩进

PEP 8 建议每级缩进都使用四个空格。

行长

很多 Python 程序员建议每行不超过 80 字符。

在大多数编辑器中,可以设置一个视觉标志(通常是一条竖线),让你知道不能越过的界线在什么地方

空行

将程序的不同部分分开,可使用空行。

第五章 if语句

简单示例

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())

条件测试

每条 if 语句的核心都是一个值为 True 或 False 的表达式,这种表达式称为条件测试

检查是否相等

== 使用两个等号`

检查是否相等时忽略大小写

1
2
car = 'Audi'
car.lower() == 'audi'

检查是否不相等

!= 使用!=

检查多个条件

使用 and 检查多个条件
1
2
3
age_0 = 22
age_1 = 18
age_0 >= 21 and age_1 >= 21
使用 or 检查多个条件
1
2
3
age_0 = 22
age_1 = 18
age_0 >= 21 or age_1 >= 21

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

使用关键字in

1
2
requested_toppings = ['mushrooms', 'onions', 'pineapple']
'mushrooms' in requested_toppings

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

使用关键字not in

1
2
banned_users = ['andrew', 'carolina', 'david'] 
user = 'marie'

if 语句

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!")

if-elif-else结构

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

也可以省略else代码块

第六章 字典

一个简单的字典

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

使用字典

在 Python 中,字典是一系列键值对。每个键都与一个值相关联,你可使用键来访问相关联 的值。

键值对是两个相关联的值。指定键时,Python 将返回与之相关联的值。键和值之间用冒号分 隔,而键值对之间用逗号分隔。

访问字典中的值

1
2
alien_0 = {'color': 'green'} 
print(alien_0['color'])

添加键值对

字典是一种动态结构,可随时在其中添加键值对。要添加键值对,可依次指定字典名、用方 括号括起的键和相关联的值。

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

先创建一个空字典

1
alien_0 = {}

修改字典中的值

1
2
alien_0 = {'color': 'green'} 
alien_0['color'] = 'yellow'

删除键值对

可使用 del 语句将相应的键值对彻底删除

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

使用 get()来访问值

可使用方法 get()在指定的键不存在时返回一个默认值,从而避免这样的错误

方法 get()的第一个参数用于指定键,是必不可少的;第二个参数为指定的键不存在时要返 回的值,是可选的

1
2
3
alien_0 = {'color': 'green', 'speed': 'slow'} 
point_value = alien_0.get('points', 'No point value assigned.')
print(point_value)

如果字典中有键’points’,将获得与之相关联的值;如果没有,将获得指定的默认值。虽然这里没有键’points’,但将获得一条清晰的消息,不会引发错误

调用 get()时,如果没有指定第二个参数且指定的键不存在,Python 将返回值 None。这 个特殊值表示没有相应的值。None 并非错误,而是一个表示所需值不存在的特殊值

遍历字典

遍历所有键值对

1
2
3
4
5
6
7
8
user_0 = { 
'username': 'efermi',
'first': 'enrico',
'last': 'fermi',
}
for key, value in user_0.items():
print(f"\nKey: {key}")
print(f"Value: {value}")

方法 items()返回一个键值对列表。

遍历字典中的所有键

在不需要使用字典中的值时,方法 keys()很有用。

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

for name in favorite_languages.keys():
print(name.title())

遍历字典时,会默认遍历所有的键

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

从 Python 3.7 起,遍历字典时将按插入的顺序返回其中的元素。

要以特定顺序返回元素,一种办法是在 for 循环中对返回的键进行排序。使用函数sorted()

遍历字典中的所有值

对字典包含的值感兴趣,可使用方法 values()来返回一个值列表,不包含任何键。

为剔除重复项,可使用集合(set)。 集合中的每个元素都必须是独一无二的。

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

集合和字典很容易混淆,因为它们都是用一对花括号定义的。当花括号内没有键值对时, 定义的很可能是集合。不同于列表和字典,集合不会以特定的顺序存储元素。

嵌套

有时候,需要将一系列字典存储在列表中,或将列表作为值存储在字典中,这称为嵌套。

在列表中储存字典

1
2
3
4
5
6
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]
for alien in aliens:
print(alien)

在字典中存储列表

1
2
3
4
5
6
7
8
9
# 存储所点比萨的信息。
pizza = {
'crust': 'thick',
'toppings': ['mushrooms', 'extra cheese'],
}
# 概述所点的比萨。
print(f"You ordered a {pizza['crust']}-crust pizza " "with the following toppings:")
for topping in pizza['toppings']:
print("\t" + topping)

第七章 用户输入和 while 循环

函数 input()的工作原理

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

函数 input()接受一个参数——要向用户显示的提示(prompt)或说明,让用户知道该如何做。

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

使用函数 input()时,Python将用户输入解读为字符串。

函数 int()将数的字符 串表示转换为数值表示

1
2
3
4
5
6
height = input("How tall are you, in inches? ") 
height = int(height)
if height >= 48:
print("\nYou're tall enough to ride!")
else:
print("\nYou'll be able to ride when you're a little older.")

求模运算符

求模运算符(%)是个很有用的工具,它将两个数相除并返回余数。

while循环

1
2
3
4
5
6
7
8
9
prompt = "\nTell me something, and I will repeat it back to you:" 
prompt += "\nEnter 'quit' to end the program. "
message = ""
while message != 'quit':
message = input(prompt)

if message != 'quit':
print(message)

使用break退出循环

1
2
3
4
5
6
7
8
prompt = "\nPlease enter the name of a city you have visited:" 
prompt += "\n(Enter 'quit' when you are finished.) "
while True:
city = input(prompt)
if city == 'quit':
break
else:
print(f"I'd love to go to {city.title()}!")

在循环中使用continue

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

print(current_number)
#只打印1到10的奇数

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

通过将 while 循环 同列表和字典结合起来使用,可收集、存储并组织大量输入,供以后查看和显示。

在列表之间移动元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 首先,创建一个待验证用户列表
users.py # 和一个用于存储已验证用户的空列表。
unconfirmed_users = ['alice', 'brian', 'candace']
confirmed_users = []
# 验证每个用户,直到没有未验证用户为止。
# 将每个经过验证的用户都移到已验证用户列表中。
while unconfirmed_users:
current_user = unconfirmed_users.pop()
print(f"Verifying user: {current_user.title()}")
confirmed_users.append(current_user)

# 显示所有已验证的用户。
print("\nThe following users have been confirmed:")
for confirmed_user in confirmed_users:
print(confirmed_user.title())

删除为特定值的所有列表元素

1
2
3
4
5
6
pets = ['dog', 'cat', 'dog', 'goldfish', 'cat', 'rabbit', 'cat'] 
print(pets)
while 'cat' in pets:
pets.remove('cat')

print(pets)

使用用户输入来填充字典

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

#设置一个标志,指出调查是否继续。
polling_active = True

while polling_active:
# 提示输入被调查者的名字和回答。
name = input("\nWhat is your name? ")
response = input("Which mountain would you like to climb someday? ")
# 将回答存储在字典中。
responses[name] = response
# 看看是否还有人要参与调查。
repeat = input("Would you like to let another person respond? (yes/ no) ")
if repeat == 'no':
polling_active = False
# 调查结束,显示结果。
print("\n--- Poll Results ---")
for name, response in responses.items():
print(f"{name} would like to climb {response}.")

第八章 函数

定义函数

1
2
3
4
5
def greet_user(): 
"""显示简单的问候语。"""
print("Hello!")

greet_user()

向函数传递信息

1
2
3
4
5
def greet_user(username): 
"""显示简单的问候语。"""
print(f"Hello, {username.title()}!")

greet_user('jesse')

形参和实参

形参(parameter),即函数完成工作所需的信息。

实参(argument),即调用函数时传递给函数的信息。

传递实参

可使用位置实参,这要求实参的顺序与形参的顺序相同;也可使用关键字实参,其中每个实参都由变量名和值组成;还可使用列表和字典。

位置实参

调用函数时,Python 必须将函数调用中的每个实参都关联到函数定义中的一个形参。为此, 最简单的关联方式是基于实参的顺序。这种关联方式称为位置实参。

1
2
3
4
5
6
def describe_pet(animal_type, pet_name): 
"""显示宠物的信息。"""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet('hamster', 'harry')

关键字实参

关键字实参是传递给函数的名称值对。因为直接在实参中将名称和值关联起来,所以向函数传递实参时不会混淆。

1
2
3
4
5
6
def describe_pet(animal_type, pet_name): 
"""显示宠物的信息。"""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet(animal_type='hamster', pet_name='harry')

默认值

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

1
2
3
4
5
6
def describe_pet(pet_name, animal_type='dog'): 
"""显示宠物的信息。"""
print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet(pet_name='willie')

使用默认值时,必须先在形参列表中列出没有默认值的形参,再列出有默认值的实参。 这让 Python 依然能够正确地解读位置实参。

避免实参错误

实参多于或少于函数完成工作所需的信息时,将出现实参不匹配错误。

返回值

返回简单值

1
2
3
4
5
6
7
def get_formatted_name(first_name, last_name): 
"""返回整洁的姓名。"""
full_name = f"{first_name} {last_name}"
return full_name.title()

musician = get_formatted_name('jimi', 'hendrix')
print(musician)

让实参变成可选的

可使用默认值来让实参变成可选的。

返回字典

函数可返回任何类型的值,包括列表和字典等较复杂的数据结构。

1
2
3
4
5
6
7
8
9
def build_person(first_name, last_name, age=None): 
"""返回一个字典,其中包含有关一个人的信息。"""
person = {'first': first_name, 'last': last_name}
if age:
person['age'] = age
return person

musician = build_person('jimi', 'hendrix', age=27)
print(musician)

为特殊值 None(表示变量没有值)

传递列表

1
2
3
4
5
6
7
8
def greet_users(names): 
"""向列表中的每位用户发出简单的问候。"""
for name in names:
msg = f"Hello, {name.title()}!"
print(msg)

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

禁止函数修改列表

可向函数传递列表的副本而非原件。

切片表示法[:]创建列表的副本。

1
function_name(list_name[:]) 

传递任意数量的实参

有时候,预先不知道函数需要接受多少个实参。

1
2
3
4
5
6
def make_pizza(*toppings): 
"""打印顾客点的所有配料。"""
print(toppings)

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

形参名*toppings 中的星号让 Python 创建一个名为 toppings 的空元组,并将收到的所有值都封装到这个元组中。

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

如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。

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

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

你经常会看到通用形参名*args,它也收集任意数量的位置实参。

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

1
2
3
4
5
6
7
8
9
10
def build_profile(first, last, **user_info): 
"""创建一个字典,其中包含我们知道的有关用户的一切。"""
user_info['first_name'] = first
user_info['last_name'] = last
return user_info

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

你经常会看到形参名**kwargs,它用于收集任意数量的关键字实参。

将函数存储在模块中

将函数存储在称为模块的独立文件中,再将模块导入到主程序中。

import 语句允许在当前运行的程序文件中使用模块中的代码。

导入整个模块

如果使用这种 import 语句导入了名为 module_name.py 的整个模块,就可 使用下面的语法来使用其中任何一个函数:

1
module_name.function_name()

导入特定的函数

还可以导入模块中的特定函数,这种导入方法的语法如下:

1
from module_name import function_name

通过用逗号分隔函数名,可根据需要从模块中导入任意数量的函数:

1
from module_name import function_0, function_1, function_2

使用 as 给函数指定别名

如果要导入函数的名称可能与程序中现有的名称冲突,或者函数的名称太长,可指定简短而独一无二的别名。

指定别名的通用语法如下:

1
from module_name import function_name as fn

使用 as 给模块指定别名

给模块指定别名的通用语法如下:

1
import module_name as mn

导入模块中的所有函数

使用星号(*)运算符可让 Python 导入模块中的所有函数:

1
from module_name import * 

Python 可能遇到多个名称相同的函数或变量,进而覆盖函数,而不是分别导入所有的函数

最佳的做法是,要么只导入需要使用的函数,要么导入整个模块并使用句点表示法。

第九章 类

创建和使用类

创建 Dog 类

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

def __init__(self, name, age):
"""初始化属性 name 和 age。"""
self.name = name #像这样可通过实例访问的变量称为属性
self.age = age

def sit(self): #这些方法执行时不需要额外的信息,因此它们只有一个形参 self。
"""模拟小狗收到命令时蹲下。"""
print(f"{self.name} is now sitting.")

def roll_over(self):
"""模拟小狗收到命令时打滚。"""
print(f"{self.name} rolled over!")

类中的函数称为方法。

方法__init__是一个特殊方法,每当你根据 Dog 类创建 新实例时,Python 都会自动运行它。

务必确保__init__()的两边都 有两个下划线,否则当你使用类来创建实例时,将不会自动调用这个方法。

为何必须在方法定义中包含形参 self 呢?因 为 Python 调用这个方法来创建 Dog 实例时,将自动传入实参 self。每个与实例相关联的方法调用都自动传递实参 self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。

根据类创建实例

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):
"""初始化属性 name 和 age。"""
self.name = name #像这样可通过实例访问的变量称为属性
self.age = age

def sit(self): #这些方法执行时不需要额外的信息,因此它们只有一个形参 self。
"""模拟小狗收到命令时蹲下。"""
print(f"{self.name} is now sitting.")

def roll_over(self):
"""模拟小狗收到命令时打滚。"""
print(f"{self.name} rolled over!")

my_dog = Dog('Willie', 6)

print(f"My dog's name is {my_dog.name}.")
print(f"My dog is {my_dog.age} years old.")

使用类和实例

给属性指定默认值

创建实例时,有些属性无须通过形参来定义,可在方法__init__()中为其指定默认值。

修改属性的值

直接修改属性的值

最简单的方式是通过实例直接访问它。

通过方法修改属性的值

将值传递给方法, 由它在内部进行更新。

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

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

继承

一个类继承另一个类时,将自动获得另一个类的所有属性和方法。原有的类称为父类,而新类称为子类。子类继承了父类的所有属性和方法,同时还可以定义自己的属性和方法。

子类的方法__init__()

在既有类的基础上编写新类时,通常要调用父类的方法__init__()。这将初始化在父类 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
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 = f"{self.year} {self.make} {self.model}"
return long_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!")

def increment_odometer(self, miles):
self.odometer_reading += miles

#ElectricCar是Car的子类
class ElectricCar(Car):
"""电动汽车的独特之处。"""

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

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

super()是一个特殊函数,让你能够调用父类的方法。

给子类定义属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Car: 
--snip--

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.get_descriptive_name())
my_tesla.describe_battery()

重写父类的方法

可在子类中定义一个与要重写的父类方法同名的方法。这样,Python 将不会考虑这个父类方法,而只关注 你在子类中定义的相应方法。

将实例用作属性

有时候可能需要将类的一部分提取出来,作为一个独立的类。可以将大型类拆分成多个协同工作的小类。

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
class Car: 
--snip--

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 = Battery() #这行代码让Python创建一个新的 Battery 实例
'''每当方法__init__()被调用时,都将执行该操作,因此现在每个 ElectricCar 实例
都包含一个自动创建的Battery 实例。'''
my_tesla = ElectricCar('tesla', 'model s', 2019)

print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()

模拟实物

解决上述问题时,从较高的逻辑层面(而不是语法层面) 考虑;考虑的不是 Python,而是如何使用代码来表示实物

导入类

导入单个类

1
2
3
4
5
6
7
from car import Car #import 语句让 Python 打开模块 car 并导入其中的 Car 类。

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

my_new_car.odometer_reading = 23
my_new_car.read_odometer()

从一个模块中导入多个类

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

my_beetle = Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())

my_tesla = ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())

从一个模块中导入多个类时,用逗号分隔了各个类

导入整个模块

还可以导入整个模块,再使用句点表示法访问需要的类。

1
2
3
4
5
6
7
import car #导入了整个 car 模块

my_beetle = car.Car('volkswagen', 'beetle', 2019) #使用语法 module_name.ClassName 访问需要的类
print(my_beetle.get_descriptive_name())

my_tesla = car.ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())

导入模块中的所有类

1
from module_name import * 

不推荐使用这种导入方式,原因有二。

  1. 如果只看文件开头的 import 语句,就能清楚 地知道程序使用了哪些类,将大有裨益。然而这种导入方式没有明确地指出使用了模块中的哪些 类。

  2. 这种方式还可能引发名称方面的迷惑。如果不小心导入了一个与程序文件中其他东西同名的类,将引发难以诊断的错误。

需要从一个模块中导入很多类时,最好导入整个模块,并使用 module_name.ClassName 语法 来访问类。这样做时,虽然文件开头并没有列出用到的所有类,但你清楚地知道在程序的哪些地 方使用了导入的模块。这也避免了导入模块中的每个类可能引发的名称冲突。

使用别名

1
2
from electric_car import ElectricCar as EC  #可在 import 语句中给 ElectricCar 指定一个别名
my_tesla = EC('tesla', 'roadster', 2019)

类编码风格

  1. 类名应采用驼峰命名法,即将类名中的每个单词的首字母都大写,而不使用下划线。实例名和模块名都采用小写格式,并在单词之间加上下划线。

  2. 对于每个类,都应紧跟在类定义后面包含一个文档字符串。这种文档字符串简要地描述类的 功能,并遵循编写函数的文档字符串时采用的格式约定。每个模块也都应包含一个文档字符串, 对其中的类可用于做什么进行描述。

  3. 可使用空行来组织代码,但不要滥用。在类中,可使用一个空行来分隔方法;而在模块中, 可使用两个空行来分隔类。

  4. 需要同时导入标准库中的模块和你编写的模块时,先编写导入标准库模块的 import 语句, 再添加一个空行,然后编写导入你自己编写的模块的 import 语句。在包含多条 import 语句的程 序中,这种做法让人更容易明白程序使用的各个模块都来自何处。

第十章 文件和异常

从文件中读取数据

读取整个文件

1
2
3
with open('pi_digits.txt',encoding='utf-8') as file_object:
contents = file_object.read()
print(contents)

函数open()接受一个参数:要打开的文件的名称。Python在当前执行的文件所在的目录中查找指定的文件。

函数open()返 回一个表示文件的对象。在这里,open(‘pi_digits.txt’)返回一个表示文件pi_digits.txt 的对象, Python 将该对象赋给file_object 供以后使用。

关键字 with在不再需要访问文件后将其关闭。

我们调用了 open(), 但没有调用 close()。你只管打开文件,并在需要时使用它,Python 自会 在合适的时候自动将其关闭。

方法 read()读取这个文件的全部内容,并将其作为一个长长的字符串赋给变量 contents。

文件路径

显示文件路径时,Windows 系统使用反斜杠(\)而不是斜杠(/),但在代码中依然可以使用斜杠。

如果在文件路径中直接使用反斜杠,将引发错误,因为反斜杠用于对字符串中的字符进行转义。例如,对于路径”C:\path\to\file.txt”,其中的\t 将被解读为制表符。如果一 定要使用反斜杠,可对路径中的每个反斜杠都进行转义,如”C:\\path\\to\\file.txt”。

逐行读取

要以每次一行的方式检查文件,可对文件对象使用 for 循环:

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

with open(filename,encoding='utf-8') as file_object:
for line in file_object:
print(line)

关键字 with,让 Python 负 责妥善地打开和关闭文件。为查看文件的内容,通过对文件对象执行循环来遍历文件中的每一行

为何会出现这些空白行呢?因为在这个文件中,每行的末尾都有一个看不见的换行符,而函 数调用 print()也会加上一个换行符,因此每行末尾都有两个换行符:一个来自文件,另一个来 自函数调用 print()。要消除这些多余的空白行,可在函数调用 print()中使用 rstrip():

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

with open(filename,encoding='utf-8') as file_object:
for line in file_object:
print(line.rstrip())

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

使用关键字 with 时,open()返回的文件对象只在 with 代码块内可用。如果要在 with 代码块 外访问文件的内容,可在 with 代码块内将文件的各行存储在一个列表中。

1
2
3
4
5
6
7
filename = 'pi_digits.txt'

with open(filename,encoding='utf-8') as file_object:
lines = file_object.readlines()

for line in lines:
print(line.rstrip())

read() 每次读取整个文件,它通常将读取到底文件内容放到一个字符串变量中,也就是说 .read() 生成文件内容是一个字符串类型。

readline()每只读取文件的一行,通常也是读取到的一行内容放到一个字符串变量中,返回str类型。

readlines()每次按行读取整个文件内容,将读取到的内容放到一个列表中,返回list类型。(一行一行存起来)

写入文件

写入空文件

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 将在返回文件对象前清空该文件的内容。

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

python中read() readline()以及readlines()区别 - 知乎 (zhihu.com)

写入多行

函数 write()不会在写入的文本末尾添加换行符

1
2
3
4
filename = 'programming.txt' 
with open(filename, 'w') as file_object:
file_object.write("I love programming.")
file_object.write("I love creating new games.")

附加到文件

如果要给文件添加内容,而不是覆盖原有的内容,可以以附加模式(‘a’)打开文件。

以附加模式打 开文件时,Python 不会在返回文件对象前清空文件的内容,而是将写入文件的行添加到文件末尾。 如果指定的文件不存在,Python 将为你创建一个空文件。

异常

Python 使用称为异常的特殊对象来管理程序执行期间发生的错误。每当发生让 Python 不知所措的错误时,它都会创建一个异常对象。

异常是使用 try-except 代码块处理的。使用 try-except 代码块时,即便出现异常,程序也将继续运行: 显示你编写的友好的错误消息,而不是令用户迷惑的 traceback。

使用 try-except 代码块

例如:

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

使用异常避免崩溃

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

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

else 代码块

try-except-else 代码块的工作原理大致如下。Python 尝试执行 try 代码块中的代码,只有可能引发异常的代码才需要放在 try 语句中。有时候,有一些仅在 try 代码块成功执行时才需要运行的代码,这些代码应放在 else 代码块中。except 代码块告诉 Python,如果尝试运行 try 代 码块中的代码时引发了指定的异常该怎么办

处理 FileNotFoundError 异常

1
2
3
4
5
6
7
filename = 'alice.txt' 

try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print(f"Sorry, the file {filename} does not exist.")

给参数 encoding 指定了值,在系统的默认编码与要读取文件使用的 编码不一致时,必须这样做。

分析文本

方法 split()以空格为分隔符将字符串分拆成多个部分,并将这些部分都存储到一个列表中。

1
2
3
4
5
6
7
8
9
10
11
12
filename = 'alice.txt'

try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
except FileNotFoundError:
print(f"Sorry, the file {filename} does not exist.")
else:
# Count the approximate number of words in the file.
words = contents.split()
num_words = len(words)
print(f"The file {filename} has about {num_words} words.")

静默失败

要让程序静默失 败,可像通常那样编写 try 代码块,但在 except 代码块中明确地告诉 Python 什么都不要做。Python 有一个 pass 语句,可用于让 Python 在代码块中什么都不要做。

1
2
3
4
5
6
7
8
9
10
11
def count_words(filename): 
"""计算一个文件大致包含多少个单词。"""
try:
--snip--
except FileNotFoundError:
pass #什么都没做
else:
--snip--
filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
count_words(filename)

pass 语句还充当了占位符,提醒你在程序的某个地方什么都没有做,并且以后也许要在这里做些什么。

存储数据

模块 json 让你能够将简单的 Python 数据结构转储到文件中,并在程序再次运行时加载该文 件中的数据。你还可以使用 json 在 Python 程序之间分享数据。

JSON(JavaScript Object Notation)格式最初是为 JavaScript 开发的,但随后成了一种常 见格式,被包括 Python 在内的众多语言采用。

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

函数 json.dump()接受两个实参:要存储的数据,以及可用于存储数据的文件对象。

1
2
3
4
5
6
7
8
import json

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

filename = 'numbers.json' #使用文件扩展名.json 来指出文件存储的数据为 JSON 格式。
with open(filename, 'w') as f:
json.dump(numbers, f)

函数json.load()将列表读取到内存中:

1
2
3
4
5
6
7
import json 

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

print(numbers)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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}!")

重构

代码能够正确地运行,但通过将其划分为一系列完成具体工作的 函数,还可以改进。这样的过程称为重构。

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
import json

def get_stored_username():
"""Get stored username if available."""
filename = 'username.json'
try:
with open(filename) as f:
username = json.load(f)
except FileNotFoundError:
return None
else:
return username

def get_new_username():
"""Prompt for a new username."""
username = input("What is your name? ")
filename = 'username.json'
with open(filename, 'w') as f:
json.dump(username, f)
return username

def greet_user():
"""Greet the user by name."""
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()

每个函数都执行单一而清晰的任务。

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

第十一章 测试代码

测试函数

可通过的测试

下面的测试用例只包含一个方法,它检查函数 get_formatted_name()在给定名和姓时能否正确工作:

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

from name_function import get_formatted_name

class NamesTestCase(unittest.TestCase):
"""Tests for 'name_function.py'."""

def test_first_last_name(self):
"""Do names like 'Janis Joplin' work?"""
formatted_name = get_formatted_name('janis', 'joplin')
self.assertEqual(formatted_name, 'Janis Joplin') #断言方法

def test_first_last_middle_name(self):
"""Do names like 'Wolfgang Amadeus Mozart' work?"""
formatted_name = get_formatted_name(
'wolfgang', 'mozart', 'amadeus') #中间名是可选的,放在后面
self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart')

if __name__ == '__main__':
unittest.main()

创建了一个名 为 NamesTestCase 的类,用于包含一系列针对 get_formatted_name()的单元测试。这个类必须继承 unittest.TestCase 类,这样 Python 才知道如何运行你编写的测试。

运行 test_name_function.py 时,所有以 test_打头的方法都将自动运行。

断言方法核实得到的结果是否 与期望的结果一致。

很多测试框架都会先导入测试文件再运行。导入文件时,解释器将在导入的同时执行它。if 代码块检查特殊变量__name__,这个变量是在程序执行时设置的。如果这个文件作为主程序执行,变量__name__将被设置为’__main__‘。在这里,调用 unittest.main()来运行测试用例。如果这个文件被测试框架导入,变量的值将不是’__main__‘,因此不会调用 unittest.main()。

测试类

各种断言方法

image-20230630213741347

setUp()方法

setUp()让我们只需创建这些对象 一次,就能在每个测试方法中使用。

测试自己编写的类时,方法 setUp()让测试方法编写起来更容易:可在 setUp()方法中创建一 系列实例并设置其属性,再在测试方法中直接使用这些实例。相比于在每个测试方法中都创建实 例并设置其属性,这要容易得多。