Python 基础

本篇博文包含十章学习python必备的基础知识

一、变量和数据类型

1.变量命名

  • 变量名只能包含字母、数字和下划线,数字不能打头

  • 关键字和函数名不可以作为变量名

  • 慎用小写字母l和大写字母O,以免被错认为数字1和0

在Python中,变量是可以赋给值的标签,也可以说变量指向特定的值

2.字符串

在python中,引号括起来的都是字符串。可以是单引号,也可以是双引号

修改字符串大小写
name = "ada"
print(name.title())

方法是Python可对数据执行的操作

在这个示例中,输出首字母大写的name变量。name后面点的作用是让python对变量name实现指定操作。每个方法后面都有圆括号,这是因为方法通常需要额外的信息来完成其工作。函数title不需要额外信息,所以括号中为空。

如果全部改为大写或小写,可以用如下代码:

print(name.upper())
print(name.lower())
在字符串中使用变量
nameA = "ada"
nameB = "love"
full = f"{nameA} {nameB}"
print(full)

要在字符串中插入变量的值,可以在引号前面加上字母f。这种字符串名为 f字符串

f是format的缩写,python通过把花括号内的变量替换为其值来设置字符串的格式,上述代码输出ada love

nameA = "ada"
nameB = "love"
full = f"{nameA} {nameB}"
print(f"hello,{full.title()}")

输出hello,Ada Love

3.空白

制表符\t,换行符\n,使用它们生成空白

在程序中,额外的空白可能使人疑惑,多一个空格都会使两个字符串不同。

要永久删除某个字符串末尾的空白,可以使用如下代码

lan = "python "
lan = lan.rstrip()

此后lan的值中不再有字母n后面的空白

剔除字符串开头的空白或同时剔除字符串两边的空白,可以分别使用方法lstrip()和strip()

lan = lan.lstrip()
lan = lan.strip()

4.数

整数

在python中,可以执行加(+)减(-)乘(*)除(/)四则运算,也可以使用括号改变运算优先级

除此之外还有乘方运算,如3 ** 2结果为9

浮点数

也可以执行四则运算,但要注意浮点数运算结果永远是一个近似值

整数和浮点数

任意两个数相除结果都是浮点数,即使他们本来是整数且能整除

在其他运算中只要有一个操作数为浮点数则结果为浮点数

数中的下划线

书写很大的数时可用下划线分组,1000与1_000在运算中没什么不同

5.多变量赋值

x,y,z = 0,0,0

用逗号将变量名隔开,对于要赋给的值也同样如此处理

6.常量

类似于变量,但其值在程序的整个生命周期内保持不变。python没有内置常量类型,但我们可以使用全大写指出某个变量应该为常量。其值应始终不变

7.注释

