Python入门到实践学习笔记

第一部分

前面的安装环节网络上讲得比书上的要详细多了,官方文档也讲得很清楚,所以这里就不在这里说了,有兴趣可以去访问官方网站😺

第一章 变量和简单的数据类型

变量

变量命名有一定的规则

  • 不能是数字开头,比如:1_message
  • 不能包含空格,比如:mess age
  • 不能是python里面的关键字,比如:print 或 def 等,python里面有很多关键字一般你打出来会高亮显示
  • 最好的变量名应该是既简短又有描述性,比如:name、student_name 等等。

这里message是变量名,'Hello Python World!'是指向message变量的值。变量是可以重复赋值的,但它只会储存最后的值。

message = 'Hello Python World!'
print(message)
Hello Python World!

message = '你好!'
print(message)
你好!

字符串

有双引号或单引号包裹的内容就称为字符串,有些编程语言只有双引号是字符串。

"This is string."
'This is string.'

1.方法:title()、upper()、lower()

方法(method)是python对数据执行的操作,name后面的点(.)是让python对name变量执行 title() 方法指定的操作,这个方法就是把单词的手写字幕改成大写。**upper()**方法是让所有字母变成大写、**lower()**方法是让所有字母变成小写。还有一个方法修改字符串中的指定单词。**replace()**这个方法我们在后面的练习中会用到。

name = "zheng kai nan"
print(name.title())
Zheng Kai Nan

name = "Zheng Kai Nan"
print(name.upper())
ZHENG KAI NAN
print(name.lower())
zheng kai nan

name = "zheng kai nan"
name_01 = name.replace("kai nan", "ji zhong")
print(name)
print(name_01)
zheng kai nan
zheng ji zhong

2.在字符串中插入变量

这个功能很常用,python的方式是在字符串的引号前面加一个 f 字母:

first_name = "zheng"
last_name = "kai nan"
full_name = f"{first_name} {last_name}"
print(full_name)
zheng kai nan
print(f"Hello,{full_name.title()}!")
Hello,Zheng Kai Nan!

3.制表符和换行符控制字符串 \t \n

在字符串中添加**\t**、\n可以缩进和换行,字符串引号前加**r**可以让取消里面所有转义符号:

message = "你知道这几种编程语言吗?Python Swift C++ Go Java"
print(message)
你知道这几种编程语言吗?Python Swift C++ Go Java

message = "\t你知道这几种编程语言吗?Python Swift C++ Go Java"
print(message)
     你知道这几种编程语言吗?Python Swift C++ Go Java

message = "你知道这几种编程语言吗?\nPython Swift C++ Go Java"
print(message)
你知道这几种编程语言吗?
Python Swift C++ Go Java

# 也可以同时使用
message = "你知道这几种编程语言吗?\n\tPython \n\tSwift \n\tC++ \n\tGo \n\tJava"
print(message)
你知道这几种编程语言吗?
	Python 
	Swift 
	C++ 
	Go 
	Java
# 取消转义    
print(r"\nasd") 
\nasd

4.删除字符串里面的空白rstrip()、lstrip()、strip()

有时候后输入字符串的时候会多输空格,在python里面多一个空格就意味着两个不同的字符串了。这里有3个方法:分别是rstrip()、lstrip()、strip(),*rstrip()*是删除字符串右边的空格,*lstrip()*是删除左边的空格,*strip()*是删除两边的空格。但是这种删除只是暂时的,要想永久删除就要重新赋值给变量。

language = ' python '
print(language.rstrip())
' python'
print(language.lstrip())
'python '
print(language.strip())
'python'
# 重新赋值给变量
language = language.strip()
print(language)
'python'

5.删除前缀和后缀 removeprefix()、removesuffix()

有些内容有统一的前缀,比如你有很多照片,前缀是某一个时间比如2023.7.25+名字或序号,你就只想要序号或名字就可以用到这个功能,还有就是URL里面的https://这个是网站前缀,就可以用*removeprefix()*括号里面填写你想删除的前缀,用引号括起来。这种方法的删除也是暂时的,要想永久删除需要重新赋值,和上面的删除空白一样。

# 删除前缀
Travel_photo = "2023.7.25-镇远旅游照片"
print(Travel_photo.removeprefix('2023.7.25-'))
镇远旅游照片
url = "https://www.baidu.com"
print(url.removeprefix('https://'))
www.baidu.com

#删除后缀
file_name = 'abc.jpg'
print(file_name.removesuffix('.jpg'))
abc

可以理解为数学里面的数字,用来计算或可视化数据

1.整数及其运算

integer()或简写int()表示整数,+、-、*、/ 这几个符号表示加减乘除,还有些复杂的后面会讲到,比如(**)两个星号代表乘方运算。

2 + 3
5
3 ** 2
9

2.浮点数

float() 表示浮点数,就是带有小数点的数称为浮点数,数字同样都可以应用上面的计算符号。

0.1 + 0.2
0.3

3.数字中的下划线

下划线用在数字中,并不会有其他的效果,只是方便我们更好观察。

number = 1000_000_000
print(number)
10000000000

4.同时给多个变量赋值

同时给多个变量赋值,需要用逗号将变量名分开,对于要赋给变量的值也需要做同样的事情

x,y,z = 1,2,3
x = 1
y = 2
z = 3

# 错误演示
x,y,z = 1,2
print(x)
发生异常: ValueError x
not enough values to unpack (expected 3, got 2)
  File "F:\第一章变量和简单数据类型\full_name.py", line 7, in <module>
    x, y, z = 1, 2
    ^^^^^^^
ValueError: not enough values to unpack (expected 3, got 2)

5.常量

Python里面没有常量(就是整个程序的生命周期不改变值的“变量”),一般是用全大写字母来共同与其他程序员形成约定,遇到全大写字母的变量时,视为常量。

NAME = "太阳"

6.注释

注释的主要目的是阐述代码要做什么,以及是如何做的。

# 打印出Hello World
print("Hello World")

小结

本章我们学习了如何使用变量,创建了描述性的变量名,学习了字符串是什么,以及如何使用全大写、全小写和首字母大写的方式显示字符串和制表符、换行符。还学习了如何删除字符串中多余的字符和空格,以及字符串中插入变量的方法。还学习了整数和浮点数,还有学习了写注释的目的。

第二章 列表简介

列表是什么

列表(list)由一系列按特定顺序排列的元素组成,一般给列表命名以复数形式,比如:name就用names。
在python中列表用“[ ]”表示,每个元素用逗号隔开。列表里面有两个东西要搞清楚,一个是索引,一个是元素的值。要访问列表时使用索引和元素值都可以,列表的索引是从0开始的,所以要访问的n个元素就使用n-1的索引值。

# 列表
bicycles = ['trek', 'cannondale', 'redline', 'specialized']
print(bicycles)
['trek', 'cannondale', 'redline', 'specialized']
# 利用索引值访问列表元素
print(bicycles[0])
print(bicycles[3])
trek
specialized
# 访问列表的倒数第一个元素
print(bicycles[-1])
specialized
# 使用列表里面的元素到其他场景
message = f'我喜欢{bicycles[0]}品牌的摩托车。'
print(message)
我喜欢trek品牌的摩托车。

修改、添加和删除列表元素

大多数列表将是动态的,意味着列表创建后,随着程序的运行将修改、增加、或删除其中的元素。
这中间增加有**append()**方法在列表末尾添加元素,insert()在列表的指定位置插入元素。
删除有
del 列表元素
将删除列表元素并且无法在访问这个元素了。
**pop()**默认删除列表最后一个元素,也可以指定删除列表中的其他元素,在括号里面填上该元素的索引值就可以了。
这里说下,pop()删除的元素可以赋值给一个变量继续使用,不像del不能在赋值和访问了。
**remove()**方法是删除一个指定的元素值,列表有多个同样的值的话这个方法只删除第一个,要想全部删除就要使用循环。
值得一提的是这个方法和pop()一样删除了的元素可以赋值给一个变量继续使用。

# 修改列表元素
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)
['honda', 'yamaha', 'suzuki']
motorcycles[0] = 'da yang'
print(motorcycles)
['da yang', 'yamaha', 'suzuki']

# 添加列表元素
## 在列表末尾添加元素
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)
['honda', 'yamaha', 'suzuki']
motorcycles.append('da yang')
print(motorcycles)
['honda', 'yamaha', 'suzuki', 'da yang']
## 在列表的指定位置插入元素
motorcycles.insert(0, 'li fan')
print(motorcycles)
['li fan', 'honda', 'yamaha', 'suzuki', 'da yang']

# 删除列表元素
## del方法删除
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)
['honda', 'yamaha', 'suzuki']
del motorcycles[0]
print(motorcycles)
['yamaha', 'suzuki']
## pop方法删除
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)
['honda', 'yamaha', 'suzuki']
poped_motorcycles = motorcycles.pop()
print(motorcycles)
['honda', 'yamaha']
print(poped_motorcycles)
suzuki
### pop方法删除指定位置的元素
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)
['honda', 'yamaha', 'suzuki']
last_motorcycles = motorcycles.pop(0)
print(motorcycles)
['yamaha', 'suzuki']
print(last_motorcycles)
honda
## remove()根据值删除元素
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)
['honda', 'yamaha', 'suzuki']
mid_motorcycles = 'yamaha'
motorcycles.remove(mid_motorcycles)
print(motorcycles)
['honda', 'suzuki']
print(f'{mid_motorcycles.title()}')
Yamaha

列表管理

1.sort()方法可以对列表永久排序,该方法是将列表按字母顺序或数字从小到大的顺序进行排序。

cars = ['d', 'f', 'c', 'b', 'a', 'e']
print(cars)
['d', 'f', 'c', 'b', 'a', 'e']
cars.sort()
print(cars)
['a', 'b', 'c', 'd', 'e', 'f']
# 反向排序
cars.sort(reverse=True)
print(cars)
['f', 'e', 'd', 'c', 'b', 'a']
# 反转列表,这是一种快捷方法
cars = cars[::-1]
print(cars)
['a', 'b', 'c', 'd', 'e', 'f']

2. sorted()方法是对列表进行临时排序

# 临时反向排序
print(sorted(cars, reverse=True))
['a', 'b', 'c', 'd', 'e', 'f']
print(cars)
['f', 'e', 'd', 'c', 'b', 'a']

3.reverse()方法是永久反转排列列表里面的元素。

cars.reverse()
print(cars)
['a', 'b', 'c', 'd', 'e', 'f']

**4.len()**方法可以快速获取列表长度,其显示的是列表内有多少个元素。

print(len(cars))
6

5.enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。

>>> seq = ['one', 'two', 'three']
>>> for i, element in enumerate(seq):
...     print i, element
...
0 one
1 two
2 three

小节

本章学习了什么是列表,以及如何使用列表内的元素。学习了定义列表,增删改列表内元素,以及如何对列表进行永久排序和临时排序,还学习了反转列表顺序和反向排序。

第三章 操作列表

遍历整个列表

使用for循环可以很轻松的访问整个列表,以及对列表进行操作。这里值得一提的是for循环里面的变量名,在遍历列表是尽可能用列表的单数名,比如列表名为cars,那么for循环的变量尽量写成car。

magicians = ['alice', 'david', 'carolina']
for magician in magicians:
    print(magician)

alice
david
carolina
# 进行其他操作
for magician in magicians:
    print(f'{magician.title()},你的表演很精彩!')
    print(f'谢谢你,{magician.title()}')
print('感谢你们每一个人的表演')
Alice,你的表演很精彩!
谢谢你,Alice
David,你的表演很精彩!
谢谢你,David
Carolina,你的表演很精彩!
谢谢你,Carolina
感谢你们每一个人的表演

