Python基础

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

运行py文件时发生的情况
运行hello_world.py文件时,末尾的.py指出这是一个Python程序,因此编辑器将使用Python解释器来运行它。Python解释器读取整个程序,确定每个单词的含义。例如print,解释器就将括号里的内容打印到屏幕上。

变量名只能包含字母、数字和下划线,不能以数字开头,而且应该全部小写。

字符串拼接
first_name = 'ada'
last_name = 'lovelace'
full_name = first_name + " " + full_name

使用制表符或换行符来添加空白
print("Languages:\n\tPython\n\tJavaScript")

删除空白
favorite_language = 'python '
favorite_language.rstrip()
strip()和lstrip()是删除开头的空格
rstrip()是删除结尾的空格

第三章 列表

列表简介
bicycles = ['trek', 'cannondale', 'redline', 'specialized']
列表是有序集合,因此要访问列表的任何元素,只需要将元素的位置或者索引告诉Python即可。
索引是从0开始
索引的最后一个是-1,倒数第二个是-2,以此类推。
修改列表元素
motocycles = ['honda', 'yamaha', 'suzuki']
mococycles[0]='ducati'

添加元素
1、在末尾添加
motocycles = [] #先创建空的列表
motocycles.append('ducati')

2、在列表中插入元素
motocycles = ['honda', 'yamaha', 'suzuki']
motocycles.insert(1, 'ducati')
方法insert()在索引1处添加空间,并将值'ducati'存储到这个地方。这种操作将列表中排序1之后的每个元素都右移一个位置。

3、从列表中删除元素
motocycles = ['honda', 'yamaha', 'suzuki']
del motocycles[0]

pop删除
motocycles.pop()
弹出列表最后一个元素
弹出特定位置元素
motocycles.pop(1) #弹出第二个元素
被弹出的元素可以继续复制使用
比如first_owned = motocycles.pop(0)
虽然列表中没有该元素,但是该元素值可以继续使用

4、根据值删除元素
motocycles = ['honda', 'yamaha', 'suzuki']
motocycles.remove('honda')

组织列表
sort()对列表进行永久排序
sort(reverse=True)倒序排序

sorted()对列表进行临时排序,原来的列表的顺序并没有变

reverse()反转列表,这里的反转是指元素的排列顺序反转

len()确定列表的长度

第四章 操作列表

遍历列表list
motocycles = ['honda', 'yamaha', 'suzuki']
for moto in motocycles:
print(moto)

创建数字列表
使用函数range(),range()让你能够生成一系列的数字。
for value in range(1,5):
print(value)
打印不包括5

要创建数字列表,可以使用函数list()将range()的结果直接转换为列表。
numbers = list(range(1,5))
print(numbers)

[1,2,3,4]

range()还可以指定步长,如range(2,11,2),从2开始,不断的加2,直到11(不包括11)

数字列表的简单计算
min(numbers)
max(numbers)
sum(numbers)