用井号(#)标识注释,井号后面的内容都会被python解释器忽略

二、列表简介

1.列表是什么

列表由一系列按特定顺序排列的元素组成,某些方面很像C语言的数组

在python中,用方括号[]表示列表,并用逗号分割其中的元素。

num = [1,2,3,4]
print(num)

如果要让python打印列表,python将连同方括号一起打印

2.访问列表元素

num = [1,2,3,4]
print(num[0])

这样输出数字1。这里要注意索引从0开始

另外,python中可通过将索引指定为-1可以返回最后一个列表元素,-2返回倒数第二个,以此类推

3.使用列表中的各个值

language = ['c','python','java']
message = f"My first language is {language[0].title()}"
print(message)

可以像变量一样使用这些值

4.修改元素

num = [1,2,3,4]
print(num)
num[0] = 5
print(num)

5.添加元素

append()
num = [1,2,3,4]
print(num)
num.append(5)
print(num)

方法append将元素添加到列表末尾,现在这个列表中有五个元素了

在实际开发中,为控制用户,可首先创建一个空列表,用于存储用户将要输入的值,然后将用户提供的每个新值都附加到列表中。

insert()
names = ['fang','wen','long']
print(names)
names.insert(0,'xi')
print(names)

在这个列表中,使用方法insert在列表的第一个位置插入了’xi’。这种操作使其他元素都右移了一个位置

可以用该方法将元素插入到任意位置

6.删除元素

del语句实现
names = ['fang','wen','long']
print(names)
del names[0]
print(names)

这样删除掉了列表中的第一个元素,同理可以删除任意元素

使用方法pop

方法pop()删除列表末尾的元素,并让你可以接着使用它。术语弹出(pop)来源于这样的对比:列表就像一个栈,而删除列表末尾的元素相当于弹出栈顶元素。

names = ['fang','wen','long']
print(names)
man_name = names.pop()
print(man_name)
print(names)

这样弹出最后的元素’long’,并赋给变量man_name

当然可以弹出其他位置的元素,如下所示弹出第一个元素

names = ['fang','wen','long']
print(names)
man_name = names.pop(0)
print(man_name)
print(names)
根据值删除

如果不知道位置而只知道删除元素的值,可以使用方法remove(),如下所示

names = ['fang','wen','long']
print(names)
names.remove('fang')
print(names)

这样删除掉值为’fang’的元素,需要注意的是这样只删除掉一个为该值的元素

7.列表排序

sort()方法永久排序
names = ['fang','wen','long']
names.sort()
print(names)

这样让列表升序排列,降序如下所示

names = ['fang','wen','long']
names.sort(reverse = True)
print(names)
sorted()函数临时排序
names = ['fang','wen','long']
print(sorted(names))

只在本次输出中升序排列,降序多传入一个参数(reverse = True)即可

reverse()方法使列表永久颠倒
names = ['fang','wen','long']
names.reverse()
print(name)

注意,reverse不是排序方法,它只是让整个列表的顺序颠倒过来

8.列表长度

names = ['fang','wen','long']
changdu = len(names)
print(changdu)

使用len函数直接得到了一个列表的长度

9.使用clear()方法清空列表

names = ['chen','hong','sheng']
names.clear()

三、操作列表

1.遍历列表

names = ['lichenxi','chenhongsheng']
for name in names:
    print(name)

使用这个for循环遍历整个列表,输出每个元素

2.循环

for name in names:
    print(name)

这是一个循环,通过缩进表示层次关系,由于缩进了print语句,即意味着print语句在该循环内反复执行。注意不要遗漏冒号

3.创建数值列表

使用函数range()
for value in range(1,5):
    print(value)

这里只输出1到4,因为range参数左闭右开

使用函数list()创建数字列表
numbers = list(range(1,6))
print(numbers)

这样numbers就是一个有五个数字元素的列表[1, 2, 3, 4, 5]

使用range函数时还可以指定步长,为此,给函数加入第三个参数

numbers = list(range(2,11,2))
print(numbers)

在这个示例中,列表中的元素从2开始,不断加2,创建了一个列表[2, 4, 6, 8, 10]

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

在这个示例中,创建[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

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

numbers = list(range(2,11,2))
print(numbers)
print(sum(numbers))
print(min(numbers))
print(max(numbers))

5.列表解析

numbers = [value ** 2 for value in range(1,11)]
print(numbers)

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

6.使用列表的一部分

处理列表的部分元素,python称其为切片

切片
numbers = [value ** 3 for value in range(1,11)]
print(numbers[0:3])

这样打印该列表的一个切片,其中只包含前三个元素(左闭右开)

可以生成列表的任意子集,如果没有指定第一个索引,python自动从列表开头开始;如果省略终止索引,将截止到列表结束。

可以使用负数,如numbers[-3:]切出最后三个元素

遍历切片
numbers = [value ** 3 for value in range(1,11)]
for number in numbers[0:3]:
    print(number)

这样遍历前三个数字

复制列表
numbers = [value ** 3 for value in range(1,11)]
copy = numbers[:]

这样创建了一个numbers列表的复制

7.元组

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

定义元组
dimensions = (200,50)
print(dimensions[0])
print(dimensions[-1])

如果试图修改元组中的元素,就会引发错误

遍历元组
dimensions = (200,50)
for unit in dimensions:
	print(unit)

和列表一样,可以遍历整个元组

修改元组变量

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

dimensions = (200,50)
dimensions = (400,50)

8.设置代码格式

访问python网站并阅读PEP 8格式设置指南

  • python改进提案(PEP)

  • 每次缩进四个空格(一个制表符)

  • 每行不超过80字符

  • 使用空行提高可读性

四、if语句

1.示例

cars = ['audi','bmw','subaru','toyota']

for car in cars:
	if car == 'bmw':
		print(car.upper())
	else:
		print(car.title())

输出结果如下所示:

Audi
BMW
Subaru
Toyota

if语句和else语句末尾都有冒号,一定要注意这点

2.条件测试

检查是否相等使用==,注意python会区分大小写,即相同单词大小写不同是不等的

检查是否不等使用!=,仍然会区分大小写

数值比较使用>=,>,<=,<,注意和C语言不同的地方在于python中这样写是有效的:

if 12 < age < 18:

使用and时必须两个条件都满足,使用or时至少要有一个条件满足(类似于C语言中的&&和||)

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

users = ['fang','chen','long']
user = 'chen'
if user in users:
	print(users)
if user not in users:
	print(user)

关键字in让python检查列表中是否包含元素’chen’,关键字not in检查列表中是否不包含元素’chen’。如果这里把’chen’改为’sds’,则会输出’sds’

4.布尔表达式

布尔表达式的结果要么为True,要么为False。它通常用于记录条件

5.if语句

在if语句中,缩进的作用和在for循环中的相同。第一行可包含任意条件测试,在紧跟在测试后的缩进代码块中可执行任何操作,如果结果为True,python就会执行紧跟在if语句后面的代码。否则python将忽略它们

6.if-else语句

如果if语句中的条件测试通过就执行它后面的代码,否则执行else后面的代码

7.if-elif-else语句

当遇到超过两个的情景,我们可以使用如下代码:

age = 12
if age < 4:
    print("cost is $0")
elif age < 18:
    print("cost is $25")
else:
    print("cost is $40")

elif可以使用多个,它后面也必须跟一个条件测试

当然可以选择不用else语句,这取决于实际开发情况

注意,根据应用不同,来选择用if-elif还是单独的多个if结构

8.使用if语句处理列表

toppings = ['mushrooms','green pepers','extra cheese']

for topping in toppings:
    if(topping == 'green pepers'):
        print("No")
    else:
        print("Yes")

当遍历到元素’green pepers’就会输出No

那如果要确定列表是否为空应该怎么做?

toppings = []
if toppings:
    for topping in toppings:
        if(topping == 'green pepers'):
            print("No")
        else:
            print("Yes")
else:
    print("none")

因为这时的列表是空的,所以会输出none。如果列表不为空就会执行if下的代码块

五、字典

1.一个简单的字典

alien = {'color': 'green', 'point': 5}

print(alien['color'])
print(alien['point'])

输出如下所示

green
5

2.使用字典

在python中,字典是一系列键值对,每个都与一个值相关联。你可以使用键来访问相关联的值。与值相关联的可以是数字、字符串、列表、甚至字典。事实上可以将任何python对象用作字典中的值

在python中,字典用放在花括号中的一系列键值对来表示,如刚刚的示例所示。键值对之间用逗号分隔,可以存储任意个键值对

访问字典中的值

要获取与键相关联的值,可依次指定字典名和放在方括号里的键,如下所示:

alien = {'color': 'green'}
print(alien['color'])

这将返回字典中与键’color’相关联的值

添加键值对
alien = {'color': 'green'}
print(alien['color'])
alien['x_position'] = 0
alien['y_position'] = 25
print(alien)

这样在字典中新增了两个键值对,现在它一共有三个键值对了

先创建一个空字典
alien = {}
alien['x_position'] = 0
alien['y_position'] = 25
print(alien)

这里先定义了一个字典,然后再在里面增加键值对

3.修改字典中的值

alien = {'color': 'green'}
print(alien)
alien['color'] = 'yellow'
print(alien)

这样修改了外星人的颜色

4.删除键值对

alien = {'color': 'green', 'point': 5}
del alien['point']
print(alien)

使用del语句删除了’point’键值对,其他键值对不会受影响

5.由类似对象组成的字典

favorite_languages = {
    'chen': 'Python',
    'long': 'C',
    'fang': 'Java',
}
language = favorite_languages['chen'].upper()
print(f"this is {language}")

这种语法可以从字典中获取任何人喜欢的语言

6.使用get()来访问值

favorite_languages = {
    'chen': 'Python',
    'long': 'C',
    'fang': 'Java',
}
language = favorite_languages.get('chen', 'hahaha')
print(f"this is {language}")

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

这样无论字典中有没有这个键程序都不会出错,如果没有’chen’这个键,则会输出this is hahaha

如果没有指定第二个参数且指定的键不存在,将返回None

7.遍历字典

遍历所有键值对
favorite_languages = {
    'chen': 'Python',
    'long': 'C',
    'fang': 'Java',
}
for k,v in favorite_languages.items():
    print(f"\nKey:{k}")
    print(f"Value:{v}")

要编写遍历字典的for循环,可以声明两个变量,用于存储键值对中的键和值。这两个变量可以使用任意名称。

for语句的第二部分包含字典名和方法items()——它返回一个键值对列表。接下来,for循环以此将每个键值对赋给指定的两个变量,在本例中,使用这两个变量来打印每个键及其相关联的值

遍历字典中的所有键

在不需要字典中的值时,方法**keys()**很管用。如下所示:

favorite_languages = {
    'chen': 'Python',
    'long': 'C',
    'fang': 'Java',
}
for name in favorite_languages.keys():
    print(f"Name:{name}")

这和以下代码块作用相同

favorite_languages = {
    'chen': 'Python',
    'long': 'C',
    'fang': 'Java',
}
for name in favorite_languages:
    print(f"Name:{name}")

可见是否使用keys()不会影响输出,但使用它可以让代码的可读性提高

方法keys并非只能用于遍历:实际上,它返回一个列表,其中包含字典中的所有键。

按照特定顺序遍历字典中的所有键
favorite_languages = {
    'chen': 'Python',
    'long': 'C',
    'fang': 'Java',
}
for name in sorted(favorite_languages):
    print(f"Name:{name}")

这条for语句与其他for不同之处在于它在遍历前对这个列表进行了排序

遍历字典中的所有值

如果我们只对值感兴趣,可以用方法values()来返回一个值列表

favorite_languages = {
    'chen': 'Python',
    'long': 'C',
    'fang': 'Java',
}
for lan in favorite_languages.values():
    print(f"language:{lan}")

这样输出了字典中所有的值

但是如果由有键的值是相同的,我们可能会输出大量重复数据,为了规避这一情况,可使用集合(set),集合中的每个元素都是独一无二的,如下所示

favorite_languages = {
    'chen': 'Python',
    'long': 'C',
    'fang': 'Java',
    'xuan': 'C',
    'xi':'Python',
}
for lan in set(favorite_languages.values()):
    print(f"language:{lan}")

通过调用set(),可让python找出列表中独一无二的元素,并且使用这些元素来创建一个集合,结果是一个不重复的列表

可使用一对花括号直接创建集合,并在其中用逗号分隔元素

language = {'Python','C','Python','Java'}
print(language)

这样只会输出一次’Python’

集合与字典不同的地方在于集合中的元素不是键值对

8.嵌套

在列表中存储字典

我们可以让几个字典组成一个列表,如下所示:

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

我们还可以使用range()生成外星人:

# 存储外星人的列表
aliens = []

# 用range生成30个外星人
for alien in range(30):
    new = {'color': 'green', 'points': 5, 'speed': 'slow'}
    aliens.append(new)

# 显示现在列表中有多少个外星人
print(f"Total number of aliens: {len(aliens)}")
在字典中存储列表

下面是一个示例:

pizza = {
    'crust': 'thick',
    'toppings': ['mushrooms', 'extra cheese'],
}

这个字典中的信息由于使用了列表而更加丰富

favorite_languages = {
    'chen': ['Python', 'C'],
    'long': ['C'],
    'fang': ['Java', 'C'],
}
for name,language in favorite_languages.items():
    if len(language) == 1:
        print(f"\n{name.title()}'s favorite languages is:")
    else:
        print(f"\n{name.title()}'s favorite languages are:")
    for lan in language:
        print(f"\t{lan.title()}")

这样每个人就可以同时有几个最喜欢的语言了

在字典中存储字典
users = {
    'chen': {
        'first': 'chen',
        'location': 'Tianjin',
    },
    
    'fang': {
        'first': 'fang',
        'location': 'Sichuan',
    },
}

for username,info in users.items():
    print(f"\nUsername: {username}")
    print(f"Location: {info['location']}")

六、用户输入和while循环

1.函数input()

函数input让程序暂停运行,等待用户输入一些文本,获取输入后,python将其赋给一个变量

message = input("tell me:")
print(message)

函数input接受一个参数——要向用户显示的**提示(promot)**或者说明,将其呈现给用户,方便用户输入应该输入的东西。每当使用函数input时,都应该有清晰易懂的提示。

有时候,提示可能超过一行。例如,你可能需要指出获取特定输入的原因。在这种情况下,可将提示赋给一个变量,然后将该变量传入input函数中

prompt = "emmm"
prompt += "\nWhat is your name:"

name = input(prompt)
print(f"\nHello,{name}!")

这样提示占据了两行

2.函数int()获取数值输入

使用函数input时,python将用户输入解读为字符串,这样输入的数无法用作运算

为解决这个问题,可使用函数int,它让python将输入视为数值。函数int将数的字符串表示转换为数值表示

age = input("How old are you:")
age = int(age)
if(age >= 18):
    print("adult")
else:
    print("too young")

在本例中,输入21后python将其解读为字符串,但随后int将这个字符串转换为了数值表示,这样python就能运行条件测试了。将数值输入用于计算和比较前,务必将其转换为数值表示

3.求模运算符

**求模运算符%**在处理数值信息时很有用,它将两个数相除并且返回余数

4.while循环

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

使用while循环

下面的while循环表示从1数到5:

num = 1
while num <= 5:
    print(num)
    num += 1

一旦num大于5,循环就将停止

让用户决定何时退出

可以使用while循环让程序在用户愿意时不断运行,我们定义一个退出值,只要用户输入的不是这个值,程序就将接着运行:

prompt = "I can repeat your message"
prompt += "\nEnter 'quit' to end the program"
message = ""
while message != 'quit':
    message = input(prompt)
    print(message)

首次执行循环时,message是一个空字符串,因此python进入循环。等到用户输入quit时python才会退出循环,否则将一直运行

使用标志

如果有多种情况导致循环结束,这时该怎么办呢?

在要求很多条件都满足才继续运行的程序中,可定义一个变量,判断是否整个程序处于活动状态,这个变量称为标志。将控制标志的测试放在其他地方,从而让程序更简洁

prompt = "I can repeat your message"
prompt += "\nEnter 'quit' to end the program"
message = ""
active = True
while active:
    message = input(prompt)
    if(message == 'quit'):
        active = False
    else:
        print(message)

这个程序作用和前一个程序基本相同,区别在于前一个示例将条件测试放在了while语句中,而这个程序则使用一个标志来指出程序是否处于活动状态。

使用break来退出循环
prompt = "I can repeat your message"
prompt += "\nEnter 'quit' to end the program"
message = ""

while True:
    message = input(prompt)
    if(message == 'quit'):
        break
    else:
        print(message)

以while True打头的循环将不断运行,直到遇到break语句。break语句直接打破循环

使用continue来返回循环开头
num = 0
while num < 10:
    num += 1
    if num % 2 == 0:
        continue
        
    print(num)

continue语句执行后跳过本轮循环后面的所有代码,因此这个程序输出了所有10以内的奇数

使用while语句时一定要注意避免出现死循环

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

for循环是一种遍历列表的有效方式,但不应该在for循环中修改列表,否则将导致python难以跟踪其中的元素。要在遍历列表的同时对其进行修改,可使用while循环

在列表之间移动元素
unconfirmed = ['alice', 'brian', 'candace']
confirmed = []

while unconfirmed:
    current = unconfirmed.pop()
    print(f"Verifying user:{current.title()}")
    
    confirmed.append(current)

print("\nThe following users have been confirmed:")
for confirmed_user in confirmed:
    print(confirmed_user.title())

首先创建了一个未验证的用户列表,包含了几个用户,然后创建一个空列表,用于存储已验证的用户

while循环不断运行,直到未验证列表为空;每轮循环弹出一个用户,将其确认。最后所有用户都已验证

Verifying user:Candace
Verifying user:Brian
Verifying user:Alice

The following users have been confirmed:
Candace
Brian
Alice
删除为特定值的所有列表元素

在前面我们提到可以用remove来删除列表中的特定值,但它只能删除一次。如果要删除列表中所有为特定值的元素,我们可以使用while循环

pets = ['cat', 'dog', 'rabbit', 'cat']
print(pets)

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

这样删除掉了列表中的所有’cat’

使用用户输入来填充字典
survey = {}

active = True

while active:
    name = input("\nWhat is your name? ")
    response = input("Who is your girlfriend? ")
    survey[name] = response
    
    repeat = input("Would you like to let another person respond? (yes/no) ")
    
    if repeat == 'no':
        active = False
        
print("\nTHE SURVEY")
for k,v in survey.items():
    print(f"{k}'s girlfriend is {v}.")

七、函数

1.定义函数

下面是一个简单的函数:

def greet():
    """显示问候语。"""
    print("Hello!")

greet()

使用关键字def来告诉python,你要定义一个函数。这是函数定义,向python指出了函数名,还可能在圆括号内指出函数为完成任务需要什么样的信息。在这里函数名为greet,它不需要任何信息就能完成工作,但仍然需要括号。最后以冒号结尾。

后面的缩进代码是函数体,"""显示问候语。"""是称为文档字符串的注释,描述了函数是用来做什么的。python使用它们来生成有关程序中函数的文档。

下面的greet()函数调用。函数调用让python执行函数的代码,要调用函数,可依次指定函数名以及用圆括号括起来的必要信息。和预期一样,它打印Hello!

2.向函数传递信息

让我们对刚刚的函数稍作修改

def greet(username):
    """显示问候语。"""
    print(f"Hello, {username.title()}!")

greet('xi')

现在,这个函数要求你调用它时给username指定一个值。调用greet时,可以将一个名字传递给它。它接受这个名字,并对这个人发出问候

在函数greet的定义中,变量username是一个形参,即函数完成工作所需的信息。在代码greet('xi')中,值’xi’是一个实参,即调用函数时传递给函数的信息。

greet('xi')中,将实参’xi’传递给了函数greet,这个值被赋给了形参username

3.传递实参

向函数传递实参的方式很多,可以使用位置实参,这要求实参的顺序与形参的顺序相同;

也可使用关键字实参,其中每个实参都由变量名和值组成;

还可以使用列表和字典。

位置实参
def pet(animal, name):
    """显示宠物信息。"""
    print(f"My pet is a {animal} which called {name}")
    
pet('dog', 'dudu')

这个函数的定义表明,它需要一个动物类型和一个名字。调用pet时,需要按顺序提供一个动物类型和一个名字。

你可以多次调用函数,但每次都要注意自己输入实参的顺序必须和形参保持一致

关键字实参

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

关键字实参让你无需考虑函数调用中的实参顺序,还清楚地指出了每个值的用途

def pet(animal, name):
    """显示宠物信息。"""
    print(f"My pet is a {animal} which called {name}")
    
pet( name = 'dudu', animal = 'dog')

输出和上一次的一样

默认值

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

如下所示:

def pet(name, animal = 'dog'):
    """显示宠物信息。"""
    print(f"My pet is a {animal} which called {name}")
    
pet(name = 'dudu')

这时我们没有传入宠物类型,因此函数使用了默认值,给animal赋值’dog’,如果我们传入宠物类型:

def pet(name, animal = 'dog'):
    """显示宠物信息。"""
    print(f"My pet is a {animal} which called {name}")
    
pet(name = 'dudu', animal = 'cat')

则animal赋值为’cat’,这时python将忽略默认值

注意,使用默认值时,必须先在参数列表中列出没有默认值的实参,然后再列出有默认值的实参

等效函数调用

在调用函数输入参数时,既可以采用位置方式,也可以采用关键字方式,使用对你来说最容易理解的调用方式即可。

无论如何,确保函数调用使用的参数与函数定义使用的参数相匹配

4.返回值

函数可以处理一些数据,并返回一个或一组值。函数返回的值称为返回值。在函数中,可使用return语句将值返回到调用函数的代码行。返回值让你能够将程序中的大部分繁重工作转移到函数中去完成,从而简化主程序

返回简单值
def formatted_name(first, last):
    """返回整洁姓名"""
    full_name = f"{first} {last}"
    return full_name.title()

musician = formatted_name('liuyun', 'tian')
print(musician)

这样返回了一个完整的姓名,每当我们要显示姓名时都可以调用这个函数。

让实参变为可选的
def formatted_name(first, last,  middle = ''):
    """返回整洁姓名"""
    if middle:
        full_name = f"{first} {middle} {last}"
    else:
        full_name = f"{first} {last}"
    return full_name.title()

musician = formatted_name('liuyun', 'tian', middle = 'li')
print(musician)

我们这样设置后不仅使用于只有名和姓的人,而且使用于有中间名的人。当他没有中间名的时候不传入实参给middle就可以了。

返回字典
def bulid_person(first_, last_):
    person = {'first': first_, 'last': last_}
    return person

musician = bulid_person('liuyun', 'tian')
print(musician)

这样返回一个字典

def bulid_person(first_, last_, age = None):
    person = {'first': first_, 'last': last_}
    if age:
        person['age'] = age
    return person

musician = bulid_person('liuyun', 'tian',age = 27)
print(musician)

修改后可以让这个字典包含关于人年龄的键值对

5.传递列表

假设有一个用户列表,我们要问候列表中的每位用户。下面的示例将包含名字的列表传递给一个名为greet的函数,这个函数问候列表中的每个人:

def greet(names):
    for name in names:
        msg = f"Hello, {name.title()}"
        print(msg)
        
usernames = ['java', 'python', 'C']
greet(usernames)

每个用户都收到了一条个性化的标志语

在函数中修改列表
def print_models(unprinted, completed):
    while unprinted:
        current = unprinted.pop()
        print(f"Printing model: {current}")
        completed.append(current)

def show_models(completed):
    print("\nThe following models have been printed:")
    for completed_model in completed:
        print(completed_model)

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

print_models(unprinted_designs, completed_models)
show_models(completed_models)

我们创建了一个未打印的设计列表,还创建了一个空的已完成列表。在第一个函数内,我们将待打印的模型逐个打印,并移到已完成列表中;在第二个函数内,我们遍历已完成列表,逐个展示打印好的模型

Printing model: dodecahedron
Printing model: robot pendant
Printing model: phone case

The following models have been printed:
dodecahedron
robot pendant
phone case
禁止函数修改列表

从刚刚的示例我们可以看出,传入的列表会被函数修改,因此,如果我们不想让函数修改列表,我们可以修改为如下的代码:

def print_models(unprinted[:], completed):

切片表示法[:]创建列表的副本,这样传入函数的实际上是一个未打印列表的副本,所以不会影响它本身

6.传递任意数量的实参

有时候,我们并不知道函数需要接受多少个实参,不过python可以允许函数从调用语句中收集任意数量的实参。

def pizza(*toppings):
    print(toppings)
    
pizza('extra cheese')
pizza('mushrooms', 'green peppers')

形参名*toppings中的星号让python创建一个名为toppings的空元组,并将收到的所有值都封装到这个元组中。也可以将这里的print改为一个for循环,遍历配料列表,并输出每种配料

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

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

def pizza(size, *toppings)

例如这样

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

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

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

def build_profile(first, last, **info):
    info['first'] = first
    info['last'] = last 
    return info

user = build_profile('liuyun', 'tian',
                    location = 'princeton',
                    field = 'physics')

print(user)

在这里,调用这个函数时不管提供多少个额外的键值对,它都能够处理

形参**info中的两个星号让python创建一个名为info的字典,并将所有接收到的键值对都放到这个字典中

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

7.将函数存储在模块中

我们可以将函数存储在称为模块的独立文件中,再将模块导入到主程序之中。import语句允许在当前运行的程序文件中使用模块中的代码

导入整个模块

模块是扩展名为.py的文件,包含要导入到程序中的代码。

这里我们有一个名为ist.py的文件

import sdj

musician = sdj.bulid_person('liuyun', 'tian',age = 27)
print(musician)

在同一个文件中,我们还有另一个文件sdj.py

def bulid_person(first_, last_, age = None):
    person = {'first': first_, 'last': last_}
    if age:
        person['age'] = age
    return person

那么运行ist.py的话,和我们运行以下这个程序的效果是一样的

def bulid_person(first_, last_, age = None):
    person = {'first': first_, 'last': last_}
    if age:
        person['age'] = age
    return person

musician = bulid_person('liuyun', 'tian',age=27)
print(musician)

import sdj让python打开文件sdj.py,并将其所有函数复制到这个程序中。调用sdj中的函数时,注意在前面加上sdj.

导入特定的函数

如果只想导入要使用的函数,代码类似于下面这样

from sdj import bulid_person

musician = bulid_person('liuyun', 'tian',age=27)
print(musician)

使用这种语法时无需加上sdj.,指定其名称即可

使用as给函数指定别名

如果导入的函数的名称可能和现有函数冲突,或者函数名称太长,可以使用简短而独一无二的别名,如下所示

from sdj import bulid_person as bp

musician = bp('liuyun', 'tian',age=27)
print(musician)

这里使用as指定一个新的名称

导入模块中的所有函数
from sdj import *

musician = bp('liuyun', 'tian',age=27)
print(musician)

使用*运算符可以让python导入模块中的所有函数

然而这样的导入方法导入大型模块时,很容易由于函数与本项目中的同名而出错。所以最好不使用这种方法

8.函数编写指南

  • 给形参指定默认值时,等号两边不要有空格
  • 对于关键字实参,也应该遵守这种规定
  • PEP 8建议代码行的长度不要超过79字符,如果形参太多导致超过了79字符,可在函数定义中输入左括号后按回车键,并在下一行按两次Tab键,从而将形参列表和只缩进一层的函数体区分开来
  • 多个函数用空行分隔开
  • import语句应该放在文件开头

八、类

1.创建和使用类

使用类可以模拟几乎任何东西。下面来编写一个表示小狗的简单类

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def sit(self):
        print(f"{self.name} is now sitting.")
        
    def roll_over(self):
        print(f"{self.name} rolled over!")

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

根据约定,在python中首字母大写的名称指的是类。这个类定义中没有圆括号,因为要从空白创建这个类。

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

2.根据类创建实例

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def sit(self):
        print(f"{self.name} is now sitting.")
        
    def roll_over(self):
        print(f"{self.name} rolled over!")
        
my_dog = Dog('tutu', 6)

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

这里使用前一个实例中编写的Dog类。python使用实参’tutu’和6调用Dog类的方法__init__(),其创建一个表示特定小狗的实例,并且使用提供的值来设置属性name和age。

接下来,python返回一个表示这个小狗的实例,而我们将这个实例赋给了变量my_dog。通常认为首字母大写的名称表示类,小写的名称表示根据类创建的实例

访问属性
my_dog.name

这种语法演示了python如何获悉属性的值。它先找到实例my_dog,再查找与该实例相关联的属性name。在Dog类中引用这个属性时,使用的是self.name

调用方法

根据Dog类创建实例后,就能通过.来调用Dog类中定义的任何方法,如下所示:

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def sit(self):
        print(f"{self.name} is now sitting.")
        
    def roll_over(self):
        print(f"{self.name} rolled over!")
        
my_dog = Dog('tutu', 6)

my_dog.sit()
my_dog.roll_over()

python在Dog类中查找方法并运行其代码,输出如下所示:

tutu is now sitting.
tutu rolled over!
创建多个实例

可按照需求创建任意数量的实例

class Dog:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def sit(self):
        print(f"{self.name} is now sitting.")
        
    def roll_over(self):
        print(f"{self.name} rolled over!")
        
my_dog = Dog('tutu', 6)
your_dog = Dog('huanhuan', 3)

又创建了一个实例,用法和刚刚完全相同

3.使用类和实例

可使用类来模拟现实世界中的很多情景。下面是一个表示汽车的类

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def describe(self):
        name = f"{self.year} {self.make} {self.model}"
        return name.title()

my_car = Car('audi', 'a4', 2019)
print(my_car.describe())

为了让这个类更加有趣,我们给它增加一个随时间变化的属性,用于存储汽车的总里程

给属性指定默认值

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

下面添加一个名为odometer_reading的属性,其初始值总为0。我们还添加了一个名为read_odometer的方法,用于读取汽车的里程表:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def describe(self):
        name = f"{self.year} {self.make} {self.model}"
        return name.title()

    def read_odometer(self):
        print(f"This car has {self.odometer_reading} miles on it")

my_car = Car('audi', 'a4', 2019)
print(my_car.describe())

my_car.read_odometer()

一开始汽车的里程为0,但出售时里程表读数为0的汽车不多,因此需要一种方式修改该属性的值

4.修改属性的值

直接修改属性的值

最简单的方式是直接通过实例访问它,如下所示:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def describe(self):
        name = f"{self.year} {self.make} {self.model}"
        return name.title()

    def read_odometer(self):
        print(f"This car has {self.odometer_reading} miles on it")

my_car = Car('audi', 'a4', 2019)
print(my_car.describe())
my_car.odometer_reading = 23

my_car.read_odometer()

这样我们直接找到属性odometer_reading,并将其的值修改为23

有时候要这样直接访问属性,但更多的时候我们需要编写对属性更新的方法

通过方法修改属性的值

如果能有方法替你更新属性,就无需直接访问属性,而可以将值传递给方法,由它在内部更新。

下面的示例演示了一个名为update_odometer的方法

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def describe(self):
        name = f"{self.year} {self.make} {self.model}"
        return name.title()

    def read_odometer(self):
        print(f"This car has {self.odometer_reading} miles on it")

    def update_odometer(self, mileage):
        self.odometer_reading = mileage

my_car = Car('audi', 'a4', 2019)
print(my_car.describe())
my_car.update_odometer(24)

my_car.read_odometer()

这样我们通过调用方法update_odometer更新了它的里程数。下面增加一些逻辑,禁止将读数回调

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def describe(self):
        name = f"{self.year} {self.make} {self.model}"
        return name.title()

    def read_odometer(self):
        print(f"This car has {self.odometer_reading} miles on it")

    def update_odometer(self, mileage):
        if mileage >= self.odometer_reading:
            self.odometer_reading = mileage
        else :
            print("You can't roll back an odometer!")

my_car = Car('audi', 'a4', 2019)

my_car.update_odometer(24)
my_car.read_odometer()

my_car.update_odometer(50)
my_car.read_odometer()

my_car.update_odometer(33)
my_car.read_odometer()

现在调用这个方法时,无法将里程数回调了

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

如果我们希望增加属性的值而不是赋值给他,稍微修改一下代码就可以了

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def describe(self):
        name = f"{self.year} {self.make} {self.model}"
        return name.title()

    def read_odometer(self):
        print(f"This car has {self.odometer_reading} miles on it")

    def increment_odometer(self, mileage):
        if mileage > 0 :
            self.odometer_reading += mileage
        else :
            print("You can't roll back an odometer!")

my_car = Car('audi', 'a4', 2019)

my_car.increment_odometer(24)
my_car.read_odometer()

my_car.increment_odometer(50)
my_car.read_odometer()

my_car.increment_odometer(-3)
my_car.read_odometer()

这个方法将里程表读数增加指定的量,量为负数时则输出"You can't roll back an odometer!"

5.继承

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

子类的方法__init__()

在既有类的基础上编写新类的时候,通常要调用父类的方法__init__()。这将初始化在父类__init__()中定义所有的属性,从而让子类包含这些属性。

例如下面模拟电动汽车。可以在前面创建的Car类上的基础上创建新类ElectricCar。这样就只需要为电动汽车特有的属性和行为编写代码

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def describe(self):
        name = f"{self.year} {self.make} {self.model}"
        return name.title()

    def read_odometer(self):
        print(f"This car has {self.odometer_reading} miles on it")

    def increment_odometer(self, mileage):
        if mileage > 0 :
            self.odometer_reading += mileage
        else :
            print("You can't roll back an odometer!")

class ElectricCar(Car):
    def __init__(self, make, model, year):
        super().__init__(make, model, year)

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

ElectricCar类具备Car类的所有功能。

创建子类时,父类必须包含于当前文件中,且位于子类前。定义子类时,必须在圆括号内指定父类的名称。方法__init__()接受创建Car实例所需的信息。

super()是一个特殊函数,让你能够调用父类的方法。这行代码让python调用Car类的方法__init__(),让ElectricCar包含这个方法中定义的所有属性。父类也称为超类,名称super由此而来。

这里创建了一辆电动汽车。目前它还没有特别之处呢。

给子类定义属性和方法

让一个类继承另一个类后,就可以添加区分子类和父类所需的新属性和新方法了。

下面添加一个电动汽车特有的属性,以及一个描述该属性的方法。我们将存储电瓶容量,并且编写一个打印电瓶描述的方法。

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def describe(self):
        name = f"{self.year} {self.make} {self.model}"
        return name.title()

    def read_odometer(self):
        print(f"This car has {self.odometer_reading} miles on it")

    def increment_odometer(self, mileage):
        if mileage > 0 :
            self.odometer_reading += mileage
        else :
            print("You can't roll back an odometer!")

class ElectricCar(Car):
    def __init__(self, make, model, year):
        super().__init__(make, model, year)
        self.battery_size = 75

    def describe_battery(self):
        print(f"This car has a {self.battery_size}-kWh battery.")

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

现在,我们给它加了一个电瓶!电瓶容量初始值75,还有一个方法用来显示电瓶容量

ElectricCar类创建的所有实例都将包含该属性和该方法,但所有Car类实例都不包含它。

重写父类的方法

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

假设Car有一个名为fill_gas_tank()的方法,它对于电动汽车来说是毫无意义的。因此你可能想重写他。下面演示了一种重写形式:

class ElectricCar(Car):
	--snip--
	
	def fill_gas_tank(self):
		print("This car doesn't need a gas tank!")

其实本质上就是现在的方法覆盖了父类的方法,所以叫重写嘛

现在,如果有人对电动汽车调用方法fill_gas_tank,python将忽略父类中的而使用电动汽车中的

6.将实例用作属性

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

例如,当我们不断给电动汽车类添加细节时,我们可能发现其中包含很多专门针对汽车电瓶的属性和方法。在这种情况下,可以将这些属性和方法提取出来,放到一个Battery的类中,并将一个Battery实例作为ElectricCar类的属性:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def describe(self):
        name = f"{self.year} {self.make} {self.model}"
        return name.title()

    def read_odometer(self):
        print(f"This car has {self.odometer_reading} miles on it")

    def increment_odometer(self, mileage):
        if mileage > 0 :
            self.odometer_reading += mileage
        else :
            print("You can't roll back an odometer!")
class Battery:
    def __init__(self, battery_size=75):
        self.battery_size = battery_size

    def describe_battery(self):
        print(f"This car has a {self.battery_size}-kWh battery.")

class ElectricCar(Car):
    def __init__(self, make, model, year):
        super().__init__(make, model, year)
        self.battery_size = 75
        self.battery = Battery()


my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.describe())
my_tesla.battery.describe_battery()

ElectricCar类中,添加了一个名为self.battery的属性。这行代码让python创建一个新的Battery实例(没有指定容量默认赋值75),并将该实例赋予属性self.battery。每次方法__init__被调用时,都将执行该操作,因此现在每个ElectricCar实例都包含一个自动创建的实例。

my_tesla.battery.describe_battery()

这行代码让python在实例my_tesla中查找属性battery,并对存储在该属性中的Battery实例调用方法describe_battery(),输出与之前的相同

这看似做了很多无用功,但实际上我们加深了层次感,下面我们给Battery类添加一个方法,它根据电瓶容量报告汽车的续航里程。

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def describe(self):
        name = f"{self.year} {self.make} {self.model}"
        return name.title()

    def read_odometer(self):
        print(f"This car has {self.odometer_reading} miles on it")

    def increment_odometer(self, mileage):
        if mileage > 0 :
            self.odometer_reading += mileage
        else :
            print("You can't roll back an odometer!")
class Battery:
    def __init__(self, battery_size=75):
        self.battery_size = battery_size

    def describe_battery(self):
        print(f"This car has a {self.battery_size}-kWh battery.")

    def get_range(self):
        if self.battery_size == 75:
            range = 260
        elif self.battery_size == 100:
            range = 315

        print(f"This car can go about {range} miles on a full charge")

class ElectricCar(Car):
    def __init__(self, make, model, year):
        super().__init__(make, model, year)
        self.battery_size = 75
        self.battery = Battery()


my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.describe())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()