创建数值列表

range()函数可以生成一系列数,但它实际上不会打印最后一个数,这是编程语言中常见的差一行为结果,要想打印最后个数需要使用**+1或者是使用比最后一个数大1的数**

for i in range(1, 5):
    print(i, end=' ')
1 2 3 4
# 打印最后一位
for i in range(1, 5+1):
    print(i, end=' ')
1 2 3 4 5

**list()**函数可以将range()的结果直接转换为列表,方法是将range()作为list()的参数,同时range()还可以指定步长。

number = list(range(1, 6))
print(number)
[1, 2, 3, 4, 5]
# 从2开始到11结束,指定步长为2
even_number = list(range(2, 11, 2))
print(even_number)
[2, 4, 6, 8, 10]
# 用for循环创建数值列表
squares = []
for value in range(1, 11):
    square = value**2
    squares.append(square)
print(squares)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

使用这几个函数可以对数值列表进行统计列表作为其参数,min()列表里面最小值,max()列表里面最大值,sum()列表所有数求和。

# 简单统计数值列表
values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(min(values))
0
print(max(values))
9
print(sum(values))
45

列表推导式是一种简化方法,这种方法首先指定一个列表名(变量名),然后等号右边用中括号开始,括号内的排列顺序是表达式------for循环,这种方法需要经常练习。比如创建上面数值列表可以写成:

squares = [value**2 for value in range(1, 11)]
print(squares)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# 1到50的奇数之和游戏
jishu_num = list(range(1, 50, 2))
print(sum(jishu_num))
625
print(sum([jishu for jishu in range(1, 50, 2)]))
625

使用列表的一部分

切片,要使用列表的一部分,其实就是使用列表的索引。使用的方法是在调用列表时在列表名后面[0:1],这代表列表的第一个和第二个值,就是在列表索引值0,1的值。这里面也要注意差一行为。在一个班级需要评出前三名或后三名或部分名单时,切片可以起到很好的作用。

# 切片
players = ['charles', 'martina', 'michael', 'florence', 'eli']
# 索引值1到3的元素因为差一行为,所以并不会打印索引值3的元素
print(players[1:3])
['martina', 'michael']
# 索引值0到3的元素
print(players[:3])
['charles', 'martina', 'michael']
# 索引值1到列表最后一个的元素
print(players[1:])
['martina', 'michael', 'florence', 'eli']
# 索引值0到列表最后一个的元素
print(players[:])
['charles', 'martina', 'michael', 'florence', 'eli']
# 索引值为列表最后3个的元素
print(players[-3:])
['michael', 'florence', 'eli']
# 遍历切片
print('下面是我们的前三名')
for player in players[:3]:
    print(player.title())
下面是我们的前三名
Charles
Martina
Michael

复制列表或列表切片

需要注意的是不能直接用列表赋值给新变量,这会使他们指向同一个列表,你操作列表,两个变量都会改变,要复制列表时尽量使用切片复制。

players = ['charles', 'martina', 'michael', 'florence', 'eli']

yuwen_win = players[:3]
print('语文前三名是:')
print(yuwen_win)
语文前三名是:
['charles', 'martina', 'michael']

shuxue_win = players[-3:]
print('数学前三名是:')
print(shuxue_win)
数学前三名是:
['michael', 'florence', 'eli']

# 需要注意的是不能直接用列表赋值给新变量,这会使他们指向同一个列表,你操作列表,两个变量都会改变
new_players = players
print(new_players)
['charles', 'martina', 'michael', 'florence', 'eli']
players.append('AAABBB')
print(new_players)
['charles', 'martina', 'michael', 'florence', 'eli', 'AAABBB']

元组

不可以改变值的列表,称为元组(tuple),元组和列表很像,但是列表是用方括号,而元组是用圆括号。虽然不可以修改元组里面的值,但是可以通过重新赋值改变该变量。

dimensions = (200, 50)
print(dimensions)
(200, 50)
# 遍历
dimensions = (400, 50)
for i in dimensions:
    print(i)
400
50

设置代码格式

为什么要设置代码格式?

代码被阅读的次数远大于编写的次数。在编写完以后的多次阅读中,良好的代码格式可以让阅读花的时间很短。
PEP 8 是设置代码指南,它建议每级缩进4个空格。每个编辑器(IDE)都可以设置,通常(tab)使用是最多的。
每行长度建议不超过79个字符,但这并不是不可逾越的红线,刚开始学的时候不必在意,不过养成这样的习惯后,会对以后和别人合作带来很多方便。
空行建议:不同内容建议用一个空行隔开,不建议使用3、4或多个空行来区隔,主要是空行多了影响阅读。

小结

本章学习了如何高效的处理列表中的元素,如何使用for循环遍历列表,如何创建简单的数值列表以及对数值列表执行的一些操作。还学习了如何使用切片对列表进行操作和复制,最后还学习了元组,以及设置代码格式。

第四章 if语句

编程时经常需要检查一系列的条件,并根据此决定采取什么措施。if语句让你能够检查程序的当前状态,并采取相应的措施。每条if语句的核心都是一个值为True或False的表达式。相应的符号有:==、!=、<、>、<=、>=。关键字有:if、elif、else。在检查多个条件时会用到与、或、非:他们的关键词是:and(两个都为真)、or(一个为真)、not(条件为假)。

示例

# 示例
cars = ['audi', 'bmw', 'subaru', 'toyota']
for car in cars:
    if car == 'bmw':
        print(car.upper())
    else:
        print(car.title())
Audi
BMW
Subaru
Toyota

条件测试

>>> car = 'bmw'
# ==是相等运算符,目的是比较等式两边的值是否相等,相等就是True否则就是False
>>> car == 'bmw'
True
>>> car == 'toyota'
False
# 检查是忽略大小写可以用到字符串的方法upper()或title()等等
>>> car.upper() == 'BMW'
True

检查是否不等,这里用的符号是 != 。

requested_topping = 'mushrooms'
if requested_topping != 'anchovies':
    print('Hold the anhovies!')
Hold the anhovies!

数值比较

>>> age = 18
>>> age == 19
False
>>> age == 18  
True

answer = 17
if answer != 42:
    print('这个数值不是42,请再次更改变量值!')
这个数值不是42,请再次更改变量值!

检查多个条件

>>> age_0 = 21
>>> age_1 = 18
# and
>>> age_0 >=21 and age_1 >=21
False
# or
>>> age_0 >=21 or age_1 >=21  
True

检查某个特定的值是否在或不在列表中

>>> number = [1,2,3,4,5]
>>> 5 in number
True
>>> 9 in number
False

number = [1, 2, 3, 4, 5]
number_0 = 8
if number_0 not in number:
    print(f'{number_0}不在我们的数字列表里面。我们把他加进来吧!')
8不在我们的数字列表里面。我们把他加进来吧!

if 语句

在了解了条件测试之后,就可以编写if 语句了,具体使用哪一种取决于测试的条件数量。

最简单的if 语句

age = 18
if age >= 18:
    print('你已经成年了。')
你已经成年了。

if-else语句

age = 17
if age >= 18:
    print('你已经成年了。')
else:
    print('你还是未成年。')
你还是未成年。

if-elif-else语句

age = 12
if age < 4:
    print('你可以免费观看电影。')
elif age < 18:
    print('你可以半价购买电影票。')
else:
    print('你需要购买全票。')
你可以半价购买电影票。
# 有些情况下是可以省略else这个代码块的
if age < 4:
    print('你可以免费观看电影。')
elif age < 18:
    print('你可以半价购买电影票。')
elif age >= 18:
    print('你需要购买全票。')
你可以半价购买电影票。

使用if 语句处理列表

检查列表里面是否有相应的元素

numbers = [1, 2, 3, 4, 5]
for number in numbers:
    if number == 3:
        print('这个列表里3是中间数')
    else:
        print(f'这个列表里有{number}.')
这个列表里有1.
这个列表里有2.
这个列表里3是中间数
这个列表里有4.
这个列表里有5.

确定列表是不是空列表,列表为空时都是返回False。

numbers = []
if numbers:
    for number in numbers:
        print(f'adding {number}')
    print('\n所有数字添加了')
else:
    print('好像这里面没有数字诶!')
好像这里面没有数字诶!

多个列表

numbers_0 = [0, 1, 2, 3, 4, 5]
numbers_1 = [6, 7, 8, 9, 0]
print(numbers_0)
print(numbers_1)
for number in numbers_1:
    if number in numbers_0:
        print(f'这两个列表都有 {number}')
    else:
        print(f'第一个列表里面没有 {number} 这个数。')
[0, 1, 2, 3, 4, 5]
[6, 7, 8, 9, 0]
第一个列表里面没有 6 这个数。
第一个列表里面没有 7 这个数。
第一个列表里面没有 8 这个数。
第一个列表里面没有 9 这个数。
这两个列表都有 0

小结

本章学习了if语句以及相关的关键词和符号,也使用for循环遍历列表时对某些元素做特出处理。

第五章 字典

字典就是储存多个有两种相关信息的元素,比如姓名及其年龄,单词及其含义等,字典的信息量也不守限制。

一个简单的字典

alien = {'color': 'bule', 'points': 5}
print(alien['color'])
print(alien['points'])
bule
5

使用字典

字典(dictionary)是一系列的键值对,每个键都对应一个值,字典用放在花括号{ }中的一系列键值对表示。

访问字典中的值

alien = {'color': 'bule', 'points': 5}
print(alien['color'])
bule

添加键值对

alien['x_position'] = 0
alien['y_position'] = 25
print(alien)
{'color': 'bule', 'points': 5, 'x_position': 0, 'y_position': 25}

创建一个空字典

alien = {}
alien['color'] = 'blue'
alien['x_position'] = 0
alien['y_position'] = 25
print(alien)
{'color': 'blue', 'x_position': 0, 'y_position': 25}

修改字典中的值

alien['x_position'] = 10
print(alien)
{'color': 'blue', 'x_position': 10, 'y_position': 25}

删除键值对

alien['speed'] = 'medium'
print(alien)
{'color': 'blue', 'x_position': 10, 'y_position': 25, 'speed': 'medium'}
# 删除键值对
del alien['speed']
print(alien)
{'color': 'blue', 'x_position': 10, 'y_position': 25}

由类似对象组成字典

favonlie_languages = {
    'jen': 'python',
    'sarah': 'c',
    'edward': 'swift',
    'phil': 'python',
}
for key, value in favonlie_languages.items():
    print(f'{key.title()}喜欢使用{value.title()}语言编程!')
Jen喜欢使用Python语言编程!
Sarah喜欢使用C语言编程!
Edward喜欢使用Swift语言编程!
Phil喜欢使用Python语言编程!

使用get()方法来访问值

如果你访问的值不存在字典里,你直接访问的话会出现错误,这时候可以使用get()方法来访问,这个方法可以让你在访问不存在的值的时候返回一个默认值。

alien = {'color': 'bule', 'points': 5}
print(alien['speed'])

发生异常: KeyError
'speed'
  File "favonlie_languages.py", line 2, in <module>
    print(alien['speed'])
          ~~~~~^^^^^^^^^
KeyError: 'speed'

print(alien.get('speed', '不好意思,没有这个索引且没有对应的值'))
不好意思,没有这个索引且没有对应的值
alien['speed'] = 'medium'
print(alien.get('speed', '不好意思,没有这个索引且没有对应的值'))
medium

遍历字典