使用列表的一部分
切片
列表的部分元素-Python称之为切片
players = ['charles', 'martina', 'michael, 'florence', 'eli']
提取第2-4个元素。
players[1:4]
如果没有指定第一个索引,那就是从开头开始
players[:4]
如果没有指定第二个索引,那就直接到末尾
players[2:]
负数索引能够让你输出特定位置到列表末尾的所有元素
players[-2:]
将输出-1,-2两个元素

遍历切片
for player in players[1:3]:
print player

复制列表
假如我喜欢的食物,有一个朋友也喜欢,然后我喜欢的列表多了一样,朋友喜欢的也多了一样。
刚开始三样一样,如何复制列表?
my_foods =['pizza', 'falafel', 'carrot cake']
friend_foods = my_foods

my_foods.append('cannoli')
friend_foods.append('ice cream')

print(my_foods)
print(friend_foods)
你会发现,这两个列表输出的一模一样,而且cannoli和ice cream都在列表里面。为什么呢,因为friend_foods = my_foods并没有复制列表,而只是新建了一个friend_foods变量,关联到原来的my_foods上。所以,改动其实是在一个列表上。
那如何正确复制?利用切片。
my_foods =['pizza', 'falafel', 'carrot cake']
friend_foods = my_foods[:]

my_foods.append('cannoli')
friend_foods.append('ice cream')

print(my_foods)
print(friend_foods)

元组tuple
列表可以动态调整,使得满足各种需求,如果你有不变的列表,那这样的列表可以被称为元组。

定义元组
元组看起来和列表类似,区别在于元组是圆括号,列表是方括号。

dimensions = (200, 50)
dimensions[0] = 250
这里复制会报错,因为tuple不能被改变

遍历元组
for dimension in dimensions:
print(dimension)
虽然不能更改单个元组某个元素的值,但是可以更改整个元组的值。
dimensions = (200, 50)
dimensions = (400, 100)
这里并不会报错。

第五章 if语句

判断相等 ==
判断不等 !=
检查特定值是否包含在列表中
request_topping=['mushrooms', 'onions', 'pineapple']
'mushrooms' in request_toppings

判断特定值是否不包含在列表中
if 'mushrooms' not in request_toppings:

if else
if elif else
if

第六章 字典

字典:包含key-value键值对的数据结构。
alien={'color':'green', 'points':5}
可将任何字段用作key,包括数字、字符串、列表乃至字典。

访问字典中的值
alien['color']

添加键值对
alien['x_position']=0
alien['y_position']=250

空字段
alien={}

修改字典中的值
alien['color']=‘yellow'

删除键值对
del alien['points']

遍历字典
for key,value in alien:
print(key,value)
注意,键值对返回的顺序和存储顺序不同,Python不关心键值对的存储顺序,而只关心键值对之间的关联关系。

遍历字典中的所有键
for key in alien.keys():#for key in alien效果一样

按顺序遍历字典中所有键
要以特定的顺序返回元素,一种方法是在for循环中对返回的键进行排序,为此,可以用sorted()来排序
for key in sorted(alien.keys()):

遍历字典中所有值
for value in alien.values():

如果值有重复,那输出重复的值没有意义,如何去重:可以使用集合
for value in set(alien.values()):

嵌套
字典列表
alien_0={'color':'green', 'points':5}
alien_1={'color':'yellow', 'points':10}

aliens = [alien_0, alien_1]

在字典中存储列表
pizza = {'crust':'thick', 'topping': ['mushroom','extra cheese'],}

在字典中存储字典

用户输入和while循环

name = input("Please enter your name:")
print(name)
input()将用户输入解读为字符串
如果输入的是数字字符串,可以通过int()将其转化成数字

求模运算符
4%3
求模运算符,将两个数相除并符合余数。

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

另一种写法:break
使用break退出循环
while True:
message=input()
if message == 'quit':
break
else:
print(message)

continue:继续使用循环,从头开始执行
打印10以内的奇数
current_number = 0
while current_number < 10:
current_number += 1
if current_number % 2 == 0:
continue
print(current_number)

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

unconfirmed_users = ['alice','brain', 'candace']

while unconfirmed_users:
current_user = unconfirmed_users.pop()

删除包含特定值的所有列表元素
pets = ['dog','cat','dog','goldfish','cat','rabbit','cat']

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

函数

def greet_user(username):
print("hello!," + username.title() + "!")

greet_user('sarah')
实参和形参
username是一个形参,'sarah'是一个实参。将实参'sarah'传递给函数的形参username,值被存储在形参username中。

位置实参
def describe_pet(animal_type, pet_name):
print("\nI have a " + animal_type +".")
print("My " + animal_type + "'s name is " + pet_name.title() + ".")
describe_pet('dog', 'willie')
调用函数时,Python必须将函数调用中的每个实参都关联到函数定义中的一个形参,最简单的关联方式就是基于实参的顺序。这种关联方式称为位置实参。

关键字实参
关键字实参是传递给函数的名称-值对。你直接在实参中将名称和值关联起来。因此向函数传递实参时不会混淆。关键字实参让你无需考虑函数调用中的实参顺序,还清楚的指出了函数调用中各值的用途。
describe_pet(animal_type = 'dog', pet_name = 'willie')

默认值
def describe_pet(pet_name, animal_type='dog'):
print("\nI have a " + animal_type +".")
print("My " + animal_type + "'s name is " + pet_name.title() + ".")

describe_pet(pet_name='willie')
也可以通过位置实参调用
describe_pet('willie')
使用默认值时,在形参列表中必须先列出没有默认值的形参,再列出有默认值的形参。这让Python依然能够正确解读位置参数。

返回值
def get_formatted_name(first_name, last_name):
full_name = first_name + ' ' + last_name
return full_name.title()

返回字典
def build_person(first_name,last_name):
person = {'first_name':first_name,'last_name':last_name}
return person

禁止函数修改列表
有时候,我们需要禁止修改列表。为了解决这个问题,可以向函数传递列表的副本,而不是列表本身,这样对列表的修改只是在副本上。不会影响列表本身。也就是传递列表的切片
function(last_name(:))

传递任意数量的实参
一个制作披萨的函数,它需要接受很多配料,但你无法预先确定顾客要多少种配料。
def make_pizza(*toppings):
for topping in toppings:
print("- " + topping)

make_pizza('pepperoni')
make_pizza('mushrooms','green peppers','extra cheese')
形参名*toppings中的星号让Python创建一个名为toppings的空元组,并将收到的所有值都封装到这个元组中。

结合使用位置实参和任意数量实参
如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。Python先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中。
def make_pizza(size, *toppings):
print("\nMaking a "+ str(size) + "-inch pizza with following toppings:")
for topping in toppings:
print("- " + topping)
make_pizza(16,'pepperoni')
基于函数定义,Python将第一个值存储在形参中,并将其他所有值存储在元组toppings中。

使用任意数量的关键字实参
def build_profile(first, last, **user_info):
profile={}
profile['first_name'] = first
profile['last_name'] = last
for key,value in user_info:
profile[key] = value
return profile

build_profile('albert', 'einstein', location='princeton', field='physics')
函数的定义要求提供名和姓,同时允许用户根据需要提供任意数量的名称-键值对,形参*user_inmfo中的两个号让Python创建一个名为user_info的空字典。并将收到的所有键值对都封装到这个字典中。可以像访问其他字典那样访问user_info键值对

将函数存储在模块中,可以通过import来使用模块中的函数
导入整个模块

pizza.py
def make_pizza(size, *toppings):
print("\nMaking a "+ str(size) + "-inch pizza with following toppings:")
for topping in toppings:
print("- " + topping)

我们在pizza.py目录创建另一个making_pizzas.py文件,导入pizzs模块,再调用make_pizza()两次
making_pizzas.py
import pizza

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

Python读取这个文件时,代码行import pizza让Python打开文件pizza.py,并将其中的所有的函数都复制到这个程序中。调用方法就是模块名,后面加.,最后再加函数名
module_name.fuction_name()

导入特定函数
from module_name import fuction_name0, fuction_name1
通过这种方式导入的函数,可以直接用fuction_name0()进行调用

使用as给指定函数指定别名
如果要导入的函数的名称可能与程序中的现有名称冲突,或者函数名称太长,可指定简短且独一无二的别名。
from module_name import function_name as fn

from pizza import make_pizza as mp

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

使用as给模块指定别名
import module_name as mn

import pizza as p

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

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

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

import语句中的*让Python将模块pizza中的每个函数都复制到这个程序文件中。由于导入了每个函数,可通过名称来调用每个函数,而无需使用句点表示法。然而,使用并非自己编写的大型模块时,最好不要采用这种导入方法:如果模块中有函数的名称与你的项目中使用的名称相同,可能导致意想不到的结果:Python可能遇到多个名称相同的函数活变量,进而覆盖函数,而不是分别导入所有的函数。
最佳的做法是,要么只导入你需要用的函数,要么导入整个模块并使用句点法

函数编写指南
1、函数名称只用小写字母和下划线
2、函数都应包含简要阐述期功能的注释
3、参数的意义的注释

第九章 类

创建dog类

class Dog():
    """一次模拟小狗的简单尝试"""
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def sit(self):
        print(self.name.title() + " is now sitting.")
        
    def roll_over(self):
        print(self.name.title() + " rolled over.")

方法 init()
类中的函数称为方法,init()是一个特殊的方法,每当你根据Dog类创建新实例时,Python就会自动运行它。在这个方法的名称中,开头和结尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生冲突。

init()包含三个形参,self、name和age。在这个方法的定义中,形参self必不可少,还必须位于其他形参的前面。因为Python调用init()方法创建Dog实例时,将自动传入实参self。每个与类相关联的方法调用都自动传递实参self,它是指向实例本身的引用,让实例能够访问类中的属性和方法。我们创建Dog实例时,Python将调用Dog类的方法,init(),我们将通过实参向Dog()传递名字和年龄,self会自动传递。

init方法里面两个变量都有前缀self,以self为前缀的变量都可供类中的所有方法使用,我们还可以通过类的任何实例访问这些变量。self.name=name获取存储在形参name中的值,并将其存储到变量name中,然后将该变量关联到当前创建的实例。像这样可通过实例访问的变量称为属性。

Dog类还定义了另外两个方法:sit()和roll_over()。由于这些方法不需要额外的参数,因此他们只有一个形参self。

根据类创建实例
my_dog = Dog('willie', 6)###调用init()创建特定小狗实例

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

访问属性
要访问实例的属性,可使用句点法表示。my_dog.name

调用方法
my_dog.sit()

使用类和实例

class Car():
    """一次模拟汽车的简单尝试"""
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year
        
    def get_descriptive_name(self):
        long_name = str(self.year) + ' ' + self.make + ' ' + self.model
        return long_name.title()

my_new_car = Car("audi", "a4", 2016)

给属性指定默认值

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 = str(self.year) + ' ' + self.make + ' ' + self.model
        return long_name.title()
        
    def read_odometer(self):
        print("This car has " + str(self.odometer_reading) + " miles on it.")

修改属性的值
my_new_car.odometer = 23
my_new_car.read_odometer()

通过方法修改属性的值

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 = str(self.year) + ' ' + self.make + ' ' + self.model
        return long_name.title()
        
    def read_odometer(self):
        print("This car has " + str(self.odometer_reading) + " miles on it.")
    def update_odometer(self, mileage):
        self.odomter = mileage

my_new_car.update_odometer(23)

通过方法对属性的值进行递增
def increment_odometer(self, miles):
self.odometer += miles

my_new_car.increment_odometer(50)

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 = str(self.year) + ' ' + self.make + ' ' + self.model
        return long_name.title()
        
    def read_odometer(self):
        print("This car has " + str(self.odometer_reading) + " miles on it.")
    def update_odometer(self, mileage):
        self.odomter = mileage
        
    def increment_odometer(self, miles):
        self.odometer += miles
        
class  ElectricCar(Car):
    """电动汽车的独特之处"""
    
    def __init__(self, make, model, year):
        super().__init__(make, model, year)

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

创建子类时,父类必须包含在当前文件中,且位于子类之前。
super()函数是一个特殊函数,帮助Python将父类和子类关联起来。

给子类定义属性和方法

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 = str(self.year) + ' ' + self.make + ' ' + self.model
        return long_name.title()
        
    def read_odometer(self):
        print("This car has " + str(self.odometer_reading) + " miles on it.")
    def update_odometer(self, mileage):
        self.odomter = mileage
        
    def increment_odometer(self, miles):
        self.odometer += miles
        
class  ElectricCar(Car):
    """电动汽车的独特之处"""
    
    def __init__(self, make, model, year):
        super().__init__(make, model, year)
        self.battery_size = 70
        
    def describe_battery(self):
        print("This car has a " + str(self.battery_size) + "-kwh battery.")

my_tesla.describe_battery()

重写父类方法
假设Car类有一个名为fill_gas_tank()的方法,它对电动汽车毫无意义,因此可以重写他

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

将实例用作属性
可能需要将类的一部分作为一个独立的类提取出来。可以将大型类拆分成多个协同工作的小类。

class  Car():
    --snip--
  
class  Battery():
    def __init__(self,battery_size=70):
        self.battery_size = battery_size
        
    def describe_battery(self):
        print("This car has a " + str(self.battery_size) + "-kwh battery.")
        
class  ElectricCar(Car):
    def __init__(self,make, model, year):
        super().__init__(make, model, year)
        self.battery = Battery()

my_tesla = ElectricCar('tesla', 'model s', 2016)

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

导入类
由于类不断的添加功能,文件变的很长,为了让文件尽可能整洁,Python允许你将类存储在模块中,然后在主程序中导入所需模块

导入单个类
car.py

"""一个可用于表示汽车的类"""

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 = str(self.year) + ' ' + self.make + ' ' + self.model
        return long_name.title()
        
    def read_odometer(self):
        print("This car has " + str(self.odometer_reading) + " miles on it.")
    def update_odometer(self, mileage):
        self.odomter = mileage
        
    def increment_odometer(self, miles):
        self.odometer += miles

my_car.py

from car import Car##从文件中导入类,可用利用逗号,一次引入多个类

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

my_new_car.odometer  = 23
my_new_car.read_odometer()

导入整个模块
可用import car导入整个模块。接下来,我们使用module_name.class_name访问需要的类

导入模块所有类
from car import *
不推荐这种导入方式,其原因有二:
1、如果只要看一下文件开头的import语句,就能清楚知道程序使用了哪些类,将大有裨益。但这种方式并没有明确你使用了模块中哪些类
2、这种导入方式还可能引发名称方面的困惑。如果你不小心导入了一个与程序文件中其他同名的类,可能引发错误。

在一个模块中引入另一个模块
这个用法和普通的模块引入没有任何区别,比如把Car类和ElectricCar分成两个模块文件,任何在ElectricCar里面from car import Car
后面如果需要导入Car和ElectricCar类的时候,不能以为ElectricCar类里面导入了Car就不需要再导入了。所以,Car类和ElectricCar类都需要单独导入。

类编码风格
1、类名应采取驼峰命名法,将类名中的每个首字母大写,而不使用下划线。实例名和模块名都是小写格式,并在单词之间加上下划线。
2、对于每个类,都应紧跟在类定义后面包含一个文档字符串。描述累的功能。每个模块也都要包含一个文档字符串,对其中的类的行为进行描述
3、在类中,可使用一个空行来分隔方法;而在模块中,可使用两个空行来分隔类。
4、需要同时导入标准库中的模块和你自己编写的模块时,先导入标准库模块的import语句,再添加一个空行,然后再编写导入你自己编写的模块的import语句。这种做法让人更容易明白程序使用的各个模块来自何方。

总结:
除了类名是驼峰命名法,首字母大写,其他比如实例、模块名、函数名、方法名、变量名都是小写加下划线,导入变量名还可以加数字。

第十章 文件和异常

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

这里有调用open(),但是没有调用close()。用with打开文件,Python会自动在合适的时候调用close()关闭文件。
这个输出会多一行,原因在于read()到达文件末尾时返回一个空字符串,而将这个空字符串显示出来就是一个空行。要删除空行,可以在print里面使用rstrip()
print(contents.rstrip())

上面的open的文件没有带路径,Python会自动搜索程序文本所在的当前文件夹。

文件路径
相对路径
linux和os x系统
with open('text_files/filename.txt') as file_object

windows系统
with open('text_files\filename.txt') as file_object

绝对路径
linux和os x系统
file_path = '/home/ehmatthes/text_files/filename.txt'
with open(file_path) as file_object

windows系统
file_path = 'C:\Users\ethmatthes\other_files\text_files\filename.txt'
with open(file_path) as file_object

逐行读取
filename = 'pi_digits.txt'

with open(filename) as file_object:
for line in file_object:
print(line.rstrip())

调用open后,将一个表示文件及其内容的对象存储到变量file_object中。

创建一个包含文件各行内容的列表
filename = 'pi_digits.txt'

with open(filename) as file_object:
for line in file_object:
lines = file_object.readlines()

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

lines列表中的每个元素就是file_object的每一行内容。

filename = 'pi_millon_digits.txt'

with open(filename) as file_object:
lines = file_object.readlines()

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

print(pi_string[:52] + "...")

圆周率是否包含你的生日

filename = 'pi_millon_digits.txt'

with open(filename) as file_object:
lines = file_object.readlines()

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

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

写入文件
filename = 'programming.txt'

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

调用open时,提供了两个参数,第一个参数是要打开的文件的名称,第二个参数'w'告诉Python,我们要以写入的方式打开文件。
打开文件有几种模式
w-写入模式
r-只读模式
a-附加模式
r+-读取和写入模式
如果省略模式实参,那Python默认以只读打开

如果要写入的文件不存在,函数open()自动创建它。以'w'模式打开文件要小心,如果文件以及存在,Python将返回对象前清空该文件。

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

写入多行
file_object.write("I love programming.\n")
file_object.write("I love creating new game.\n")
如果不加换行符,则会连成一行

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

异常
filename = 'alice.txt'

try:
with open(filename) as f_obj:
contents = f_obj.read()
except FileNotFoundError:
msg = "Sorry, the file " + filename + " does not exist."
print(msg)
else:
words = contents.split()
num_words = len(words)
print("The file " + filename + " has about " + str(num_words) + " words.")

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

import json

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

第十一章 测试代码

name_function.py
def get_formatted_name(first_name, last_name):
full_name = first_name + ' ' + last_name
return full_name.title()

names.py

from name_function import get_formatted

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

单元测试
test_name_function.py

import unittest
from name_function import get_formatted_name

class NameTestCase(unittest.TestCase):
    """测试name_function.py"""
    
    def test_first_last_name(self):
        formatted_name = get_formatted_name('janis', 'joplin')
        self.assertEqual(formatted_name, 'Janis Joplin')
        
unittest.main()

首先我们创建了一个名为NameTestCase的类,用于包含一系列针对get_formatted_name()的单元测试。你可以随便给这个类命名,但最好让它看起来与测试的函数相关,并包含Test字样。这个类必须继承unittest.TestCase类,这样Python才知道如何运行你编写的测试。
NameTestCase只包含一个方法,用于测试get_formatted_name()的一个方面。我们将这个方法命名为test_first_last_name()。
我们运行test_name_function.py时,所有以test_打头的方法都将自动运行。

方法里,我们使用了unittest类最常用的功能之一:一个断言方法。断言方法用来合适得到的结果是否与期望的结果一致。
unittest断言方法

方法 用途
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中

测试类
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("Survey results:")
        for response in self.responses:
            print('- ' + response)

测试AnonymousSurvey类
test_survey.py

import unittest
from survey import AnonymousSurvey


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

    def setUp(self):
        question = "What language did you first leanr to speak?"
        self.my_survey = AnonymousSurvey(question)
        self.responses = ['English', 'Spanish', 'Mandarin']

    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)

unittest.main()

方法setUp()
在前面的测试代码中,我们在测试方法中都单独创建了AnonymousSurvey的实例。
有没有办法进行优化?
可以使用setUp(),让我们只需创建这些对象一次,并在每个测试方法中运用它们。
如果你在TestCase类中包含方法setUp(),Python将先运行它,再运行各个以test_打头的方法。

import unittest
from survery import AnonymousSurvey

class TestAnonymousSurvey(unittest.TestCase):
    """针对AnonymousSurvey类的测试"""
    
    def setUp(self):
        question = "What language did you first leanr to speak?"
        self.survey = AnonymousSurvey(question)
        self.responses = ['English', 'Spanish', 'Mandarin']
    
    def test_store_single_response(self):
        my_survey.store_response(self.responses[0]) 
        self.assertIn(self.responses[0], my_survey.response)
        
    def test_store_three_response(self):
        for response in responses:
            my_survey.store_response(response)
            
        for response in responses:
            self.assertIn(response, my_survey.responses)
unittest.main()

你可能感兴趣的:(Python基础)