7.导入类

随着不断给类添加功能,文件可能变得很长,即使妥善使用了继承也会如此。为了让文件尽可能简洁,python允许将类存储在模块中,然后在主程序中导入需要的模块。

导入单个类

我们把Car类放入一个单独的模块car中。

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def describe(self):
        name = f"{self.year} {self.make} {self.model}"
        return name.title()

    def read_odometer(self):
        print(f"This car has {self.odometer_reading} miles on it")

    def increment_odometer(self, mileage):
        if mileage > 0 :
            self.odometer_reading += mileage
        else :
            print("You can't roll back an odometer!")

下面创建另一个文件,在其中导入Car类并创建其实例:

from car import Car

my_car = Car('audi', 'a4', 2019)

my_car.update_odometer(24)
my_car.read_odometer()

my_car.update_odometer(50)
my_car.read_odometer()

my_car.update_odometer(33)
my_car.read_odometer()

import语句让python打开模块car并且导入其中的Car类。这样我们就可以使用Car类,类似于前面引入函数

在一个模块中存储多个类

可以将多个作用类似的类存储于一个模块中

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0

    def describe(self):
        name = f"{self.year} {self.make} {self.model}"
        return name.title()

    def read_odometer(self):
        print(f"This car has {self.odometer_reading} miles on it")

    def increment_odometer(self, mileage):
        if mileage > 0 :
            self.odometer_reading += mileage
        else :
            print("You can't roll back an odometer!")