遍历字典中的内容有3个基本的关键词,items()字典中的键和值,有这个方法需要两个临时变量用于遍历字典、keys()遍历字典中的键、values()遍历字典中的值。

遍历所有键值对

like_number = {
    'a': '8',
    'b': '7',
    'c': '5',
    'd': '9',
}
for key, value in like_number.items():
    print(f'{key.title()} like number is {value}')
A like number is 8
B like number is 7
C like number is 5
D like number is 9

遍历所有键

favonlie_languages = {
    'jen': 'python',
    'sarah': 'c#',
    'edward': 'swift',
    'phil': 'python',
}
# 遍历所有键
for name in favonlie_languages.keys():
    print(name.title())
Jen
Sarah
Edward
Phil

# 按特定顺序遍历字典中的所有键
for name in sorted(favonlie_languages.keys()):
    print(f'Thank you {name.title()},you are welcome!')
Thank you Edward,you are welcome!
Thank you Jen,you are welcome!
Thank you Phil,you are welcome!
Thank you Sarah,you are welcome!

# 遍历字典中的所有值
print('编程语言')
for language in favonlie_languages.values():
    print(language)
编程语言
python
c#
swift
python

**set()**方法用于提取列表或集合中的不同元素,如果有相同的只会提取一个。

print('编程语言')
for language in set(favonlie_languages.values()):
    print(language.title())
编程语言
swift
c#
python

# 集合表现形式,一般用花括号,但里面是没有键值对的,里面的元素表现跟列表一样
>>> number = {1,2,3,4,5,6,7,8,9,1,2,3}
>>> number
{1, 2, 3, 4, 5, 6, 7, 8, 9}

嵌套

有些时候需要将多个字典储存在列表中或将列表作为值储存在字典中,这中行为就成为嵌套。

字典列表就是在列表中储存字典可以应用于用户信息等。

alien_0 = {'color': 'red', 'points': 5}
alien_1 = {'color': 'green', 'points': 10}
alien_2 = {'color': 'blue', 'points': 15}
aliens = [alien_0, alien_1, alien_2]
for alien in aliens:
    print(alien)
{'color': 'red', 'points': 5}
{'color': 'green', 'points': 10}
{'color': 'blue', 'points': 15}

# 创建一个用于储存外星人的空列表
aliens = []
# 用for循环创建30个绿色的外星人
for alien_number in range(30):
    new_alien = {'color': 'green', 'points': 5, 'speed': 'slow'}
    aliens.append(new_alien)
# 打印前5个外星人
for alien in aliens[:5]:
    print(alien)
{'color': 'green', 'points': 5, 'speed': 'slow'}
{'color': 'green', 'points': 5, 'speed': 'slow'}
{'color': 'green', 'points': 5, 'speed': 'slow'}
{'color': 'green', 'points': 5, 'speed': 'slow'}
{'color': 'green', 'points': 5, 'speed': 'slow'}
# 显示创建了多少个外星人
print(f'创建了多少个外星人:{len(aliens)}')
创建了多少个外星人:30
# 修改前三个
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)
{'color': 'yellow', 'points': 10, 'speed': 'medium'}
{'color': 'yellow', 'points': 10, 'speed': 'medium'}
{'color': 'yellow', 'points': 10, 'speed': 'medium'}
{'color': 'green', 'points': 5, 'speed': 'slow'}
{'color': 'green', 'points': 5, 'speed': 'slow'}

在字典中储存列表可以用于食品配方等

pizza = {
    'crust': 'think',
    'toppings': ['mushrooms', 'extra cheese']
}
print(
    f'You ordered a {pizza["crust"]}-crust pizza '"with the following toppings:")
for topping in pizza['toppings']:
    print(f'\t{topping}')
You ordered a think-crust pizza with the following toppings:
	mushrooms
	extra cheese

在字典中储存字典

user = {
    'aeinstein': {
        'first': 'albert',
        'last': 'einstein',
        'location': 'princeton',
    },
    'mcurie': {
        'first': 'marie',
        'last': 'curie',
        'location': 'paris',
    }
}
for username, user_info in user.items():
    print(f'\nUsername: {username}')
    full_name = f"{user_info['first']} {user_info['last']}"
    location = user_info['location']

    print(f'\tFullname: {full_name.title()}')
    print(f'\tLocation: {location.title()}')

Username: aeinstein
	Fullname: Albert Einstein
	Location: Princeton

Username: mcurie
	Fullname: Marie Curie
	Location: Paris

小结

本章我们学习了如何定义字典,以及如何使用储存在字典中的信息。然后学习了如何访问和修改字典中的元素,以及如何遍历字典中的信息。还学习了如何遍历字典中的所有键值对、所有的键和所有的值。学习了如何在列表中嵌套字典,在字典中如何嵌套列表,在字典中嵌套字典。

第六章 用户输入和while循环

input()函数的工作原理

input()函数让程序暂停运行,等待用户输入一些文本。获取用户输入后,Python将其赋给一个变量,以便使用。input()函数接受一个参数,即要向用户显示的提示(prompt)。用户输入的内容Python默认为字符串,要想用户输入其他内容需要再input()前面制定类型,比如:要想用户输入的是数值 int(input(“How old are you”))。
**注意:**有些文本编辑器不能运行提示用户输入的程序,要运行他们需要从终端运行。在cmd终端里面直接运行python文件就可以了,格式是:路径>py 文件名和文件后缀。有些python版本需要使用:路径>python 文件名和文件后缀。

>>> message = input("tell me something, and I will repeat it back to you:")
tell me something, and I will repeat it back to you: hello world
>>> print(message)
 hello world
>>>   

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

# 控制用户输入的内容为数字
>>> age = int(input("How old are you: "))
How old are you: 36
>>> age>20
True

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

number = int(input("请输入一个数,我可以判断是奇数还是偶数:"))
if number % 2 == 0:
    print("这个数是偶数!")
else:
    print("这个数是奇数!")
请输入一个数,我可以判断是奇数还是偶数:50
这个数是偶数!

while 循环简介

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

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

让用户选择何时退出

# 只要用户输入的不是‘quit’和‘exit’该循环就继续运行。
prompt = "我们来玩个游戏,看看你知不知道怎么退出这个游戏: "
message = ""
while message != "quit" or message != "exit":
    # 这里添加了一个lower方法目的是让用户输入的内容都变成小写和下面的if进行判断。
    message = input(prompt).lower()
    if message == "quit" or message == "exit":
        print("恭喜你!")
        break
我们来玩个游戏,看看你知不知道怎么退出这个游戏: 2
我们来玩个游戏,看看你知不知道怎么退出这个游戏: 3
我们来玩个游戏,看看你知不知道怎么退出这个游戏: ExiT
恭喜你!

# 使用标志让用户选择何时退出
prompt = "我们来玩个游戏,看看你知不知道怎么退出这个游戏: "
active = True
while active:
    message = input(prompt).lower()
    if message == "quit" or message == "exit":
        print("恭喜你!")
        active = False
    else:
        print("再想想退出的英文是什么?")
我们来玩个游戏,看看你知不知道怎么退出这个游戏: qwe
再想想退出的英文是什么?
我们来玩个游戏,看看你知不知道怎么退出这个游戏: QUIT
恭喜你!

break 退出循环,不管条件满不满足,想立即退出 while 循环,不再运行后面的代码,可以使用 break 语句。上面的例子已经使用过一次了。我们再使用一个特别的例子。break 还可以用来退出遍历列表或字典的 for 循环。

while True:
    message = input("请你说说你喜欢哪个城市: ").lower()
    if message == "quit" or message == "exit":
        print("再见!")
        break
    else:
        print(f"除了{message.title()}还有其他的城市么?")
请你说说你喜欢哪个城市: shang hai
除了Shang Hai 还有其他的城市么?
请你说说你喜欢哪个城市: QUIT
再见!

在循环中使用 continue

要返回循环的开头,并根据条件测试的结果决定是否继续执行循环,可以使用 continue 语句,他不像 break 那样不执行余下的代码并退出整个循环。

number = 0
while number < 10:
    number += 1
    if number % 2 == 0:
        continue
    print(number, end=" ")
1 3 5 7 9 

避免无线循环

while 循环一定要设置退出条件,如果没有退出条件,while 循环就会无止境的一直执行代码。

number = 0
while number < 5:
    print(number)

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

for 循环是一种遍历列表的有效方式,但不应该在 for 循环中修改列表,否则将导致Python难以跟踪其中的元素。要在遍历列表的同时修改它,可使用 while 循环。通过将 while 循环与列表和字典结合起来使用,可收集、存储并组织大量的输入,供以后查看和使用。注意,这里有个有意思的事情,就是列表里面是空的时候它的布尔值是 False ,列表里面有元素的时候,它的布尔值是 True 。

在列表之间移动元素

# 首先创建一个待验证的用户列表
# 和一个用户储存已验证用户的空列表
unconfirmed_users = ['admin', 'zqten', '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())
    
Verifying user: Candace
Verifying user: Zqten
Verifying user: Admin

The following users have been confirmed:
Candace
Zqten
Admin

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

# 删除为特定值的所有列表元素
pets = ['dog', 'cat', 'dog', 'goldfish', 'cat', 'rabbit', 'cat']
print(pets)
while 'cat' in pets:
    pets.remove('cat')
print(pets)
['dog', 'cat', 'dog', 'goldfish', 'cat', 'rabbit', 'cat']
['dog', 'dog', 'goldfish', 'rabbit']

使用用户输入填充字典

可以使用 while 循环提示用户输入任意多的信息。

# 使用用户输入填充字典
responses = {}
# 设置一个标志,指出调查是否继续
polling_active = True
print('这是调查每个人的爱好,请根据提示输入。')
while polling_active:
    # 提示输入被调查者的名字和回答
    name = input("请输入被调查者的名字:")
    response = input("请输入被调查者的回答:")
    # 将输入的数据储存在字典中
    responses[name] = response
    # 询问用户是否继续调查
    repeat = input("是否有人继续参与调查?(yes/no)")
    if repeat == 'no':
        polling_active = False
    # 下面这段代码的目的是让用户输入yes和no,输入其他的会提示错误,如果是只判断退出循环条件的话,可以省略下面。
    elif repeat == 'yes':
        continue
    else:
        while repeat != 'yes' or repeat != 'no':
            if repeat == 'no':
                polling_active = False
                break
            elif repeat == 'yes':
                break
            else:
                print("输入错误,请重新输入")
                repeat = input("是否有人继续参与调查?(yes/no)")
        if repeat == 'no':
            polling_active = False

# 输出调查结果
print('\n---调查结果---')
for name, response in responses.items():
    print(f"{name}的爱好是: {response}。")

这是调查每个人的爱好,请根据提示输入。
请输入被调查者的名字:刘翔
请输入被调查者的回答:跨栏
是否有人继续参与调查?(yes/no)不知道
输入错误,请重新输入
是否有人继续参与调查?(yes/no)yes
请输入被调查者的名字:姚明
请输入被调查者的回答:篮球
是否有人继续参与调查?(yes/no)不知道
输入错误,请重新输入
是否有人继续参与调查?(yes/no)no

---调查结果---
刘翔的爱好是: 跨栏。
姚明的爱好是: 篮球。

小结

本章学习了如何在程序中使用 input() 来让用户提供信息,如何处理文本和数的输入,以及如何使用 while 循环让程序按用户的要求不断地运行。然后见识了多种控制 while 循环流程的方式:设置活动标志,使用 break 语句,以及使用 continue 语句。还学习了如何使用 while 循环在列表之间移动元素,以及如何从列表中删除所有包含特定值的元素。最后,学习了如何结合使用 while 循环和字典。

第七章 函数

函数是带名字的代码块,用于完成具体的工作。要执行函数定义的特定任务,可调用(call)该函数。当需要再程序中多次执行同一项任务时,无须反复编写完成该任务的代码,只需要调用执行该任务的函数,让Python运行其中的代码即可。

定义函数

定义函数的关键字是 def 。后面是函数名和括号,没有参数时可以是空括号,然后和 for 、while 一样也需要冒号,表示定义完成,换行后缩进表示要执行的代码块。

def greet_user():
    '''显示简单的问候语'''
    print("Hello World!")

greet_user()
Hello World!

向函数传递信息

def greet_user(uesrname):
    '''显示简单的问候语'''
    print(f"Hello,{uesrname}!")


greet_user('zqten')
Hello,zqten!

实参和形参

在上面的例子中,uesrname是一个形参,即函数完成工作所需要的信息。
而 ‘zqten’ 则是实参,即在调用函数时传递给函数的信息。

传递实参

传递实参有两种方式,一种是按照位置顺序传递,另一种是按照关键字传递。按照关键字传递实参不用考虑顺序,但要记得关键字。

def describe_pet(animal_type, pet_name: str):
    '''显示宠物信息'''
    print(f'\nI have a {animal_type}.')
    print(f"My {animal_type}'s name is {pet_name.title()}.")
    
# 按位置顺序传递实参
describe_pet('cat', 'duoduo')
I have a cat.
My cat's name is Duoduo.

# 按关键字传递实参
describe_pet(pet_name='nai cha', animal_type='dog')
I have a dog.
My dog's name is Nai Cha.

默认值

有些时候你想改变某个参数,但有些时候你想让这个参数先有一个默认值,后面调用的时候看情况是否修改。这种情况可以为参数设置一个默认值。如果没有设置默认值,在调用的时候也没有传递参数 Python 就会报错。

def describe_pet(animal_type='cat', pet_name='duo duo'):
    '''显示宠物信息'''
    print(f'\nI have a {animal_type}.')
    print(f"My {animal_type}'s name is {pet_name.title()}.")
# 有默认值的情况下,可以不用传递参数,也可以看需要传递
describe_pet()
I have a cat.
My cat's name is Duo Duo.

describe_pet('dog', 'nai cha')
I have a dog.
My dog's name is Nai Cha.

返回值

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

返回简单的值

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)
Jimi Hendrix 