class Battery:
    def __init__(self, battery_size=75):
        self.battery_size = battery_size

    def describe_battery(self):
        print(f"This car has a {self.battery_size}-kWh battery.")

    def get_range(self):
        if self.battery_size == 75:
            range = 260
        elif self.battery_size == 100:
            range = 315

        print(f"This car can go about {range} miles on a full charge")

class ElectricCar(Car):
    def __init__(self, make, model, year):
        super().__init__(make, model, year)
        self.battery_size = 75
        self.battery = Battery()

这些都是和汽车相关的类,因此我们把它们都放到car模块中。

从一个模块中导入多个类
from car import Car,ElectricCar

my_car = Car('audi', 'a4', 2019)

my_car.update_odometer(24)
my_car.read_odometer()

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

my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
导入整个模块
import car
导入模块中的所有类
from car import *

不推荐这样导入,原因与之前导入函数时所说的一样

使用别名

和前面函数一章时所说的相似,导入类时,也可为其指定别名。

from car import Car as C

8.python标准库

python标准库是一组模块,我们安装的python都包括它。现在我们可以使用标准库中的任何函数和类,只需要在程序开头包含一条import语句。让我们先来了解一下模块random

from random import randint
num = randint(1,6)
print(num)

在这个模块中,randit()函数将两个整数作为参数,并随即返回一个位于这两个整数之间(含)的整数。也就是说,你每次运行这个模块的结果都在变化。