让实参变成可选

就是先设置参数的默认值,但这个默认值是一个空值。不需要的时候就不会出现,需要的时候只需要在调用的时候传递一个参数就可以。

def get_formatted_name(first_name, last_name, middle_name=''):
    '''返回标准格式的姓名'''
    full_name = f'{first_name} {last_name} {middle_name}'
    return full_name.title()

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

musician = get_formatted_name('jimi', 'hendrix', 'YYY')
print(musician)
Jimi Hendrix Yyy

返回字典

这里有个小小的细节,就是 age=None 是一个布尔值并且是 False 。意思就是这个参数并没有值返回 False 。如果为这个参数传递了一个值,它就返回 True 。

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', 'hendeix')
print(musician)
{'first': 'jimi', 'last': 'hendeix'}

musician = build_person('jimi', 'hendeix', age=36)
print(musician)
{'first': 'jimi', 'last': 'hendeix', 'age': 36}

结合使用函数和 while 循环

def get_formatted_name(first_name, last_name, middle_name=''):
    '''返回标准格式的姓名'''
    full_name = f'{first_name} {last_name} {middle_name}'
    return full_name.title()

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

    f_name = input("你姓什么:")
    if f_name == 'q':
        break
    l_name = input("你的名字是什么:")
    if l_name == 'q':
        break

    full_name = get_formatted_name(f_name, l_name)
    print(f"你好, {full_name}")

Pleasr tell me your name:
(enter 'q' at any time to quit.)
你姓什么:yao
你的名字是什么:ming
你好, Yao Ming 

Pleasr tell me your name:
(enter 'q' at any time to quit.)
你姓什么:q

传递列表

将列表传递给函数后,函数就能直接访问其内容,进行修改等等操作。

def greet_users(names):
    '''向列表中的每个用户发出简单的问候'''
    for name in names:
        print('Hello, %s!' % name)

username = ['zqten', 'zhengjizhong', 'zhengkainan', 'zhouhuarong']
greet_users(username)

Hello, zqten!
Hello, zhengjizhong!
Hello, zhengkainan!
Hello, zhouhuarong!

在函数中修改列表这个程序演示了一个概念:每个函数都应只负责一项具体工作。这有助于将复杂的任务分解成一系列简单的步骤。

unprinted_designs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
completed_models = []

def print_models(unprinted_designs, completed_models):
    '''
    模拟打印每个数字,直到没有未打印的数字为止
    打印每个数字后,都将其移到列表completed_models中
    '''
    while unprinted_designs:
        current_design = unprinted_designs.pop(0)
        print('Printing model: %s' % current_design)
        completed_models.append(current_design)

def show_completed_models(completed_models):
    '''显示打印好的所有数字'''
    print('\nThe following models have been printed:')
    for completed_model in completed_models:
        print(completed_model, end=' ')

print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)
print(unprinted_designs)

Printing model: 1
Printing model: 2
Printing model: 3
Printing model: 4
Printing model: 5
Printing model: 6
Printing model: 7
Printing model: 8
Printing model: 9
Printing model: 10

The following models have been printed:
1 2 3 4 5 6 7 8 9 10 []
# 如果不希望传递后列表为空,可以使用副本传递参数 [:] 切片传递
print_models(unprinted_designs[:], completed_models)
show_completed_models(completed_models)
print(unprinted_designs)

Printing model: 1
Printing model: 2
Printing model: 3
Printing model: 4
Printing model: 5
Printing model: 6
Printing model: 7
Printing model: 8
Printing model: 9
Printing model: 10

The following models have been printed:
1 2 3 4 5 6 7 8 9 10 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

传递任意数量的参数

“ * ” 带形参名中的星号让Python创建一个名为形参名的元组,该元组包含函数收到的所有值。

def make_pizza(*toppings):
    '''比如概述要制作的披萨'''
    print('\nMaking a pizza with the following toppings:')
    for topping in toppings:
        print(topping)

make_pizza('aaa')
make_pizza('mushrooms', 'extra cheese')

Making a pizza with the following toppings:
aaa

Making a pizza with the following toppings:
mushrooms
extra cheese

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

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

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, 'aaa')
make_pizza(19, 'mushrooms', 'extra cheese')

Making a 16-inch pizza with the following toppings:
- aaa

Making a 19-inch pizza with the following toppings:
- mushrooms
- extra cheese

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

“ ** ” 带形参名中的两个星号让Python创建一个名为形参名的字典,该字典包含函数收到的所有的键值对。

def build_profile(first, last, **user_info):
    '''创建一个字典,其中包含我们知道的有关用户的一切'''
    user_info['first_name'] = first
    user_info['last_name'] = last
    return user_info

user_profile = build_profile(
    'zheng', 'ji zhong', age=11, height='147cm', weight='48kg')
print(user_profile)
{'age': 11, 'height': '147cm', 'weight': '48kg', 'first_name': 'zheng', 'last_name': 'ji zhong'}

将函数存储在模块中

将函数存储在称为模块的独立文件中,再将模块导入(import)主程序。可以让代码看起来容易理解。

导入整个模块

要让函数是可导入的,得先创建模块。模块是扩展名为 .py 的文件。

import pizza
pizza.make_pizza(12, 'zhu rou')
pizza.make_pizza(17, 'niu rou', 'bai cai')

Making a 12-inch pizza with the following toppings:
- zhu rou

Making a 17-inch pizza with the following toppings:
- niu rou
- bai cai

导入特定的函数

只想导入模块中的特定函数。

from pizza import make_pizza

make_pizza(12, 'zhu rou')
make_pizza(16,'niu rou', 'bai cai')

Making a 12-inch pizza with the following toppings:
- zhu rou

Making a 16-inch pizza with the following toppings:
- niu rou
- bai cai

使用 as 给函数指定别名

如果要导入的函数的名称太长或者可能与程序中的其他名称有冲突,可指定简短而独一无二的别名。这个方法也可以应用到给模块指定别名。

from pizza import make_pizza as mp

mp(12, 'zhu rou')
mp(16, 'niu rou', 'bai cai', 'fanqie')

Making a 12-inch pizza with the following toppings:
- zhu rou

Making a 16-inch pizza with the following toppings:
- niu rou
- bai cai
- fanqie

# 也可以给模块指定别名
import pizza as p

p.make_pizza(12, 'zhu rou')
p.make_pizza(16, 'niu rou', 'bai cai', 'fanqie')

Making a 12-inch pizza with the following toppings:
- zhu rou

Making a 16-inch pizza with the following toppings:
- niu rou
- bai cai
- fanqie

导入模块中的所有函数

这种方法一般慎用,因为如果模块中有函数的名称与当前项目中的名称相同,可能导致意想不到的结果。最好的做法是要么只导入需要使用的函数,要么导入整个模块并使用点号调用

from pizza import *

make_pizza(12, 'zhu rou')
make_pizza(16, 'niu rou', 'bai cai', 'fanqie')

Making a 12-inch pizza with the following toppings:
- zhu rou

Making a 16-inch pizza with the following toppings:
- niu rou
- bai cai
- fanqie

# 导入其他文件夹的模块
from 文件夹名称 import 模块
from 文件夹名称.模块 import 函数

函数编写指南

在编写函数时需要牢记几个细节。

  • 应给函数指定描述性的名称,且只使用小写字母和下划线。
  • 每个函数都应包含简要阐述其功能的注释。意思就是每个函数都需要编写使用说明的注释。
  • 形参指定默认值时,等号两边不要有空格。
  • 如果程序或模块包含多个函数,可以使用两个空行将函数隔开。
  • 所有的 import 语句都应该放在文件开头。唯一的例外是,你要在文件开头编写整个程序的注释。
  • 导入自己边学的模块时,尽量把模块和程序放在一个文件夹。

小结

本章学习了如何编写函数,以及如何传递实参,让函数能够访问完成工作所需的信息。然后学习了如何使用位置实参和关键字实参,以及如何接受任意数量的实参,学习了显示输出的函数和返回值的函数,知道了如何将函数与列表、字典、if、语句和 while 循环结合起来使用,以及如何将函数存储在称为模块的独立文件中,让程序文件更简单、更易于理解。最后,了解了函数编写指南,遵循这些指南可让程序始终保持良好的结构。
程序员的目标之一是编写简单的代码来完成任务,而函数有助于实现这样的目标。

第八章 类

面向对象编程(object-oriented-programming, OOP)是最有效的软件编写方法之一。在基于类创建对象时,每个对象都自动具备类定义的通用行为。然后,你可根据需要赋予每个对象独特的个性。根据类来创建对象称为实例化,这让你能够使用类的实例。

面相对象变成有助于你像程序员那样看世界,并且真正明白自己编写的代码:不仅是各行代码的作用,还有代码背后更宏大的概念。了解类背后的概念可培养逻辑思维能力,让你能够通过编写程序来解决遇到的几乎任何问题。

创建和使用类

创建Dog类