from random import choice
players = ['a','b','c','d']
first_up = choice(players)
print(first_up)

另一个有用的函数是choice(),它将一个列表或者元组作为参数,并随机返回其中的一个元素

9.类编码风格

  • 类名应采用驼峰命名法,实例名和模块名在单词之间加下划线
  • 对于每个类,最好在类定义的后面包含一个文档字符串,简要描述其功能。每个模块也应该有一个文档字符串,对其中的类可用于做什么进行描述。
  • 可使用空行组织代码,但不要滥用。类中使用空行分隔方法,模块中使用两个空行来分隔类
  • 同时导入标准库模块和自定义模块时,先编写导入标准库模块的import语句,再添加一个空行,然后编写导入你自己编写的模块的import语句。

九、文件和异常

1.从文件中读取数据

读取整个文件

我们现在有一个文件包含圆周率值,它的名字是pi_digits.txt

3.14159
  26535
  89793

如何在一个程序中调用该文件?

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

这个程序在其源文件所在的目录中查找pi_digits.txt。函数open()返回一个表示文件的对象,在本例中,open ('pi_digits.txt')返回一个表示文件pi_digits.txt的对象,python将该对象赋给file_object供以后使用。

关键字with在不再需要访问文件时将其关闭。在这个程序中,我们调用了open(),但没有调用close()。如果在程序中过早调用close(),你会发现需要使用文件时它已关闭。

有了表示文件pi_digits.txt的对象后,使用方法read()读取这个文件的全部内容,并将其作为一个字符串赋给变量contents,这样,通过打印contents的值,我们就可以将这个文本文件的全部内容显示出来

3.14159
  26535
  89793

相比于原始文件,该输出唯一不同的地方是末尾多了一个空行。为何会多出这个空行呢?因为read()到达文件末尾时返回一个空字符串,而将这个空字符串显示出来时就是一个空行。要删除多出来的空行,可在函数调用print()中使用rstrip():

with open ('pi_digits.txt') as file_object:
	contents = file_object.read()
print(contents.rstrip())

前面说过,rstrip()方法删除字符串末尾的空白

文件路径

像刚刚那样的代码,python只会在当前执行文件的所在目录中查找,如何在不同目录下查找?

例如,你可能将源文件存在了work文件夹中,而该文件夹中有一个名text_files的文件夹用于存储程序文件操作的文本文件。那我们可以使用如下的代码:(相对路径)

with open('text_files//filename.txt') as file_object:

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

除了相对路径,我们还可以使用绝对路径,即直接告诉python文件在计算机中的准确位置。绝对路径通常比较长,因此不妨将其赋给一个变量,再将该变量传给open()

path = 'D:\\python_files\\text_files.txt'
with open(path) as file_object:

在使用反斜杠时,不能直接将路径复制过来了事。因为反斜杠本身是用来对字符进行转义的。所以在复制路径后,对每个反斜杠进行转义,即在其前面再加一个反斜杠

逐行读取

读取文件时,常常需要检查其中的每一行。要以每次一行的方式检查文件,可对文件对象使用for循环:

path = 'D:\\python_files\\text_files.txt'
with open(path) as file_object:
	for line in file_object:
		print(line)

这样打印出来的结果会有很多的空白行

3.14159

  26535
  
  89793
  

为何会有如此多的空白行?因为在这个文件中,每行的末尾都有一个看不见的换行符,而函数调用print()也会加上一个换行符,因此每行末尾都有两个换行符。要想消除这些多余的空白行,我们还是使用rstrip():

path = 'D:\\python_files\\text_files.txt'
with open(path) as file_object:
	for line in file_object:
		print(line.rstrip())

现在的输出又与文件内容相同了

3.14159
  26535
  89793

我们还可以将各行内容存在一个列表中,再在with代码块以外打印

path = 'D:\\python_files\\text_files.txt'
with open(path) as file_object:
	lines = file_object.readlines()

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

方法readlines()从文件中读取每一行,并将其存储在一个列表内。接下来该列表被赋给变量lines。后面使用一个简单的for循环打印lines中的各行,输出还是和文件内容完全一致。

使用文件中的内容

将文件读入到内存后,就能以任何方式使用这些数据了。下面使用刚刚那个圆周率值。

path = 'D:\\python_files\\text_files.txt'
with open(path) as file_object:
	lines = file_object.readlines()

pi_string = ''
for line in lines:
	pi_string += line.rstrip()
	
print(pi_string)
print(len(pi_string))

输出如下所示

3.14159  26535  89793
21

变量pi_string指向的字符串包含原来位于每行左边的空格,为删除这些空格,可使用strip()而非rstrip():

path = 'D:\\python_files\\text_files.txt'
with open(path) as file_object:
    lines = file_object.readlines()

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

print(pi_string)
print(len(pi_string))

输出如下所示

3.141592653589793
17

这样就获得了一个字符串,其中包含准确到15小数的圆周率值,这个字符串长17字符,因为它还包含整数部分的3和小数点

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

包含一百万位的大型文件

如果我们有一个文本文件,其中包含精确到小数点后一百万位的圆周率值,也可创建一个包含所有这些数字的字符串。在这里,我们只打印到小数点后50位,以免终端显示所有位数。

path = 'D:\\python_files\\pi_million.txt'
with open(path) as file_object:
    lines = file_object.readlines()

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

print(f"{pi_string[:52]}...")
print(len(pi_string))

但是长度还是100,0002

圆周率值中包含你的生日吗

我们可以用一个程序检测圆周率中是否包含你的生日

path = 'D:\\python_files\\pi_million.txt'
with open(path) as file_object:
    lines = file_object.readlines()

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

birthday = input("Enter your birthday: ")
if birthday in pi_string:
    print("Your birthday appears in the first million digits of pi!")
else:
    print("Your birthday does not appear in the first million digits of pi!")

2.写入文件

写入空文件

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

我们从下面的例子来体会一下

filename = 'programing.txt'

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

在本例中,调用open()提供了两个实参。第一个实参也是要打开的文件的名称,第二个实参告诉python,要以写入模式打开这个文件。打开文件时,可指定读取模式(‘r’),写入模式(‘w’),附加模式(‘a’),读写模式(‘r+’)。如果省略了模式实参,python将以默认的只读模式打开文件

如果要写入的文件不存在,函数open()将自动创建它。然而,以写入模式(‘w’)打开文件时要千万小心,因为如果指定的文件已经存在,python会先清空该文件的内容。

该代码中使用文件对象的方法write(),将一个字符串写入文件。这个程序没有终端输出,但如果打开文件programing.txt,将看到其中包含如下一行内容

I love summer

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

写入多行

函数write()不会在写入的文本末尾添加换行符,因此如果你想要输入多行,你需要在每次的print()语句中加上换行符,如下所示:

filename = 'programing.txt'

with open(filename, 'w') as file_object:
    file_object.write("I love summer.\n")
    file_object.write("I love you.\n")

现在输出出现在不同的行中。

像显示到终端的输出一样,还可以使用空格、制表符和空行来设置这些输出的格式

附加到文件

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

filename = 'programing.txt'

with open(filename, 'a') as file_object:
    file_object.write("Do you love me?\n")
    file_object.write("Hhhhh\n")

在运行了上一块代码后运行该块代码,则现在文件中的内容如下所示

I love summer.
I love you.
Do you love me?
Hhhhh

3.异常

python使用称为异常的特殊对象来管理程序执行期间发生的错误。每当发生让python不知所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常的代码, 程序将继续运行;否则程序将停止并且显示traceback,其中包含有关异常的报告

异常是使用try-except代码块来处理的。try-except代码块让python执行指定的操作,同时告诉python告诉发生异常时该怎么办。使用try-except代码块时,即便出现异常,程序也将继续运行

处理ZeroDivisionError异常

运行以下代码

print(5/0)

python给出traceback

Traceback (most recent call last):
  File "D:/jetbrains/python/sss.py", line 1, in <module>
    print(5/0)
ZeroDivisionError: division by zero

在上述traceback中,ZeroDivisionError是个异常对象。python无法按你的要求做时,就会创建这种对象。下面让我们告诉python发生这种错误时怎么办。

使用try-except代码块
try:
    print(5/0)
except ZeroDivisionError:
    print("You can't divide by zero!")

将导致错误的代码行放在一个try代码块中。如果try代码块中的代码运行起来没有问题,python将跳过except代码块;如果try代码块中的代码导致了错误,python将查找与之匹配的except代码块并且运行其中的代码。

在本例中,try代码块中的代码引发了ZeroDivisionError异常,因此python查找指出了该怎么办的except代码块,并运行其中的代码。这样,将会有以下输出显示

You can't divide by zero!

如果后面还有代码,程序将继续运行

使用异常避免崩溃

发生错误时,如果程序还有工作尚未完成,妥善处理错误就尤其重要。如果程序能够妥善地处理无效输入,就能再提示用户提供有效输入,而不至于崩溃。

下面来创建一个只执行除法运算的简单计算器:

first = input("First number:")
second = input("Second number:")
answer = int(first)/int(second)
print(answer)