# 创建Dog类
class Dog:
    '''模拟小狗的简单尝试'''

    def __init__(self, name, age):
        '''初始化属性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.")

init()”方法是类的初始化方法,类中的函数称为方法。这个方法的开头和结尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。
这个方法定义成包含三个形参:self 、name、age。这个方法中self 是必不可少,而且必须位于其他形参的前面。self就是类的自身,当我们实例化这个类时,我们传递的参数会通过 self 传递给类自身。self.name = name 里面 self 前缀的变量可供类中的所有方法使用。

根据类创建实例

可以将类视为有关如何创建实例的说明。😋

# 创建实例
my_dog = Dog('duoduo', 7)
# my_dog.name 就是访问实例的属性
print(f"My dog's name is {my_dog.name}")
print(f"My dog's age is {my_dog.age}")

My dog's name is duoduo
My dog's age is 7
# 调用方法
my_dog.sit()
my_dog.roll_over()

duoduo is now sitting.
duoduo rolled over.
# 创建多个实例
your_dog = Dog('Lucy', 4)
print(f"Your dog's name is {your_dog.name}")
print(f"Your dog's age is {your_dog.age}")
your_dog.sit()

Your dog's name is Lucy
Your dog's age is 4
Lucy is now sitting.

使用类和实例

Car 类

# Car类
class Car:
    '''模拟汽车的简单尝试'''

    def __init__(self, make, model, year):
        '''初始化描述汽车属性'''
        self.make = make
        self.model = model
        self.year = year
# 给属性默认值
        self.odometer = 0
        self.oli = 240

    def get_descriptive_name(self):
        '''返回格式规范的描述性信息'''
        long_name = f"{self.year}{self.make}生产{self.model}"
        return long_name.title()

    def read_odometer(self):
        '''返回汽车里程数'''
        print(f"这两车已经行驶了{self.odometer}公里了。")
        return self.odometer

    def update_odometer(self, mileage):
        '''设置汽车里程数'''
        if mileage >= self.odometer:
            self.odometer = mileage
            print(f"这辆车已经行驶了{self.odometer}公里了。")
        else:
            print("你不能往回设置公里数")

    def increment_odometer(self, mileage):
        '''增加汽车里程数'''
        self.odometer += mileage
        print(f"这辆车已经行驶了{self.odometer}公里了。")
        
    def car_oli_v(self, oli):
        '''描述车辆油箱的容积'''
        self.oli = oli
        print(f'这辆车的油箱容积是{self.oli}L.')

my_new_car = Car('奥迪汽车公司', '奥迪A4', '2003')
print(my_new_car.get_descriptive_name())
# 修改属性值
my_new_car.odometer = 1
my_new_car.read_odometer()
# 通过方法修改属性值
my_new_car.update_odometer(34)
my_new_car.increment_odometer(12)
my_new_car.update_odometer(12)

2003年奥迪汽车公司生产奥迪A4
这两车已经行驶了1公里了。
这两车已经行驶了34公里了。
这辆车已经行驶了46公里了。
你不能往回设置公里数

继承

编写类的时候并非总是要从头开始,如果要编写的类是一个已经存在的类的特殊版本,可以使用继承。当一个类继承另一个类时,将自动获得后者的素有属性和方法。原有类称为父类,而新的类称为子类。子类不仅继承了父类的所有属性和方法,还可以定义自己的属性和方法。

子类初始化方法

class ElectricCar(Car):
    '''模拟电动汽车'''

    def __init__(self, make, model, year):
        '''初始化父类属性,在初始化电动汽车特有的属性'''
        super().__init__(make, model, year)


my_leaf = ElectricCar('nissan', 'leaf', '2024')
print(my_leaf.get_descriptive_name())

2024年Nissan生产Leaf

给子类定义属性和方法

class ElectricCar(Car):
    '''模拟电动汽车'''

    def __init__(self, make, model, year):
        '''初始化父类属性,在初始化电动汽车特有的属性'''
        super().__init__(make, model, year)
        self.battery_size = 40

    def describe_battery(self):
        '''打印一条描述电池容量的消息'''
        print(f'This car has a {self.battery_size}-KWh battery.')


my_leaf = ElectricCar('nissan', 'leaf', '2024')
print(my_leaf.get_descriptive_name())
my_leaf.describe_battery()

2024年Nissan生产Leaf
This car has a 40-KWh battery.

重写父类中的方法

class ElectricCar(Car):
    '''模拟电动汽车'''

    def __init__(self, make, model, year):
        '''初始化父类属性,在初始化电动汽车特有的属性'''
        super().__init__(make, model, year)
        self.battery_size = 40

    def describe_battery(self):
        '''打印一条描述电池容量的消息'''
        print(f'This car has a {self.battery_size}-KWh battery.')

    def car_oli_v(self):
        '''电动汽车没有油箱'''
        print("电动汽车没有油箱")


my_leaf = ElectricCar('nissan', 'leaf', '2024')
print(my_leaf.get_descriptive_name())
my_leaf.describe_battery()
my_leaf.car_oli_v()

2024年Nissan生产Leaf
This car has a 40-KWh battery.
电动汽车没有油箱

将实例用作属性

class ElectricCar(Car):
    '''模拟电动汽车'''

    def __init__(self, make, model, year):
        '''初始化父类属性,在初始化电动汽车特有的属性'''
        super().__init__(make, model, year)
        self.battery = Battery()

    def car_oli_v(self):
        '''电动汽车没有油箱'''
        print("电动汽车没有油箱")


class Battery:
    def __init__(self, battery_size=40):
        '''初始化电池的属性'''
        self.battery_size = battery_size

    def describe_battery(self):
        '''打印一条描述电池容量的消息'''
        print(f'This car has a {self.battery_size}-KWh battery.')


my_leaf = ElectricCar('nissan', 'leaf', '2024')
print(my_leaf.get_descriptive_name())
my_leaf.battery.describe_battery()

2024年Nissan生产Leaf
This car has a 40-KWh battery.

导入类

主要目的是让文件整洁,我们可以将类存储在模块中,然后在主程序导入所需的模块。我们需要整理一下代码,把上面我们学的三个类都复制到一个文件,命名为 car.py 。

# Car类
class Car:
    '''模拟汽车的简单尝试'''

    def __init__(self, make, model, year):
        '''初始化描述汽车属性'''
        self.make = make
        self.model = model
        self.year = year
        self.odometer = 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"这辆车已经行驶了{self.odometer}公里了。")
        return self.odometer

    def update_odometer(self, mileage):
        '''设置汽车里程数'''
        if mileage >= self.odometer:
            self.odometer = mileage
            print(f"这辆车已经行驶了{self.odometer}公里了。")
        else:
            print("你不能往回设置公里数")

    def increment_odometer(self, mileage):
        '''增加汽车里程数'''
        self.odometer += mileage
        print(f"这辆车已经行驶了{self.odometer}公里了。")
        
# 电池模组
class Battery:
    def __init__(self, battery_size=40, car_model_isoli=False):
        '''初始化电池的属性'''
        self.battery_size = battery_size
        '''默认是电动汽车,如果是汽油车可以设置会Ture.'''
        self.car_model_isoli = car_model_isoli

    def describe_battery(self):
        '''打印一条描述电池容量的消息'''
        if self.car_model_isoli is True:
            print('这是一辆油车,没有电池.')
        else:
            print(f'This car has a {self.battery_size}-KWh battery.')

    def update_battery(self):
        '''升级电池容量'''
        if self.battery_size != 65:
            self.battery_size = 65

    def get_range(self):
        '''打印一条消息,指出汽车的续航里程'''
        if self.battery_size == 40:
            range = 150
        elif self.battery_size == 65:
            range = 225
        print(f'这辆车的续航里程为{range}公里.')

# 电动车
class ElectricCar(Car):
    '''模拟电动汽车'''

    def __init__(self, make, model, year):
        '''初始化父类属性,在初始化电动汽车特有的属性'''
        super().__init__(make, model, year)
        self.battery = Battery()

导入单个类

from car import Car

导入多个类

from car import Car, ElectricCar

导入整个模块

这里我把上面的代码分成了两个文件,Car类单独储存成 car.py ,电池模组和电动车储存为 electric_car.py 。

import car
# 用法就是用点引用该模块的类
my_car = car.Car("柳州五菱", "五菱之光", 2008)

# 还有下面这种方法,但是不推荐用,因为可能会引起很多难以诊断的错误,比如出现同名的类
# 这种方法是导入模块的所有类
from car import *

使用别名

就是给导入的模块或类更改使用名,这并不会影响该模块和类的代码。

# 给模块使用别名
import electric_car as ec
# 给类使用别名
from electric_car import ElectricCar as EC

合适的工作流程

首先尝试在一个文件中完成所有工作,确定一切都能正确运行后,在将类移到独立的模块中。

这里讲一下 VMC 模式:

  • V: 就是视窗,用户界面等可视化的代码。
  • M:就是模块或类等等的代码。
  • C:就是整个程序的运行逻辑,流程控制等等的代码。

这个模式可以简单的理解为建立三个文件夹,分别存放这三种类型的文件。这样可以让自己的代码更加简洁高效,别人理解也更容易。

Python标准库

Python标准库是一组模块,在安装Python时已经包含在内了。我们可以使用标准可中的任何函数和类。查看Python标准库,可以在命令行输入 help(“modules”) 命令。

# 随机数模组,返回两个数之间的随机一个数。
>>> from random import randint
>>> randint(1,6)
4   
# 返回一个列表里随机一个元素
>>> from random import choice 
>>> players = ['aaa','bbb','ccc','ddd']
>>> choice(players)
'ccc'

类的编程风格

编写复杂程序时采用以下几项:

  • 类名:驼峰命名法。例:MyNewCar 。类名不使用下划线。
  • 模块名和实例名都采用全小写格式,并在单词之间采用下划线。
  • 每个类都要在定义后面和函数一样需要描述类功能的文档字符串。
  • 当需要导入标准库中的模块和自己编写的模块时,优先导入标准库中的模块,在导入自己编写的模块。

小结

本章我们学习了如何编写类,如何使用属性在类中存储的信息,以及如何编写方法让类具备所需的行为。然后学习了 init 初始化方法。了解了如何修改实例的属性,包括直接修改以及通过方法修改。还了解到使用继承可简化相关类的创建工作,将一个类的实例用作另一个类的属性能让类更简洁。
明白了,通过将类存储在模块(文件)中,并在需要使用这些类的文件中导入它们,可让项目变的更简洁。开始了解python标准库,还看了一个random模块,最后学习了编写类时应遵循的Python约定。

第九章 文件和异常

处理文件,让程序能够快速地分析大量数据;错误处理,避免程序在面对意外情况时崩溃;异常是Python创建特殊对象,用于管理程序运行时出现的错误;还将学习使用 json 模块保存用户数据,以免这些数据在程序结束运行后丢失。

读取文件

读取文件对数据分析应用程序很有用。要使用文本文件中的信息,首先需要将信息读取到内存中。既可以一次性读取文件的全部内容,也可以逐行读取。

读取文件的全部内容

# 这是一个txt文件
3.1415926535
  8979323846
  2643383279
# 读取文件
from pathlib import Path
# 这里值得注意的是VScode里面用的斜杠是反的
path = Path("E:/text_files/pi_digits.txt")
contents = path.read_text()
# 删除文档中的空格
# contents = contents.strip()
print(contents)

3.1415926535
  8979323846
  2643383279

相对文件路径和绝对文件路径

相对文件路径让 Python 到相对于当前运行的程序所在目录的指定位置去查找。比如上面的文件可以这样读取

from pathlib import Path
path = Path("text_files/pi_digits.txt")

绝对文件路径可以读取系统中任何地方的文件。

from pathlib import Path
path = Path("E:/text_files/pi_digits.txt")

现在最简单的做法是,要么将数据文件存储在程序文件所在的目录中,要么将其存储在存续文件所在目录下的一个文件夹中。

注意:在显示文件路径时,windows 系统使用反斜杠( \ )而不是斜杠( / )但是你在代码中应该始终使用斜杠( / ),即便在windows系统中也是如此。在与你或其他用户的系统交互时,pathlib 库会自动使用正确的路径表示方法。

访问文件中的各行

使用 splitlines() 方法可以将字符串转换为一系列行,在使用 for 循环遍历文件中的每一行,splitlines() 方法返回一个列表,其中包含文件中所有的行。可以把这个列表赋值给变量。

from pathlib import Path

path = Path("E:/text_files/pi_digits.txt")
contents = path.read_text()
lines = contents.splitlines()

for line in lines:
    print(line)

3.1415926535
  8979323846
  2643383279

使用文件的内容

读取文件后才能使用这些数据。

from pathlib import Path

path = Path("E:/text_files/pi_digits.txt")
contents = path.read_text()
pi_string = ''
lines = contents.splitlines()
for line in lines:
    pi_string += line

print(pi_string)
print(len(pi_string))
3.1415926535  8979323846  2643383279
36

# 删除左边空格
--snip--
for line in lines:
    pi_string += line.lstrip()

print(pi_string)
print(len(pi_string))
3.141592653589793238462643383279
32

注意:读取文本文件时,python 将其中的所有文本都解释为字符串。如果读取的是数,并且要将其作为数字使用,就必须使用 int() 函数将其转换为整数,或者使用 float() 函数将其转为浮点数。

大型文件可以做切片等列表的操作。

--snip--
for line in lines:
    pi_string += line.lstrip()

print(pi_string[:10])
print(len(pi_string))
3.14159265
32

趣味小练习

# 圆周率中包含你的生日吗
from pathlib import Path

path = Path("pi_million_digits.txt")
contents = path.read_text()

pi_string = ''
lines = contents.splitlines()
for line in lines:
    pi_string += line.strip()

birthday = input("你的生日如(20200506):")
if birthday in pi_string:
    print("圆周率包含了你的生日")
else:
    print("圆周率没有你的生日哦...")
    
你的生日如(20200506):20200506
圆周率没有你的生日哦...

写入文件

保存数据的最简单的方式之一是将其写入文件。

写入一行

注意:在python调用 write_text() 方法时,如果指定文件已存在,这个方法会将其内容替换为你要写入的内容。**replace()**方法可以将字符串中的特定单词替换为另一个单词。

# 写入一行文字
from pathlib import Path
path = Path('programming.txt')
path.write_text("hello world")

programming.txt
hello world
# 替换字符串 replace()
>>> a = 'How are you?'
>>> a.replace('you','me')
'How are me?'

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

写入多行

from pathlib import Path
path = Path('programming.txt')
contents = "nihao"
contents += "\nwohao"
contents += "\ndajiahao"
path.write_text(contents)

programming.txt
nihao
wohao
dajiahao

趣味小练习

# 重复写入多个词语
from pathlib import Path
path = Path("guest.txt")
n = 0
c = ''
while n != 5:
    b = input("请输入内容:")
    c += f"{b}\n"
    n += 1
    path.write_text(f"{c}", encoding='utf-8')

异常

异常是使用 try-except 代码块处理的。也就是说你运行一段代码,如过出错了会根据你编写的代码执行,如果没有对异常进行处理,程序出错了就会停止。如果你运行一段代码出错,编辑器会提示你是什么错误,你可以针对这个错误给出处理方法,就像下面的 0 不能作为除数的错误是:ZeroDivisionError

print(5/0)
ZeroDivisionError: division by zero

使用 try-except 代码块

当你认为可能会发生错误的时候,可以使用这个代码块来处理可能引发的错误。

try:
    print(5/0)
except ZeroDivisionError:
    print("0不能作为除数。")
    
0不能作为除数。

处理 FileNotFoundError 异常

from pathlib import Path

path = Path("alice.txt")
try:
    contents = path.read_text(encoding='utf-8')
except FileNotFoundError:
    print("没有找到这个文件。")

没有找到这个文件。

分析文本

split() 方法是把一个很长的字符串转换成很多单词的列表的一个方法。

from pathlib import Path

path = Path("alice.txt")
try:
    contents = path.read_text(encoding='utf-8')
except FileNotFoundError:
    print("没有找到这个文件。")
else:
    # 计算文件大概包含多少个单词
    words = contents.split()
    num_words = len(words)
    print(f"The file {path} has about {num_words} words")

读取多个文件

利用函数我们可以读取多个文件

from pathlib import Path

def count_words(path):
    try:
        contents = path.read_text(encoding='utf-8')
    except FileNotFoundError:
        print(f"没有找到{path}这个文件。")
    else:
        # 计算文件大概包含多少个单词
        words = contents.split()
        num_words = len(words)
        print(f"The file {path} has about {num_words} words")


filenames = ['alice.txt', 'little_women.txt', 'moby_dick.txt', 'kkk.txt']
for filename in filenames:
    path = Path(filename)
    count_words(path)
   
The file alice.txt has about 29594 words
The file little_women.txt has about 189142 words
The file moby_dick.txt has about 215864 words
没有找到kkk.txt这个文件。

静默失败

并非每次错误都需要告诉用户,有时候有错误什么也不做,但是就是让程序继续运行可以使用 pass 语句。

def count_words(path):
    try:
        contents = path.read_text(encoding='utf-8')
    except FileNotFoundError:
        pass
    else:
        # 计算文件大概包含多少个单词
        words = contents.split()
        num_words = len(words)
        print(f"The file {path} has about {num_words} words")

filenames = ['alice.txt', 'little_women.txt', 'moby_dick.txt', 'kkk.txt']
for filename in filenames:
    path = Path(filename)
    count_words(path)

The file alice.txt has about 29594 words
The file little_women.txt has about 189142 words
The file moby_dick.txt has about 215864 words

存储数据

json 模块能够将简单的Python数据结构转换为JSON格式的字符串,并在程序再次运行时从文件中加载数据。

**注意:**JSON格式最初是为 JavaScript 开发的,但后来成为了一种通用格式,被众多语言采用。

json.dumps() 和 json.loads()

json.dumps() 接受一个参数,即要转换为JSON格式的数据。这个函数返回一个字符串。

# 存储数据 json.dumps()
from pathlib import Path
import json

number = [1, 2, 3, 5, 6, 7, 4, 8, 9]

path = Path('number.json') # 文件路径,如果没有就会自动创建这个文件
contents = json.dumps(number) # 数据通过json.dumps()转化文JSON格式,存储在变量contents中
path.write_text(contents) # 把contents中的数据写入number.json这个文件中
# number.json文件内容
[1, 2, 3, 5, 6, 7, 4, 8, 9]

# 读取数据 json.loads()
from pathlib import Path
import json

path = Path('number.json') # 文件路径,如果没有就会自动创建这个文件
contents = path.read_text() # 把读取的数据存储在变量contents中
number = json.loads(contents)# contents中的数据通过json.loads()转化文JSON格式,并存储在变量number中
print(number)
[1, 2, 3, 5, 6, 7, 4, 8, 9]

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

保存数据很有必要,因为如果不以某种方式进行存储,用户的信息就会在程序停止运行时丢失。

# 保存用户数据
from pathlib import Path
import json

username = input("请输入你的名字:")
path = Path('username.json')
contents = json.dumps(username)
path.write_text(contents, encoding='utf-8')
print("我们将储存你的名字。")
请输入你的名字:zqten
我们将储存你的名字。
# username.json文件
"zqten"

# 读取用户生成的数据
from pathlib import Path
import json
path = Path("username.json")
contents = path.read_text()
username = json.loads(contents)
print(f"Welcome back {username}")
Welcome back zqten

Path 类提供了很多有用的方法。如果指定的文件或文件夹存在,exists() 方法返回 True ,否则返回 False 。

from pathlib import Path
import json

path = Path('username.json')
if path.exists():
    contents = path.read_text()
    username = json.loads(contents)
    print(f"Welcome back {username}")
else:
    username = input("请输入你的名字:")
    contents = json.dumps(username)
    path.write_text(contents, encoding='utf-8')
    print(f"我们将储存你的名字。{username}")
Welcome back zqten

重构

# 保存用户数据
from pathlib import Path
import json

def get_stored_username(path):
    '''如果用户存储了用户名,我们就获取他'''
    if path.exists():
        contents = path.read_text()
        username = json.loads(contents)
        return username
    else:
        return None

def get_new_username(path):
    '''提示用户输入用户名'''
    username = input("请输入你的名字:")
    contents = json.dumps(username)
    path.write_text(contents, encoding='utf-8')
    return username

def greet_user():
    '''问候用户,并指出其名字'''
    path = Path('username.json')
    username = get_stored_username(path)
    if username:
        print(f"Welcome back {username}")
    else:
        username = get_new_username(path)
        print(f"我们将储存你的名字,{username}")

greet_user()
请输入你的名字:zqten
我们将储存你的名字,zqten
# 在运行一次
greet_user()
Welcome back zqten

小结

本章学习了如何使用文件,包括如何读取整个文件,如何读取文件中的各行,以及如何根据需要将任意数量的文本写入文件。然后学习了异常,以及如何处理程序可能引发的异常。最后学习了如何存储Python数据结构,以保存用户提供的信息,避免让用户在每次运行程序时都重新提供。

第十章 测试代码

本章使用的是pip安装的pytest库来进行测试代码。

# 在控制台安装
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pytest

测试函数

测试基本分为下面几种:

  • 单元测试,用于核实函数的某个方面没有问题。
  • 测试用例,是一组单元测试,核实函数在各种情况下的行为都符合要求。
  • 全覆盖,测试一整套单元测试,涵盖了各种可能的函数使用方式。

测试函数,需要新建一个文件,导入需要测试的函数,然后定义一个测试函数(这个函数的命名规范是:必须以 test 加下划线打头)。在测试过程中,pytest会找出并运行所有以 test 加下划线打头的函数。运行测试,需要在终端进入到要测试的程序的文件夹,输入pytest就会出现下面的测试内容。

# name_function.py 文件
def get_formatted_name(frist, last, middle=''):
    if middle:
        full_name = f"{frist} {middle} {last}"
    else:
        full_name = f"{frist} {last}"
    return full_name.title()

# test_name_function.py 测试文件
from name_function import get_formatted_name

def test_frist_last_name():
    formatted_name = get_formatted_name('janis', 'joplin')
    assert formatted_name == 'Janis Joplin'
    
# 运行测试,需要在终端进入到要测试的程序的文件夹,输入pytest就会出现下面的测试内容。
======================== test session starts ========================
platform win32 -- Python 3.11.3, pytest-7.4.0, pluggy-1.3.0
rootdir: E:Python\第十章测试代码
collected 1 item

test_name_function.py .                                     [100%]

======================== 1 passed in 0.01s ==========================


# 未通过测试示例
===================== test session starts ============================
platform win32 -- Python 3.11.3, pytest-7.4.0, pluggy-1.3.0
rootdir: E:Python\第十章测试代码
collected 1 item

test_name_function.py F                                         [100%]

====================== FAILURES ====================================== 
__________________ test_frist_last_name_______________________________ 

    def test_frist_last_name():
        formatted_name = get_formatted_name('janis', 'joplin')
>       assert formatted_name == 'Janis Joplil'
E       AssertionError: assert 'Janis Joplin' == 'Janis Joplil'
E         - Janis Joplil
E         ?            ^
E         + Janis Joplin
E         ?            ^

test_name_function.py:6: AssertionError
============= short test summary info =========================================== 
FAILED test_name_function.py:
:test_frist_last_name - AssertionError: assert 'Janis Joplin' == 'Janis Joplil'
============= 1 failed in 0.05s ================================================= 

测试类

上面是测试了函数,现在我们针对类进行测试。

各种断言:

  • assert a == b 断言两个值相等
  • assert a != b 断言两个值不等

这里只列出了两个,测试能包含任意可用条件语句表示的断言,比如 not 、in等等。

要测试的类

# 测试类 survey.py
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("所有问卷结果:")
        for response in self.responses:
            print(f"- {response}")

# 实例化 language_survey.py
# survey类的实例
from survey import AnonymousSurvey

# 定义个问题,并创建一个表示调查的 AnonymousSurvey 对象
question = "你学习了几种语言?"
language_survey = AnonymousSurvey(question)

# 显示问题并存储答案
language_survey.show_question()
print("按'q'退出。\n")
while True:
    response = input("输入你学习的语言: ")
    if response == "q":
        break
    language_survey.store_response(response)

# 显示答案
print("\n你学习的语言有")
language_survey.show_results()

你学习了几种语言?
按'q'退出。       

输入你学习的语言: 汉语
输入你学习的语言: 英语
输入你学习的语言: 日语
输入你学习的语言: 德语
输入你学习的语言: q

你学习的语言有
所有问卷结果: 
- 汉语        
- 英语        
- 日语        
- 德语  

测试AnonymousSurvey类

# 测试AnonymousSurvey类 test_survey.py
from survey import AnonymousSurvey


def test_store_single_response():
    '''测试单个答案会被妥善地存储'''
    question = "What language did you first learn to speak?"
    language_survey = AnonymousSurvey(question)
    language_survey.store_response('English')
    assert 'English' in language_survey.responses
    
==================== test session starts ============================== 
platform win32 -- Python 3.11.3, pytest-7.4.0, pluggy-1.3.0
rootdir: E:Python\第十章测试代码
collected 1 items                                                                                                                       

test_survey.py .                             	  [100%] 

===================== 1 passed in 0.02s ===============================     

def test_store_three_response():
    '''测试多个答案会被妥善地存储'''
    question = "What language did you first learn to speak?"
    language_survey = AnonymousSurvey(question)
    responses = ['English', 'Spanish', 'Chinese']
    for response in responses:
        language_survey.store_response(response)

    for response in responses:
        assert response in language_survey.responses

========================= test session starts ===============================
platform win32 -- Python 3.11.3, pytest-7.4.0, pluggy-1.3.0
rootdir: E:Python\第十章测试代码
collected 2 items                                                                                                                       

test_survey.py ..                   [100%] 

========================= 2 passed in 0.02s =================================

使用夹具

夹具(@pytest.fixture)可帮助我们搭建测试环境,用于测试多个项目。这个需要导入(import pytest)。夹具使用方法是放在函数定义前面的指令。要使用夹具时,可编写一个函数来生成供多个测试函数使用的资源再对这个函数应用装饰器@pytest.fixture,并让使用该资源的每个测试函数都接受一个与该函数同名的形参。

import pytest
from survey import AnonymousSurvey
# 测试AnonymousSurvey类


@pytest.fixture
def language_survey():
    '''一个可供所有测试函数使用的AnonymousSurvey实例'''
    question = "What language did you first learn to speak?"
    language_survey = AnonymousSurvey(question)
    return language_survey


def test_store_single_response(language_survey):
    '''测试单个答案会被妥善地存储'''
    language_survey.store_response('English')
    assert 'English' in language_survey.responses


def test_store_three_response(language_survey):
    '''测试多个答案会被妥善地存储'''
    responses = ['English', 'Spanish', 'Chinese']
    for response in responses:
        language_survey.store_response(response)

    for response in responses:
        assert response in language_survey.responses

小结

本章学习了如何使用 pytest 模块中的工具来为函数和类编写测试。不仅学习了如何编写测试函数,以核实函数和类的行为符合预期,而且学习了如何使用夹具来高效地创建可在测试文件中的多个测试函数中使用的资源。

第二部分 项目

第十二章 武装飞船

外星人入侵项目规划

  • 玩家控制着一艘武装飞船出现在屏幕底部中央,玩家可以使用方向键左右移动飞船,使用空格键进行射击。
  • 当游戏开始时,一个外形舰队出现在天空中,并向屏幕下方移动。
  • 玩家的任务是消灭这些外星人。
  • 玩家将万星人消灭干净后,将出现一个新的外形舰队,其移动速度更快。
  • 只要有万星人撞到玩家的飞船或到达屏幕下边缘,玩家就损失一艘飞船。玩家损失三艘飞船游戏结束。

安装Pygame

pip install pygame

开始游戏项目

创建 Pygame 窗口及响应用户输入

这里有及个新方法:

  • pygame.display.set_mode((1200,800)) 设置显示画面的大小
  • pygame.display.set_caption(“Alien Invasion”) 设置标题和logo
  • pygame.display.flip() 让渲染的可见
  • pygame.time.Clock() 设置游戏帧率
import sys
import pygame


class AlienInvasion:
    """管理游戏资源和行为的类"""
    def __init__(self):
        """初始化游戏并创建游戏资源"""
        pygame.init()

        self.screen = pygame.display.set_mode((1200,800))
        pygame.display.set_caption("Alien Invasion")

    def run_game(self):
        """开始游戏主循环"""
        while True:
            # 监听键盘和鼠标事件
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    sys.exit()
            # 让最近绘制的屏幕可见
            pygame.display.flip()

if __name__ == '__main__':
    # 创建游戏实例并运行游戏
    ai = AlienInvasion()
    ai.run_game()

控制帧率

class AlienInvasion:
    """管理游戏资源和行为的类"""

    def __init__(self):
        """初始化游戏并创建游戏资源"""
        pygame.init()
        # 创建游戏时钟,保证在其他系统都是相同的速度(帧率)运行
        self.clock = pygame.time.Clock()
        --snip--
        
    def run_game(self):
        """开始游戏主循环"""
        while True:
            --snip--
            # 让最近绘制的屏幕可见
            pygame.display.flip()    
            self.clock.tick(60)

设置背景颜色

fill() 方法是填充背景颜色,该方法只接受一个表示颜色的实参。

	def __init__(self):
    	--snip--
        pygame.display.set_caption("Alien Invasion")
        # 设置背景颜色
        self.bg_color = (230,230,230)
    def run_game(self):
        """开始游戏主循环"""
        --snip--
        # 每次循环时都重绘制屏幕
            self.screen.fill(self.bg_color)
        # 让最近绘制的屏幕可见
            pygame.display.flip()

创建 Settings 类

class Settings:
    """存储游戏《外星人入侵》中所有设置的类"""

    def __init__(self):
        """初始化游戏的设置"""
        # 屏幕设置
        self.screen_width = 1200
        self.screen_height = 800
        self.screen_color = (230, 230, 230)

class AlienInvasion:
    """管理游戏资源和行为的类"""

    def __init__(self):
        """初始化游戏并创建游戏资源"""        
        --snip--
        self.screen = pygame.display.set_mode((self.settings.screen_width, self.settings.screen_height))
    def run_game(self):
        """开始游戏主循环"""
        --snip--
            self.screen.fill(self.settings.screen_color)

添加飞船图像

import pygame


class Ship:
    """管理飞船类"""

    def __init__(self, ai_game):
        """初始化飞船并设置其初始位置"""
        self.screen = ai_game.screen
        self.screen_rect = ai_game.screen.get_rect()

        # 加载飞船图像并获取其外接矩形
        self.image = pygame.image.load('images/ship.bmp')
        self.rect = self.image.get_rect()

        # 每艘新飞船都放在屏幕底部的中央
        self.rect.midbottom = self.screen_rect.midbottom

    def blitme(self):
        """在指定位置绘制飞船"""
        self.screen.blit(self.image, self.rect)

**注意:**在 pygame 中,原点(0,0)位于屏幕左上角,当一个点向右下方移动时,它的坐标值将增大,在1200X800的屏幕上,原点位于左上角,右下角的坐标为(1200,800)。这些坐标对应的是游戏窗口,而不是物理屏幕。

在屏幕上绘制飞船

from ship import Ship

class AlienInvasion:
    """管理游戏资源和行为的类"""
    def __init__(self):
    """初始化游戏并创建游戏资源"""
    --snip--
    pygame.display.set_caption("Alien Invasion")
    self.ship = Ship(self)
    
    def run_game(self):
    """开始游戏主循环"""
    --snip--
            self.screen.fill(self.settings.screen_color)
            self.ship.blitme()

重构:_check_events() 方法和 _update_screen() 方法

在Python中辅助方法的名称以单下划线打头

_ check_events() 方法和 _update_screen()方法

    def run_game(self):
        """开始游戏主循环"""
        while True:
            # 监听键盘和鼠标事件
            self._check_events()
            self._update_screen()
            # 每秒60帧
            self.clock.tick(60)

    def _check_events(self):
        # 监听键盘和鼠标事件
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()

    def _update_screen(self):
        # 每次循环时都重绘制屏幕
        self.screen.fill(self.settings.screen_color)
        self.ship.blitme()
        # 让最近绘制的屏幕可见
        pygame.display.flip()

驾驶飞船

响应按键

pygame中,事件都是通过 pygame.event.get() 方法获取的。

--snip--
    def _check_events(self):
            # 监听键盘和鼠标事件
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    sys.exit()
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_RIGHT:
                        # 飞船向右移动
                        self.ship.rect.x += 1

持续移动

pygame.KEYDOWN 和 pygame.KEYUP 两个事件是,按下键盘,和释放键盘。

    def run_game(self):
            """开始游戏主循环"""
            while True:
                # 监听键盘和鼠标事件
                self._check_events()
                # 调用飞船位置更新函数
                self.ship.update()
                # 更新画面
                self._update_screen()
                # 每秒60帧
                self.clock.tick(60)
    def _check_events(self):
        # 监听键盘和鼠标事件
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RIGHT:
                    # 飞船向右移动
                    self.ship.moving_right = True
            elif event.type == pygame.KEYUP:
                if event.key == pygame.K_RIGHT:
                    self.ship.moving_right = False
                    
# ship.py
class Ship:
    """管理飞船类"""

    def __init__(self, ai_game):
        """初始化飞船并设置其初始位置"""
        --snip--
        # 移动标志(飞船一开始不移动)
        self.moving_right = False

    def update(self):
        """根据移动标志调整飞船的位置"""
        if self.moving_right:
            self.rect.x += 1
        --snip--

左右移动

class Ship:
    """管理飞船类"""
		--snip--
        # 移动标志(飞船一开始不移动)
        self.moving_right = False
        self.moving_left = False

    def update(self):
        """根据移动标志调整飞船的位置"""
        if self.moving_right:
            self.rect.x += 1
        if self.moving_left:
            self.rect.x -= 1
        --snip--
        
class AlienInvasion:
    """管理游戏资源和行为的类"""
    def _check_events(self):
        # 监听键盘和鼠标事件
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
                # 飞船移动
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RIGHT:
                    self.ship.moving_right = True
                if event.key == pygame.K_LEFT:
                    self.ship.moving_left = True
            elif event.type == pygame.KEYUP:
                if event.key == pygame.K_RIGHT:
                    self.ship.moving_right = False
                if event.key == pygame.K_LEFT:
                    self.ship.moving_left = False    

调整飞船的速度

class Settings:
    """存储游戏《外星人入侵》中所有设置的类"""

    def __init__(self):
        """初始化游戏的设置"""
        --snip--
        # 飞船的设置
        self.ship_speed = 1.5
class Ship:
    """管理飞船类""" 
    def __init__(self, ai_game):
        """初始化飞船并设置其初始位置"""    
        --snip--
        # 在飞创的属性X中存储一个浮点数
        self.x = float(self.rect.x)
        
    def update(self):
        """根据移动标志调整飞船的位置"""
        # 更新飞船的属性 x 的值,而不是其外接矩形的属性 x 的值
        if self.moving_right:
            self.x += self.settings.ship_speed
        if self.moving_left:
            self.x -= self.settings.ship_speed

        # 根据self.x更新self.rect.x
        self.rect.x = self.x        

限制飞船的活动范围

class Ship:
    """管理飞船类""" 
        --snip--    
    def update(self):
        """根据移动标志调整飞船的位置"""
        # 更新飞船的属性 x 的值,而不是其外接矩形的属性 x 的值
        if self.moving_right and self.rect.right < self.screen_rect.right:
            self.x += self.settings.ship_speed
        if self.moving_left and self.rect.left > 0:
            self.x -= self.settings.ship_speed        

重构:_check_events() 方法

检查事件方法越来越长,我们将其部分代码放在两个方法中,一个处理键盘按下(KEYDOWN),一个处理键盘释放(KEYUP)

    def _check_events(self):
        # 监听键盘和鼠标事件
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
                # 飞船移动
            elif event.type == pygame.KEYDOWN:
                self._check_keydown_events(event)
            elif event.type == pygame.KEYUP:
                self._check_keyup_events(event)

    def _check_keydown_events(self, event):
        """响应按下键盘"""
        if event.key == pygame.K_RIGHT:
            self.ship.moving_right = True
        elif event.key == pygame.K_LEFT:
            self.ship.moving_left = True

    def _check_keyup_events(self, event):
        """响应释放键盘"""
        if event.key == pygame.K_RIGHT:
            self.ship.moving_right = False
        if event.key == pygame.K_LEFT:
            self.ship.moving_left = False

按 Q 退出

    def _check_keydown_events(self, event):
        """响应按下键盘"""
        if event.key == pygame.K_RIGHT:
            self.ship.moving_right = True
        elif event.key == pygame.K_LEFT:
            self.ship.moving_left = True
        elif event.key == pygame.K_q:
            sys.exit()

在全屏模式下运行游戏

在创建屏幕时,传入(0, 0), pygame.FULLSCREEN ,这让pygame生成一个覆盖整个显示器的屏幕。由于无法知道屏幕的宽度和高度,所有后面接着要更新屏幕的 rect 的属性宽和高来更新对象 settings 。pygame不提供全屏模式下退出游戏的默认方式,所以运行前,确保可以使用 ‘q’ 退出

class AlienInvasion:
    """管理游戏资源和行为的类"""

    def __init__(self):
        """初始化游戏并创建游戏资源"""
        --snip--
        self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
        self.settings.screen_width = self.screen.get_rect().width
        self.settings.screen_height = self.screen.get_rect().height        

简单回顾

下面将添加射击功能,所以需要新增一个名为 bullet.py 的文件,并修改一些原有的文件,在添加其他功能前,先回顾一下这些文件,以便对这个项目的组织结构有清楚的认识。

  • alien_invasion.py
    这个文件包含 AlienInvasion 类,这个类创建在游戏的很多地方会用到的一系列属性。

    import sys
    import pygame
    
    from settings import Settings
    from ship import Ship
    from bullet import Bullet
    
    
    class AlienInvasion:
        """管理游戏资源和行为的类"""
    
        def __init__(self):
            """初始化游戏并创建游戏资源"""
            pygame.init()
            # 创建游戏时钟,保证在其他系统都是相同的速度(帧率)运行
            self.clock = pygame.time.Clock()
            # 设置背景颜色和窗口大小
            self.settings = Settings()
            # 独立窗口运行程序
            self.screen = pygame.display.set_mode((self.settings.screen_width, self.settings.screen_height))
            # 全屏模式游戏 'q' 退出
            # self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
            # self.settings.screen_width = self.screen.get_rect().width
            # self.settings.screen_height = self.screen.get_rect().height
            pygame.display.set_caption("Alien Invasion")
            self.ship = Ship(self)
            self.bullets = pygame.sprite.Group()
    
        def run_game(self):
            """开始游戏主循环"""
            while True:
                # 监听键盘和鼠标事件
                self._check_events()
                # 调用飞船位置更新函数
                self.ship.update()
                # 更新子弹的位置并删除已消失的子弹
                self._update_bullets()
                # 更新画面
                self._update_screen()
                # 每秒60帧
                self.clock.tick(60)
    
        def _check_events(self):
            # 监听键盘和鼠标事件
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    sys.exit()
                    # 飞船移动
                elif event.type == pygame.KEYDOWN:
                    self._check_keydown_events(event)
                elif event.type == pygame.KEYUP:
                    self._check_keyup_events(event)
    
        def _check_keydown_events(self, event):
            """响应按下键盘"""
            if event.key == pygame.K_RIGHT:
                self.ship.moving_right = True
            elif event.key == pygame.K_LEFT:
                self.ship.moving_left = True
            elif event.key == pygame.K_q:
                sys.exit()
            elif event.key == pygame.K_SPACE:
                self._fire_bullet()
    
        def _check_keyup_events(self, event):
            """响应释放键盘"""
            if event.key == pygame.K_RIGHT:
                self.ship.moving_right = False
            if event.key == pygame.K_LEFT:
                self.ship.moving_left = False
    
        def _fire_bullet(self):
            """创建一颗子弹,并将其加入编著bullets """
            if len(self.bullets) < self.settings.bullet_allowed:
                new_bullet = Bullet(self)
                self.bullets.add(new_bullet)
    
        def _update_bullets(self):
            """更新子弹的位置并删除已消失的子弹"""
            # 更新子弹位置
            self.bullets.update()
            # 删除已消失的子弹
            for bullet in self.bullets.copy():
                if bullet.rect.bottom <= 0:
                    self.bullets.remove(bullet)
    
        def _update_screen(self):
            # 每次循环时都重绘制屏幕
            self.screen.fill(self.settings.screen_color)
            # 子弹绘制        
            for bullet in self.bullets.sprites():
                bullet.draw_bullet()
            self.ship.blitme()
            # 让最近绘制的屏幕可见
            pygame.display.flip()
    
    
    if __name__ == '__main__':
        # 创建游戏实例并运行游戏
        ai = AlienInvasion()
        ai.run_game()
    
  • settings.py
    这个文件包含 Settings 类,这个类只有一个方法,这个文件主要用于初始化控制游戏外观和飞船速度的属性

    class Settings:
        """存储游戏《外星人入侵》中所有设置的类"""
    
        def __init__(self):
            """初始化游戏的设置"""
            # 屏幕设置
            self.screen_width = 1200
            self.screen_height = 800
            self.screen_color = (230, 230, 230)
    
            # 飞船的设置
            self.ship_speed = 1.5
    
            # 子弹设置
            self.bullet_speed = 2.0
            self.bullet_width = 3
            self.bullet_height = 15
            self.bullet_color = (60, 60, 60)
            # 屏幕上最多出现的子弹数量
            self.bullet_allowed = 5
    
  • ship.py
    这个文件包含 Ship 类,这个类主要用于在屏幕上绘制飞船

    import pygame
    
    
    class Ship:
        """管理飞船类"""
    
        def __init__(self, ai_game):
            """初始化飞船并设置其初始位置"""
            self.screen = ai_game.screen
            self.settings = ai_game.settings
            self.screen_rect = ai_game.screen.get_rect()
    
            # 加载飞船图像并获取其外接矩形
            self.image = pygame.image.load('images/ship.bmp')
            self.rect = self.image.get_rect()
    
            # 每艘新飞船都放在屏幕底部的中央
            self.rect.midbottom = self.screen_rect.midbottom
    
            # 在飞船的属性X中存储一个浮点数
            self.x = float(self.rect.x)
    
            # 移动标志(飞船一开始不移动)
            self.moving_right = False
            self.moving_left = False
    
        def update(self):
            """根据移动标志调整飞船的位置"""
            # 更新飞船的属性 x 的值,而不是其外接矩形的属性 x 的值
            if self.moving_right and self.rect.right < self.screen_rect.right:
                self.x += self.settings.ship_speed
            if self.moving_left and self.rect.left > 0:
                self.x -= self.settings.ship_speed
    
            # 根据self.x更新self.rect.x
            self.rect.x = self.x
    
        def blitme(self):
            """在指定位置绘制飞船"""
            self.screen.blit(self.image, self.rect)
    

射击

  • bullett.py
    import pygame
    from pygame.sprite import Sprite
    
    
    class Bullet(Sprite):
        """管理飞船所发射子弹的类"""
    
        def __init__(self, ai_game):
            """在飞船的当前位置创建一个子弹对象"""
            super().__init__()
            self.screen = ai_game.screen
            self.settings = ai_game.settings
            self.color = self.settings.bullet_color
    
            # 在(0,0)处创建一个表示子弹的矩形,再设置正确的位置
            self.rect = pygame.Rect(0, 0, self.settings.bullet_width, self.settings.bullet_height)
            self.rect.midtop = ai_game.ship.rect.midtop
    
            # 存储用浮点数表示的子弹位置
            self.y = float(self.rect.y)
    
        def update(self):
            """向上移动子弹"""
            # 更新子弹的准确位置
            self.y -= self.settings.bullet_speed
            # 更新表示子弹的 rect 的位置
            self.rect.y = self.y
    
        def draw_bullet(self):
            """在屏幕上绘制子弹"""
            pygame.draw.rect(self.screen, self.color, self.rect)
    

小结

本章学习了游戏开发计划的指定以及使用Pygame编写的游戏的基本结构。接着学习了如何设置背景色,以及如何将设置存储在独立的类中。然后学习了如何在屏幕上绘制图像,以及如何让玩家控制游戏元素的移动。不仅创建了能自动移动的元素,还删除了不再需要的对象。最后学习了经常性重构是如何为项目的后续开发提供便利的。

第十三章 外星人

项目回顾

本章将完成下列开发:

  • 在屏幕左上角添加一个外星人,并指定合适的边距。
  • 沿屏幕上边缘添加一行万星人,再不断地添加成行的外星人,直到填满屏幕的上半部分。
  • 让外星人向两侧和向下移动,直到外星舰队被全部击落、有外星人撞到飞船或有外星人抵达屏幕的下边缘。如果外星舰队都被击落,将再创建一个外星舰队;如果有外星人撞到飞船或抵达屏幕下边缘,就销毁飞船并再创建一个外星舰队。
    注意:pygame里面的精灵碰撞和对象碰撞是两个方法
    • pygame.sprite.groupcollide(精灵1,精灵2,1是否参与碰撞,2是否参与碰撞)。这个是直接赋值给变量就可以了。和 input 差不多
    • pygame.sprite.spritecollideany(对象,精灵)。这个是返回一个bool值
  • 限制玩家可用的飞船数量,分配的飞船被用完后,游戏将结束。

创建第一个外星人

小结

本章通过创建外星舰队学习了如何在游戏中添加大量相同的元素,如何使用嵌套循环来创建成行成列的整齐元素,以及如何通过调用每个元素的 update()方法移动大量的元素。接着学习了如何控制对象在屏幕上的移动方向,以及如何响应特定的情形,如有外星人到达屏幕边缘。然后学习了如何检测并相应子弹和外星人的碰撞以及外星人和飞创的碰撞。最后学习了如何在游戏中跟踪统计信息,以及如何使用标志 game_active来判断游戏是否结束。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值