这个计算器会在第二个数为0时崩溃,那我们应该怎么改进呢?

else代码块
first = input("First number:")
second = input("Second number:")

try:
    answer = int(first) / int(second)
except ZeroDivisionError:
    print("You can't divide by zero!")
else:
    print(answer)

这个示例还包含一个else代码块,依赖try代码块成功执行的代码都应放到else代码块中

try-except-else代码块的工作原理大致如下。python尝试执行try代码块中的代码,只有可能引发异常的代码才放到try语句中。仅在try代码块成功执行时才需要运行的代码应放在else代码块中。except代码块告诉python如果try代码块中的代码出错应该如何做。

处理FileNotFoundError异常
filename = 'alice.txt'

with open(filename, encoding='utf-8') as f:
	contents = f.read()

相比于本章前面的文件打开方式,这里给参数encoding指定了值,在系统的默认编码与要读取文件使用的编码不一致时,必须这样做。

然而实际上我们并没有这样一个文件,因此它将引发一个异常

Traceback (most recent call last):
  File "D:/jetbrains/python/sss.py", line 3, in <module>
    with open(filename, encoding='utf-8') as f:
FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'

在本例中,这个错误是函数open()导致的,因此要处理这个错误,必须将try语句放在open前

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

这样将会有如下输出

sorry, the file does not exist.
分析文本

你可以分析包含整本书的文本文件,下面来提取《爱丽丝漫游奇境记》的文本,并尝试计算它共有多少个单词。我们将使用方法split(),他能根据一个字符串创建一个单词列表

filename = 'alice.txt'
try:
    with open(filename, encoding='utf-8') as f:
        contents = f.read()
except FileNotFoundError:
    print("sorry, the file does not exist.")
else:
    # 计算该文件大致包含多少个单词
    words = contents.split()
    num_words = len(words)
    print(f"The file {filename} has about {num_words} words.")

我们将文件alice.txt移到正确的目录下让try代码块得以执行。对变量contents调用方法split(),以生成一个列表,其中包含这部童话中的所有单词。使用len()来确定这个列表的长度时,就能知道原始字符串大概包含多少个单词了

这个数会稍微大一点,因为使用的文本文件包含出版商提供的额外信息

使用多个文件

下面多分析几本书,在此之前,先将这个程序的大部分代码移到一个函数中

def count_words(filename):
    """计算一个文件大致包含多少个单词"""
    try:
        with open(filename, encoding='utf-8') as f:
            contents = f.read()
    except FileNotFoundError:
        print("sorry, the file does not exist.")
    else:
        # 计算该文件大致包含多少个单词
        words = contents.split()
        num_words = len(words)
        print(f"The file {filename} has about {num_words} words.")

filename = 'alice.txt'
count_words(filename)

现在我们可以将要分析的文件名称存储在一个列表中,然后对列表中的每个文件调用该函数

def count_words(filename):
    """计算一个文件大致包含多少个单词"""
    try:
        with open(filename, encoding='utf-8') as f:
            contents = f.read()
    except FileNotFoundError:
        print("sorry, the file does not exist.")
    else:
        # 计算该文件大致包含多少个单词
        words = contents.split()
        num_words = len(words)
        print(f"The file {filename} has about {num_words} words.")

filenames = ['alice.txt', 'siddhartha.txt', 'little_women.txt']
for filename in filenames:
    count_words(filename)
静默失败

如果我们希望捕获到异常时程序保持静默,我们可以使用pass语句

def count_words(filename):
    """计算一个文件大致包含多少个单词"""
    try:
        with open(filename, encoding='utf-8') as f:
            contents = f.read()
    except FileNotFoundError:
        pass
    else:
        # 计算该文件大致包含多少个单词
        words = contents.split()
        num_words = len(words)
        print(f"The file {filename} has about {num_words} words.")

filenames = ['alice.txt', 'siddhartha.txt', 'little_women.txt']
for filename in filenames:
    count_words(filename)

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

4.存储数据

很多程序都要求用户输入某种信息,用户关闭程序时,几乎总是要保存这些数据。一种简单的方式是使用模块json来接收数据。模块json让你能够将简单的python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据。你还可以使用json在python程序之间分享数据

更重要的是,JSON数据格式并非python专用。这让你能够将以JSON格式存储的数据与使用其他编程语言的人分享

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

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

我们编写一个存储一组数的简短程序,再编写一个将这些数读取到内存中的程序。

函数json.dump()接受两个实参:要存储的数据,以及可用于存储数据的文件对象。下面演示如何使用json.dump()来存储列表:

import json

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

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

先导入模块json,再创建一个数字列表。接着指定要将该数字列表存储到哪个文件中,通常使用扩展名.json来指出文件存储的数据为JSON格式。以写入模式打开该文件,让json能将数据写入其中。最后使用函数json.dump()将数字列表存储到文件中。

下面再编写一个程序,使用json.load()将列表读取到内存中:

import json

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

print(numbers)

打印结果与刚刚存入的数字列表相同

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

来看一个例子:提示用户在首次运行程序时输入自己的名字,并在再次运行程序时记住他。

先存储用户名字

import json

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

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

现在再编写一个程序,向已经存储了名字的用户发出问候:

import json

filename = 'username.json'

with open(filename) as f:
    username = json.load(f)
    print(f"Welcome back, {username}!")

需要将这两个程序读入到一个程序中,这个程序运行时,先尝试从文件中获取用户名,如果用户名不存在,就提示用户输入用户名;否则打印问候消息

import json

# 如果以前存储用户名,就加载他
# 否则提示用户输入用户名并存储
filename = 'username.json'
try:
    with open(filename) as f:
        username = json.load(f)
except FileNotFoundError:
    username = input("What is your name:")
    with open(filename, 'w') as f:
        json.dump(username, f)
        print(f"We'll remember you when you come back, {username}!")
else:
    print(f"Welcome back, {username}!")
重构

代码能够运行,但通过将其划分为一系列完成具体工作的函数,还可以改进。这样的过程称为重构。重构让代码更清晰、更易于理解、更容易扩展

要重构我们刚刚那个文件,可将其大部分逻辑放到一个或多个函数中,如下所示

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

greet_user()

但函数greet_user()所做的不仅仅是问候用户,还在存储了用户名的时候获取它、在没有用户名时提示用户输入。

下面我们再次对该函数重构,减少其任务

import json

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

def get_new_username():
    """提示用户输入用户名并存储它"""
    filename = 'username.json'
    username = input("What is your name:")
    with open(filename, 'w') as f:
        json.dump(username, f)
    return username

def greet_user():
    """问候用户,并指出其名字"""
    username = get_stored_username()
    if username:
        print(f"Welcome back, {username}!")
    else:
        username = get_new_username()
        print(f"We'll remember you when you come back, {username}!")

greet_user()

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

十、测试代码

1.测试函数

下面是一个简单的函数

def get_formatted_name(first, last):
    """生成整洁的姓名"""
    full_name = f"{first} {last}"
    return full_name.title()

如果我们编写一个程序来进行测试很烦琐。python提供了一种自动测试函数输出的高效方式。

单元测试和测试用例

python标准库中的模块unittest提供了代码测试工具。单元测试用于核实函数某个方面没有问题。测试用例是一组单元测试,它们一道核实函数在各种情形下的行为都符合要求。全覆盖的测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。

对于大型项目要进行全覆盖测试可能很难,通常最初只要针对代码的重要行为编写测试即可,等项目被广泛使用时再考虑全覆盖

可通过的测试

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

import unittest
from sss import get_formatted_name

class NamesTestCase(unittest.TestCase):
    """测试sss.py"""

    def test_first_last_name(self):
        """能处理像Liuyun Tian这样的姓名吗"""
        formatted_name = get_formatted_name('liuyun', 'tian')
        self.assertEqual(formatted_name, 'Liuyun Tian')

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

首先导入了模块unittest和要测试的函数。接着我们创建了一个名为NamesTestCase的类,用于包含一系列对get_formatted_name()的单元测试。这个类必须继承unittest.TestCase类,这样python才知道如何运行你编写的测试。

NamesTestCase只包含一个方法,用于测试get_formatted_name()的一个方面。将该方法命名为test_first_last_name(),因为要核实的是只有名和姓的姓名能否被正确格式化。运行该文件时,所有以test_打头的方法都将自动运行。在这个方法中,调用了要测试的函数。在本例中,使用实参’liuyun’和’tian’调用该函数,并将结果赋给变量formatted_name

我们使用了unittest类最有用的方法之一:断言方法。断言方法核实得到的结果是否与期望的结果一致。

self.assertEqual(formatted_name, 'Liuyun Tian')

这行代码的意思是将formatted_name的值与字符串’Liuyun Tian’比较,看它们是否相等。

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

得到输出如下所示

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

句点表明有一个测试通过了,接下来一行指出python运行了一个测试,消耗时间不到0.001秒。OK表示该测试样例中所有单元测试都已通过

未通过的测试

我们修改一下函数get_formatted_name

def get_formatted_name(first, middle, last):
    """生成整洁的姓名"""
    full_name = f"{first} {middle} {last}"
    return full_name.title()

再执行刚刚的测试样例,发现出错

E
======================================================================
ERROR: test_first_last_name (__main__.NamesTestCase)
能处理像Liuyun Tian这样的姓名吗
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/jetbrains/python/python.py", line 9, in test_first_last_name
    formatted_name = get_formatted_name('liuyun', 'tian')
TypeError: get_formatted_name() missing 1 required positional argument: 'last'

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

FAILED (errors=1)

里面包含的信息很多:

  • E指出测试用例中有一个单元测试导致了错误

  • ERROR: test_first_last_name (__main__.NamesTestCase)
    能处理像Liuyun Tian这样的姓名吗
    

    告诉我们是哪里导致了错误

  • traceback指出函数调用get_formatted_name('liuyun', 'tian')有问题

  • 运行了一个单元测试在0.001s内

  • 运行测试用例共发生了1个错误

测试未通过时怎么做

测试未通过时不要修改测试,而是应该修改函数

def get_formatted_name(first, last, middle=''):
    """生成整洁的姓名"""
    if middle:
        full_name = f"{first} {middle} {last}"
    else:
        full_name = f"{first} {last}"
    return full_name.title()

现在测试用例又能通过了

添加新测试

我们再添加一个测试,测试包含中间名的姓名

import unittest
from sss import get_formatted_name

class NamesTestCase(unittest.TestCase):
    """测试sss.py"""

    def test_first_last_name(self):
        """能处理像Liuyun Tian这样的姓名吗"""
        formatted_name = get_formatted_name('liuyun', 'tian')
        self.assertEqual(formatted_name, 'Liuyun Tian')

    def test_first_last_middle_name(self):
        """能处理像Liuyun Bin Tian这样的姓名吗"""
        formatted_name = get_formatted_name(
            'liuyun', 'tian', 'bin')
        self.assertEqual(formatted_name, 'Liuyun Bin Tian')


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

结果如下所示

..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

方法名必须以test_打头,这样它才会在我们运行这个文件时自动运行

2.测试类

之前我们编写了针对单个函数的测试,下面来编写针对类的测试

各种断言方法

以下描述了六个常见的断言方法

方法用途
assertEqual(a, b)核实a == b
assertNotEqual(a, b)核实a != b
assertTrue(x)核实x为True
assertFalse(x)核实x为False
assertIn(item, list)核实item在list中
assertNotIn(item, list)核实item不在list中
一个要测试的类

来看一个帮助管理匿名调查的类

class AnonymousSurvey:
    """收集问卷答案"""

    def __init__(self, question):
        """存储一个问题,并为存储答案做准备"""
        self.question = question
        self.responses = []

    def show_question(self):
        """显示问卷"""
        print(self.question)

    def store_response(self, new_response):
        """存储单份问卷"""
        self.responses.append(new_response)

    def show_results(self):
        """显示收集到的所有问卷"""
        print("Survey results:")
        for response in self.responses:
            print(f"-{response}")

这个类首先存储了一个调查问题,并创建了一个空列表,用于存储答案。这个类包含打印调查问题的方法,在答案列表中添加新答案的方法,以及将存储在列表中的答案都打印出来的方法

测试AnonymousSurvey类
import unittest
from sss import AnonymousSurvey

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

    def test_store_single_response(self):
        """测试单个答案会被妥善地存储"""
        question = "What language did you first learn to speak?"
        my_survey = AnonymousSurvey(question)
        my_survey.store_response('English')
        self.assertIn('English', my_survey.responses)


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

首先导入模块unittest和要测试的类。第一个测试方法验证:调查问题的单个答案被存储后,会包含在调查结果列表中。如果这个测试未通过,我们就能通过输出中的方法名得知,在存储单个答案方面存在问题

当我们运行这个程序时,测试通过了

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

OK

下面来核实用户提供三个答案时,它们也将被妥善地存储

import unittest
from sss import AnonymousSurvey

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

    def test_store_single_response(self):
        """测试单个答案会被妥善地存储"""
        question = "What language did you first learn to speak?"
        my_survey = AnonymousSurvey(question)
        my_survey.store_response('English')
        self.assertIn('English', my_survey.responses)

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

        for response in responses:
            self.assertIn(response, my_survey.responses)

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

再次运行时,两个测试都通过了

..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

但这些测试有些重复的地方。下面使用unittest的另一项功能来提高其效率。

方法setUp()

unittest.TestCase类包含的方法setUp()让我们只需创建这些对象一次,就可以在每个测试方法中使用。如果在TestCase类中包含了方法setUp(),python将先运行它,再运行各个以test_打头的方法。这样,在你编写的每个方法中,都可以使用在方法setUp()中创建的对象

下面使用setUp()来创建一个调查对象和一组答案

import unittest
from sss import AnonymousSurvey

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

    def setUp(self):
        """创建一个调查对象和一组答案"""
        question = "What language did you first learn to speak?"
        self.my_survey = AnonymousSurvey(question)
        self.responses = ['English', 'Chinese', 'Japanese']

    def test_store_single_response(self):
        """测试单个答案会被妥善地存储"""
        self.my_survey.store_response(self.responses[0])
        self.assertIn(self.responses[0], self.my_survey.responses)

    def test_store_three_response(self):
        """测试三个答案会被妥善地存储"""
        for response in self.responses:
            self.my_survey.store_response(response)
        for response in self.responses:
            self.assertIn(response, self.my_survey.responses)

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

如果要扩展AnonymousSurvey,使其允许每位用户输入多个答案,这些测试将很有用。修改代码以接受多个答案后,可运行这些测试,确认存储单个答案或一系列答案的行为未受影响。

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

注意,运行测试用例时,每完成一个单元测试,python都打印一个字符:测试通过时打印一个句点,测试引发错误时打印一个E,测试导致断言失败时则打印一个F。


以上为博主自己整理的python入门笔记。由于水平有限,难免出现低级错误,还请各位朋友多多指正。

表中的答案都打印出来的方法

测试AnonymousSurvey类
import unittest
from sss import AnonymousSurvey

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

    def test_store_single_response(self):
        """测试单个答案会被妥善地存储"""
        question = "What language did you first learn to speak?"
        my_survey = AnonymousSurvey(question)
        my_survey.store_response('English')
        self.assertIn('English', my_survey.responses)


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

首先导入模块unittest和要测试的类。第一个测试方法验证:调查问题的单个答案被存储后,会包含在调查结果列表中。如果这个测试未通过,我们就能通过输出中的方法名得知,在存储单个答案方面存在问题

当我们运行这个程序时,测试通过了

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

OK

下面来核实用户提供三个答案时,它们也将被妥善地存储

import unittest
from sss import AnonymousSurvey

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

    def test_store_single_response(self):
        """测试单个答案会被妥善地存储"""
        question = "What language did you first learn to speak?"
        my_survey = AnonymousSurvey(question)
        my_survey.store_response('English')
        self.assertIn('English', my_survey.responses)

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

        for response in responses:
            self.assertIn(response, my_survey.responses)

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

再次运行时,两个测试都通过了

..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

但这些测试有些重复的地方。下面使用unittest的另一项功能来提高其效率。

方法setUp()

unittest.TestCase类包含的方法setUp()让我们只需创建这些对象一次,就可以在每个测试方法中使用。如果在TestCase类中包含了方法setUp(),python将先运行它,再运行各个以test_打头的方法。这样,在你编写的每个方法中,都可以使用在方法setUp()中创建的对象

下面使用setUp()来创建一个调查对象和一组答案

import unittest
from sss import AnonymousSurvey

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

    def setUp(self):
        """创建一个调查对象和一组答案"""
        question = "What language did you first learn to speak?"
        self.my_survey = AnonymousSurvey(question)
        self.responses = ['English', 'Chinese', 'Japanese']

    def test_store_single_response(self):
        """测试单个答案会被妥善地存储"""
        self.my_survey.store_response(self.responses[0])
        self.assertIn(self.responses[0], self.my_survey.responses)

    def test_store_three_response(self):
        """测试三个答案会被妥善地存储"""
        for response in self.responses:
            self.my_survey.store_response(response)
        for response in self.responses:
            self.assertIn(response, self.my_survey.responses)

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

如果要扩展AnonymousSurvey,使其允许每位用户输入多个答案,这些测试将很有用。修改代码以接受多个答案后,可运行这些测试,确认存储单个答案或一系列答案的行为未受影响。

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

注意,运行测试用例时,每完成一个单元测试,python都打印一个字符:测试通过时打印一个句点,测试引发错误时打印一个E,测试导致断言失败时则打印一个F。


以上为博主自己整理的python入门笔记。由于水平有限,难免出现低级错误,还请各位朋友多多指正。

参考资料:《Python编程:从入门到实践(第二版)》

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值