Chatper 9 类

9.1 创建和使用类

9.1.1 创建Dog类

❶class Dog():
❷    """一次模拟小狗的简单尝试"""❸    def _init_(self, name, age):"""初始化属性name和age"""
❹       self.name=nameself.age=age❺    def sit(self):"""模拟小狗被命令时蹲下"""print(self.name.title() + " is now sitting.")def roll_over(self):"""模拟小狗被命令时打滚"""print(self.name.title() + " rolled over!")

在❶处,我们定义了一个名为Dog 的类。根据约定,在Python中,首字母大写的
名称指的是类。这个类定义中的括号是空的,因为我们要从空白创建这个类。在❷处,我们编写了一个文档字符串,对这个类的功能作了描述。

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

9.1.2 根据类创建实例

class Dog():"""一次模拟小狗的简单尝试"""def __init__(self, name, age):"""初始化属性name和age"""self.name=nameself.age=agedef sit(self):"""模拟小狗被命令时蹲下"""print(self.name.title() + " is now sitting.")def roll_over(self):"""模拟小狗被命令时打滚"""print(self.name.title() + " rolled over!")my_dog = Dog('willie',6)
print("My dog's name is "+my_dog.name.title()+ ".")
print("My dog is "+str(my_dog.age)+" years old.")
my_dog.roll_over()

My dog’s name is Willie.
My dog is 6 years old.
Willie rolled over!
要访问实例的属性,可使用句点表示法。使用句点还可调用Dog类中定义的任何方法。
练习题9-1~9-3

class Restaurant():def __init__(self,name,cuisine_type):self.name=nameself.cuisine_type=cuisine_typedef describe_restaurant(self):print("name of the restaurant: " + self.name)print('cuisine type: ' + self.cuisine_type)def open_restaurant(self):print("The restaurant is running.")test=Restaurant('zhang','big')
print(test.name)
print(test.cuisine_type)
test.describe_restaurant()
test.open_restaurant()
class User():def __init__(self,first_name,last_name,age):self.first_name=first_nameself.last_name=last_nameself.age=agedef describe_user(self):print("first name:" + self.first_name)print("last name:" + self.last_name)print("age: " + str(self.age))def greet_user(self):print("Hello " + self.first_name + " " + self.last_name + "!")test=User("wang","gabage",'11')
print(test.first_name + " " + test.last_name)
test.describe_user()
test.greet_user()

9.2.2 给属性指定默认值

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

class Car():def __init__(self,make,model,year):self.make=makeself.model=modelself.year=yearself.odometer_reading=0def get_descriptive_name(self):long_name=str(self.year) + " " + self.make + " " +self.modelreturn long_name.title()def read_odometer(self):print("this car has " + str(self.odometer_reading) + " miles on it.")my_new_car=Car('audi','a4',2016)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()

2016 Audi A4
this car has 0 miles on it.

9.2.3 修改属性的值

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

class Car():def __init__(self,make,model,year):self.make=makeself.model=modelself.year=yearself.odometer_reading=0def get_descriptive_name(self):long_name=str(self.year) + " " + self.make + " " +self.modelreturn long_name.title()def read_odometer(self):print("this car has " + str(self.odometer_reading) + " miles on it.")my_new_car=Car('audi','a4',2016)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading = 23
my_new_car.read_odometer()

2016 Audi A4
this car has 23 miles on it.
2.通过方法修改属性的值

class Car():def __init__(self,make,model,year):self.make=makeself.model=modelself.year=yearself.odometer_reading=0def get_descriptive_name(self):long_name=str(self.year) + " " + self.make + " " +self.modelreturn long_name.title()def read_odometer(self):print("this car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self, mileage):"""将里程表读数设置为指定的值,禁止将里程表读数回调"""if mileage >= self.odometer_reading:self.odometer_reading=mileageelse:print("You can't roll back an odometer!")my_new_car=Car('audi','a4',2016)
print(my_new_car.get_descriptive_name())
my_new_car.update_odometer(23)
my_new_car.read_odometer()

2016 Audi A4
this car has 23 miles on it.
3.通过方法对属性的值进行递增

class Car():def __init__(self,make,model,year):self.make=makeself.model=modelself.year=yearself.odometer_reading=0def get_descriptive_name(self):long_name=str(self.year) + " " + self.make + " " +self.modelreturn long_name.title()def read_odometer(self):print("this car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self, mileage):"""将里程表读数设置为指定的值,禁止将里程表读数回调"""if mileage >= self.odometer_reading:self.odometer_reading=mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):self.odometer_reading += milesmy_used_car=Car('subaru','outback',2013)
print(my_used_car.get_descriptive_name())my_used_car.update_odometer(23500)
my_used_car.read_odometer()my_used_car.increment_odometer(100)
my_used_car.read_odometer()

2013 Subaru Outback
this car has 23500 miles on it.
this car has 23600 miles on it.
练习题9-4~9-5

class Restaurant():def __init__(self,name,cuisine_type):self.name=nameself.cuisine_type=cuisine_typeself.number_served=0def describe_restaurant(self):print("name of the restaurant: " + self.name)print('cuisine type: ' + self.cuisine_type)def open_restaurant(self):print("The restaurant is running.")def people(self):print("number serverd: " + str(self.number_served))def increase_people(self,people):self.number_served += people
test=Restaurant('zhang','big')
print(test.name)
print(test.cuisine_type)
test.describe_restaurant()
test.open_restaurant()test.people()test.increase_people(10)
test.people()
class User():def __init__(self,first_name,last_name,age):self.first_name=first_nameself.last_name=last_nameself.age=ageself.login_attempts=0def describe_user(self):print("first name:" + self.first_name)print("last name:" + self.last_name)print("age: " + str(self.age))def greet_user(self):print("Hello " + self.first_name + " " + self.last_name + "!")def login_increment_login_attempts(self):self.login_attempts += 1def reset_login_attemts(self):self.login_attempts = 0print("The login attempts has been reset")test=User("wang","gabage",'11')
print(test.first_name + " " + test.last_name)
print(test.login_attempts)
test.login_increment_login_attempts()
test.login_increment_login_attempts()
print(test.login_attempts)
test.reset_login_attemts()
print(test.login_attempts)

9.3 继承

编写类时,并非总是要从空白开始。如果你要编写的类时另一个现成类的特殊版本,可使用继承。一个类继承另一个类时,它将自动获得另一个类的所有属性和方法;原有的类成为父类,新类称为子类。子类继承了其父类的所有属性和方法,同时还可以 定义自己的属性和方法。
创建子类的实例时,Python首先需要完成的任务是给父类的所有属性赋值。为此,子类的方法__init__() 需要父类施以援手。例如,下面来模拟电动汽车。电动汽车是一种特殊的汽车,因此我们可以在前面创建的Car 类的基础上创建新类ElectricCar ,这样我们就只需为电动汽车特有的属性和行为编写代码。

❶class Car():def __init__(self,make,model,year):self.make=makeself.model=modelself.year=yearself.odometer_reading=0def get_descriptive_name(self):long_name=str(self.year) + " " + self.make + " " +self.modelreturn long_name.title()def read_odometer(self):print("this car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self, mileage):"""将里程表读数设置为指定的值,禁止将里程表读数回调"""if mileage >= self.odometer_reading:self.odometer_reading=mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):self.odometer_reading += miles❷class ElectricCar(Car):❸    def __init__(self,make,model,year):"""初始化父类的属性"""
❹        super().__init__(make,model,year)❺my_tesla = ElectricCar("tesla","model s", 2016)
print(my_tesla.get_descriptive_name())

2016 Tesla Model S
首先是Car 类的代码(见❶)。创建子类时,父类必须包含在当前文件中,且位于子类前面。在❷处,我们定义了子类ElectricCar 。定义子类时,必须在括号内指定父类的
名称。方法__init__() 接受创建Car 实例所需的信息(见❸)。❹处的super() 是一个特殊函数,帮助Python将父类和子类关联起来。这行代码让Python调用ElectricCar 的父类的方法__init__() ,让ElectricCar 实例包含父类的所有属性。父类也称为超类 (superclass),名称super因此而得名。
为测试继承是否能够正确地发挥作用,我们尝试创建一辆电动汽车,但提供的信息与创建普通汽车时相同。在❺处,我们创建ElectricCar 类的一个实例,并将其存储在变量my_tesla 中。这行代码调用ElectricCar 类中定义的方法__init__() ,后者让Python调用父类Car 中定义的方法__init__() 。我们提供了实参’tesla’ 、‘models’ 和2016 。
除方法__init__() 外,电动汽车没有其他特有的属性和方法。

9.3.3 给子类定义属性和方法

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

class Car():def __init__(self,make,model,year):self.make=makeself.model=modelself.year=yearself.odometer_reading=0def get_descriptive_name(self):long_name=str(self.year) + " " + self.make + " " +self.modelreturn long_name.title()def read_odometer(self):print("this car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self, mileage):"""将里程表读数设置为指定的值,禁止将里程表读数回调"""if mileage >= self.odometer_reading:self.odometer_reading=mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):self.odometer_reading += milesclass ElectricCar(Car):def __init__(self,make,model,year):"""初始化父类的属性,在初始化电动汽车特有属性"""super().__init__(make,model,year)self.batter_size=70def describe_batter(self):print("This car has a " + str(self.batter_size) + "-kWh battery.")my_tesla = ElectricCar("tesla","model s", 2016)
print(my_tesla.get_descriptive_name())
my_tesla.describe_batter()

2016 Tesla Model S
This car has a 70-kWh battery.

9.3.4 重写父类的方法

对于父类的方法,只要它不符合子类模拟的实物的行为,都可对其进行重写。为此,可在子类中定义一个这样的方法,即它与要重写的父类方法同名。这样,Python将不会考虑这个父类方法,而只关注你在子类中定义的相应方法。
假设Car 类有一个名为fill_gas_tank() 的方法,它对全电动汽车来说毫无意义,因此你可能想重写它。下面演示了一种重写方式:

def ElectricCar(Car):--snip--def fill_gas_tank():"""电动汽车没有邮箱"""print("This car doesn't need a gas tank!")

下奶如果有人对电动汽车调用方法fill_gas_tank() ,Python将忽略Car 类中的方法fill_gas_tank() ,转而运行上述代码。使用继承时,可让子类保留从父类那里继
承而来的精华,并剔除不需要的糟粕。

9.3.5 将实例用作属性

使用代码模拟实物时,你可能会发现自己给类添加的细节越来越多:属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分作为一个独立的类提取出来。你可以将大型类拆分成多个协同工作的小类。
例如,不断给ElectricCar 类添加细节时,我们可能会发现其中包含很多专门针对汽车电瓶的属性和方法。在这种情况下,我们可将这些属性和方法提取出来,放到另一个名为Battery 的类中,并将一个Battery 实例用作ElectricCar 类的一个属性:

class Car():def __init__(self,make,model,year):self.make=makeself.model=modelself.year=yearself.odometer_reading=0def get_descriptive_name(self):long_name=str(self.year) + " " + self.make + " " +self.modelreturn long_name.title()def read_odometer(self):print("this car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self, mileage):"""将里程表读数设置为指定的值,禁止将里程表读数回调"""if mileage >= self.odometer_reading:self.odometer_reading=mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):self.odometer_reading += miles❶class Battery():"""一次模拟电动汽车电瓶的简单尝试"""❷    def __init__(self,battery_size=70):"""初始化电瓶属性"""self.battery_size=battery_size❸    def describe_battery(self):print("This car has a " + str(self.battery_size) + "-kWh battery.")class ElectricCar(Car):def __init__(self,make,model,year):"""初始化父类的属性,在初始化电动汽车特有属性"""super().__init__(make,model,year)
❹        self.battery=Battery()my_tesla = ElectricCar("tesla","model s", 2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()

2016 Tesla Model S
This car has a 70-kWh battery.
在❶处,我们定义了一个名为Battery 的新类,它没有继承任何类。❷处的方法__init__() 除self 外,还有另一个形参battery_size 。这个形参是可选的:如果没有给它提供值,电瓶容量将被设置为70。方法describe_battery() 也移到了这个类中(见❸)。
在ElectricCar 类中,我们添加了一个名为self.battery 的属性(见❹)。这行代码让Python创建一个新的Battery 实例(由于没有指定尺寸,因此为默认值70 ),并将该实例存储在属性self.battery 中。每当方法__init__() 被调用时,都将执行该操作;因此现在每个ElectricCar 实例都包含一个自动创建的Battery 实例。

class Car():def __init__(self,make,model,year):self.make=makeself.model=modelself.year=yearself.odometer_reading=0def get_descriptive_name(self):long_name=str(self.year) + " " + self.make + " " +self.modelreturn long_name.title()def read_odometer(self):print("this car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self, mileage):"""将里程表读数设置为指定的值,禁止将里程表读数回调"""if mileage >= self.odometer_reading:self.odometer_reading=mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):self.odometer_reading += milesclass Battery():"""一次模拟电动汽车电瓶的简单尝试"""def __init__(self,battery_size=70):"""初始化电瓶属性"""self.battery_size=battery_sizedef describe_battery(self):print("This car has a " + str(self.battery_size) + "-kWh battery.")def get_range(self):"""打印一条消息,指出电瓶的续航里程"""if self.battery_size == 70:range=240elif self.battery_size == 85:range=270message="This car can go approximately" + str(range)message += " miles on a full charge."print(message)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()
my_tesla.battery.get_range()

2016 Tesla Model S
This car has a 70-kWh battery.
This car can go approximately240 miles on a full charge.
练习题9-6~9-9

class Restaurant():def __init__(self,name,cuisine_type):self.name=nameself.cuisine_type=cuisine_typeself.number_served=0def describe_restaurant(self):print("name of the restaurant: " + self.name)print('cuisine type: ' + self.cuisine_type)def open_restaurant(self):print("The restaurant is running.")def people(self):print("number serverd: " + str(self.number_served))def increase_people(self,people):self.number_served += peopleclass IceCreamStand(Restaurant):def __init__(self,name,cuisine_type):super().__init__(name,cuisine_type)self.flavors=['spicy','salty','chocolate']def show_flavors(self):print("There are " + str(len(self.flavors)) + " kinds of flavors.")for flavor in self.flavors:print(flavor)test=IceCreamStand('zhang','big')
test.show_flavors()
class User():def __init__(self,first_name,last_name,age):self.first_name=first_nameself.last_name=last_nameself.age=ageself.login_attempts=0def describe_user(self):print("first name:" + self.first_name)print("last name:" + self.last_name)print("age: " + str(self.age))def greet_user(self):print("Hello " + self.first_name + " " + self.last_name + "!")def login_increment_login_attempts(self):self.login_attempts += 1def reset_login_attemts(self):self.login_attempts = 0print("The login attempts has been reset")class Admin(User):def __init__(self,first_name,last_name,age):super().__init__(first_name,last_name,age)self.privileges=["can add post","can delete post","can ban user"]def show_privileges(self):print("your privileges:")for privilege in self.privileges:print(privilege)test=Admin('wang','ba',16)
test.show_privileges()
class User():def __init__(self,first_name,last_name,age):self.first_name=first_nameself.last_name=last_nameself.age=ageself.login_attempts=0def describe_user(self):print("first name:" + self.first_name)print("last name:" + self.last_name)print("age: " + str(self.age))def greet_user(self):print("Hello " + self.first_name + " " + self.last_name + "!")def login_increment_login_attempts(self):self.login_attempts += 1def reset_login_attemts(self):self.login_attempts = 0print("The login attempts has been reset")class Priverleges():def __init__(self):self.privileges=["can add post","can delete post","can ban user"]def show_privileges(self):print("your privileges:")for privilege in self.privileges:print(privilege)class Admin(User):def __init__(self,first_name,last_name,age):super().__init__(first_name,last_name,age)self.privileges=Priverleges()test=Admin('wang','ba',16)
test.privileges.show_privileges()
class Car():def __init__(self,make,model,year):self.make=makeself.model=modelself.year=yearself.odometer_reading=0def get_descriptive_name(self):long_name=str(self.year) + " " + self.make + " " +self.modelreturn long_name.title()def read_odometer(self):print("this car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self, mileage):"""将里程表读数设置为指定的值,禁止将里程表读数回调"""if mileage >= self.odometer_reading:self.odometer_reading=mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):self.odometer_reading += milesclass Battery():"""一次模拟电动汽车电瓶的简单尝试"""def __init__(self,battery_size=70):"""初始化电瓶属性"""self.battery_size=battery_sizedef describe_battery(self):print("This car has a " + str(self.battery_size) + "-kWh battery.")def get_range(self):"""打印一条消息,指出电瓶的续航里程"""if self.battery_size == 70:range=240elif self.battery_size == 85:range=270message="This car can go approximately" + str(range)message += " miles on a full charge."print(message)def upgrade_battery(self):if self.battery_size != 85:self.battery_size = 85class ElectricCar(Car):def __init__(self,make,model,year):"""初始化父类的属性,在初始化电动汽车特有属性"""super().__init__(make,model,year)self.battery=Battery()my_tesla = ElectricCar("tesla","model s", 2016)
my_tesla.battery.get_range()
my_tesla.battery.upgrade_battery()
my_tesla.battery.get_range()

9.4 导入类

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

9.4.1 导入单个类

下面来创建一个只包含Car 类的模块。这让我们面临一个微妙的命名问题:在本章中,已经有一个名为car.py的文件,但这个模块也应命名为car.py,因为它包含表示汽车的代码。我们将这样解决这个命名问题:将Car 类存储在一个名为car.py的模块中,该模块将覆盖前面使用的文件car.py。从现在开始,使用该模块的程序都必须使用更具体的文件
名,如my_car.py。
car.py

"""一个可用于表示汽车的类"""
class Car():def __init__(self,make,model,year):self.make=makeself.model=modelself.year=yearself.odometer_reading=0def get_descriptive_name(self):long_name=str(self.year) + " " + self.make + " " +self.modelreturn long_name.title()def read_odometer(self):print("this car has " + str(self.odometer_reading) + " miles on it.")def update_odometer(self, mileage):"""将里程表读数设置为指定的值,禁止将里程表读数回调"""if mileage >= self.odometer_reading:self.odometer_reading=mileageelse:print("You can't roll back an odometer!")def increment_odometer(self,miles):self.odometer_reading += miles

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

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

2016 Audi A4
This car has 23 miles on it.
在一个模块中还可存储多个类,并从一个模块中导入多个类,类如from car import Car, ElectricCar。 从一个模块中导入多个类时,用逗号分隔了各个类。导入必要的类后,就可根据需要创建每个类的任意数量的实例。你还可以导入整个模块,再使用句点表示法访问需要的类。比如

import carmy_beetle = car.Car('volkswagen', 'beetle', 2016)
print(my_beetle.get_descriptive_name())
my_tesla = car.ElectricCar('tesla', 'roadster', 2016)
print(my_tesla.get_descriptive_name())

导入 整个car模块,接下类使用语法module_name.class_name访问需要的类。
导入模块中的所有类可使用from module_name import *,但不推荐使用这种导入方式,其原因有二。首先,如果只要看一下文件开头的import 语句,就能清楚地知道程序使用了哪些类,将大有裨益;但这种导入方式没有明确地指出你使用了模块中的哪些类。这种导入方式还可能引发名称方面的困惑。如果你不小心导入了一个与程序文件中其他东西同名的类,将引发难以诊断的错误。
需要从一个模块中导入很多类时,最好导入整个模块,并使用 module_name.class_name 语法来访问类

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

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

"""一组可用于表示电动汽车的类"""
from car import Car
class Battery():
--snip--
class ElectricCar(Car):
--snip--

car.py

"""一个可用于表示汽车的类"""
class Car():
--snip--

my_cars.py

from car import Car
from electric_car import ElectricCarmy_beetle = Car('volkswagen', 'beetle', 2016)
print(my_beetle.get_descriptive_name())
my_tesla = ElectricCar('tesla', 'roadster', 2016)
print(my_tesla.get_descriptive_name())

2016 Volkswagen Beetle
2016 Tesla Roadster

9.5 Python标准库

字典让你能够将信息关联起来,但它们不记录你添加键—值对的顺序。要创建字典并记录其中的键—值对的添加顺序,可使用模块collections 中的OrderedDict类。OrderedDict 实例的行为几乎与字典相同,区别只在于记录了键—值对的添加顺序。

from collections import OrderedDictfavorite_languages = OrderedDict()favorite_languages['jen']='python'
favorite_languages['sarah']='c'
favorite_languages['edward']='ruby'
favorite_languages['phil']='python'for name,language in favorite_languages.items():print(name.title() + "'s favorite language is " + language.title()+ ".")

Jen’s favorite language is Python.
Sarah’s favorite language is C.
Edward’s favorite language is Ruby.
Phil’s favorite language is Python.
这是一个很不错的类,它兼具列表和字典的主要优点(在将信息关联起来的同时保留原来的顺序)。
练习题9-13~9-15

from random import randintclass Die():def __init__(self,sides=6):self.sides=sidesdef roll_die(self):x=randint(1,self.sides)print(x)a=Die(6)
for number in range(1,11):a.roll_die()
print("\n")
b=Die(10)
for number in range(1,11):b.roll_die()

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

Chatper 10 文件和异常

pi_digits.txt
3.1415926535
8979323846
2643383279

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

3.1415926535
8979323846
2643383279

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

10.1.2 文件路径

要让Python打开不与程序文件位于同一个目录中的文件,需要提供文件路径 ,它让Python到系统的特定位置去查找。
根据你组织文件的方式,有时可能要打开不在程序文件所属目录中的文件。例如,你可能将程序文件存储在了文件夹python_work中,而在文件夹python_work中,有一个名为text_files的文件夹,用于存储程序文件操作的文本文件。虽然文件夹text_files包含在文件夹python_work中,但仅向open() 传递位于该文件夹中的文件的名称也不可行,因为Python只在文件夹python_work中查找,而不会在其子文件夹text_files中查找。要让Python打开不与程序文件位于同一个目录中的文件,需要提供文件路径 ,它让Python到系统的特定位置去查找。
由于文件夹text_files位于文件夹python_work中,因此可使用相对文件路径来打开该文件夹中的文件。相对文件路径让Python到指定的位置去查找,而该位置是相对于当前运行的程序所在目录的。
在Linux和OS X中,你可以这样编写代码:with open('text_files/filename.txt') as file_object:
在Windows系统中,在文件路径中使用反斜杠(\ )而不是斜杠(/ ):with open('text_files\filename.txt') as file_object:
你还可以将文件在计算机中的准确位置告诉Python,这样就不用关心当前运行的程序存储在什么地方了。这称为绝对文件路径
绝对路径通常比相对路径更长,因此将其存储在一个变量中,再将该变量传递给open() 会有所帮助。在Linux和OS X中,绝对路径类似于下面这样:

file_path = '/home/ehmatthes/other_files/text_files/filename.txt'
with open(file_path) as file_object:

而在Windows系统中,它们类似于下面这样:

file_path = 'C:\Users\ehmatthes\other_files\text_files\filename.txt'
with open(file_path) as file_object:

10.1.13 逐行读取

读取文件时,常常需要检查其中的每一行:你可能要在文件中查找特定的信息,或者要以某种方式修改文件中的文本。例如,你可能要遍历一个包含天气数据的文件,并使用天气描述中包含字样sunny的行。在新闻报道中,你可能会查找包含标签 的行,并按特定的格式设置它。
要以每次一行的方式检查文件,可对文件对象使用for 循环:

filename='pi_digits.txt'
with open(filename) as file_object:for line in file_object:print(line)

3.1415926535

8979323846

2643383279

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

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

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

filename='pi_digits.txt'
with open(filename) as file_object:lines=file_object.readlines()for line in lines:print(line.rstrip())

3.1415926535
8979323846
2643383279
方法readlines()从文件中读取每一行,并将其存储在一个列表中。

10.1.5 使用文件的内容

filename='pi_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)

3.141592653589793238462643383279
练习题10-1~10-2

filename='note.txt'
with open(filename) as file_object:content=file_object.read()for number in range(1,4):print(content)

In python you can sing
In python you can dance
In python you can be a hero
In python you can sing
In python you can dance
In python you can be a hero
In python you can sing
In python you can dance
In python you can be a hero

filename='note.txt'
with open(filename) as file_object:for line in file_object:for number in range(1,4):print(line.strip())

In python you can sing
In python you can sing
In python you can sing
In python you can dance
In python you can dance
In python you can dance
In python you can be a hero
In python you can be a hero
In python you can be a hero

filename='note.txt'
with open(filename) as file_object:lines=file_object.readlines()for number in range(1,4):for line in lines:print(line.strip())

In python you can sing
In python you can dance
In python you can be a hero
In python you can sing
In python you can dance
In python you can be a hero
In python you can sing
In python you can dance
In python you can be a hero
可使用方法replace() 将字符串中的特定单词都替换为另一个单词。下面是一个简单的示例,演示了如何将句子中的’dog’ 替换为’cat’

>>> message = "I really like dogs."
>>> message.replace('dog', 'cat')
'I really like cats.'
filename='note.txt'
with open(filename) as file_object:lines=file_object.readlines()for number in range(1,4):for line in lines:new_line=line.replace('python','C')print(new_line.strip())

10.2 写入文件

10.2.1 写入空文件

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

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

调用open() 时提供了两个实参(见❶)。第一个实参也是要打开的文件的名称;第二个实参(‘w’ )告诉Python,我们要以写入模式 打开这个文件。打开文件时,可指定读取模式 (‘r’ )、写入模式 (‘w’ )、附加模式 (‘a’ )或让你能够读取和写入文件的模式(‘r+’ )。如果你省略了模式实参,Python将以默认的只读模式打开文件。
如果你要写入的文件不存在,函数open() 将自动创建它。然而,以写入(‘w’ )模式打开文件时千万要小心,因为如果指定的文件已经存在,Python将在返回文件对象前清空该文件。
在❷处,我们使用文件对象的方法write() 将一个字符串写入文件。这个程序没有终端输出,但如果你打开文件programming.txt,将看到其中包含如下一行内容:I love programming.
注意  Python只能将字符串写入文本文件。要将数值数据存储到文本文件中,必须先使用函数str() 将其转换为字符串格式

10.2.2 写入多行

函数write()不会在你写入的文本末尾添加换行符,因此如果你写入多行时,需要添加换行符。

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

I love programming.
I love creating new games.

10.2.3 附加到文件

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

filename='programming.txt'with open(filename,'a') as file_object:file_object.write("I love it.\n")file_object.write("haha.\n")

I love programming.
I love creating new games.
I love it.
haha.
练习题10-3~10-5

filename='guest.txt'
guest=input("Please input your name: ")
with open(filename,'a') as file_object:file_object.write(guest+"\n")
filename='guest.txt'
while True:guest=input("Please input your name: ")    if guest == 'quit':breakelse:with open(filename,'a') as file_object:file_object.write(guest+"\n")print("welcome! " + guest)

10.3 异常

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

10.3.1 处理ZeroDivisionError异常

print(5/0)
显然,Python无法这样左,因此你将看到一个traceback:
Traceback (most recent call last):
File “division.py”, line 1, in
print(5/0)
ZeroDivisionError: division by zero
ZeroDivisionError 是一个异常对象。Python无法按你的要求做时,就会创建这种对象。在这种情况下,Python将停止运行程序,并指出
引发了哪种异常,而我们可根据这些信息对程序进行修改。

10.3.2 使用try-except代码块

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

You can’t divide by zero!

10.3.3 使用异常避免崩溃

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

print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")while True:first_number=input("\nFirst number: ")if first_number == 'q':breaksecond_number=input("\nSecond number: ")if second_number == 'q':breakanswer=int(first_number)/int(second_number)print(answer)

这个程序没有采取任何处理错误的措施,因此让它执行除数为0的除法运算时,它将崩溃:
Give me two numbers, and I’ll divide them.
Enter ‘q’ to quit.
First number: 5
Second number: 0
Traceback (most recent call last):
File “division.py”, line 9, in
answer = int(first_number) / int(second_number)
ZeroDivisionError: division by zero
程序崩溃可不好,但让用户看到traceback也不是好主意。不懂技术的用户会被它们搞糊涂,而且如果用户怀有恶意,他会通过traceback获悉你不希望他知道的信息。例如,他将知道你的程序文件的名称,还将看到部分不能正确运行的代码。有时候,训练有素的攻击者可根据这些信息判断出可对你的代码发起什么样的攻击。

10.3.4 else代码块

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

print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")while True:first_number=input("\nFirst number: ")if first_number == 'q':breaksecond_number=input("\nSecond number: ")if second_number == 'q':breakelse:try:answer=int(first_number)/int(second_number)except ZeroDivisionError:print("You can't divide by 0!")else:print(answer)

处理FileNotFoundError异常

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

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)

10.3.6 分析文本

下面来提取童话 Alice in Wonderland 的文本,并尝试计算它包含多少个单词。我们将使用方法split() ,它根据一个字符串创建一个单词列表。下面是对只包含童话名"Alice in Wonderland" 的字符串调用方法split() 的结果:
.>>>title = “Alice in Wonderland”
.>>>title.split()
[‘Alice’, ‘in’, ‘Wonderland’]
方法split() 以空格为分隔符将字符串分拆成多个部分,并将这些部分都存储到一个列表中.

filename='alice.txt'try:with open(filename,encoding='utf-8') 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.")

The file alice.txt has about 29465 words.

10.3.7 使用多个文件

def count_words(filename):try:with open(filename,encoding='utf-8') as f_obj:contents=f_obj.read()except FileNotFoundError:msg="Sorry, the file " + filename + " does not exit."print(msg)else:words=contents.split()num_words=len(words)print("The file " + filename + " has about " +str(num_words) + " words.")filenames=['alice.txt', 'siddhartha.txt', 'moby_dick.txt']
for filename in filenames:count_words(filename)

The file alice.txt has about 29465 words.
Sorry, the file siddhartha.txt does not exit.
Sorry, the file moby_dick.txt does not exit.

10.3.8 失败时一声不吭

在前一个示例中,我们告诉用户有文件找不到。但并非每次捕获到异常时都需要告诉用户,有时候你希望程序在发生异常时一声不吭,就像什么都没有发生一样继续运行。
要让程序在失败时一声不吭,可像通常那样编写try 代码块,但在except 代码块中明确地告诉Python什么都不要做。Python有一个pass 语句,可在代码块中使用它来让Python什么都不要做:

def count_words(filename):try:with open(filename,encoding='utf-8') as f_obj:contents=f_obj.read()except FileNotFoundError:passelse:words=contents.split()num_words=len(words)print("The file " + filename + " has about " +str(num_words) + " words.")filenames=['alice.txt', 'siddhartha.txt', 'moby_dick.txt']
for filename in filenames:count_words(filename)

The file alice.txt has about 29465 words.
pass 语句还充当了占位符,它提醒你在程序的某个地方什么都没有做,并且以后也许要在这里做些什么。例如,在这个程序中,我们可能决定将找不到的文件的名称写入到文件missing_files.txt中。用户看不到这个文件,但我们可以读取这个文件,进而处理所有文件找不到的问题。
练习题10-6~10-10

print("please input two numbers:")
try:first=int(input("The first number: "))second=int(input("The second number: "))answer=first+second
except ValueError:print('Please ensure that you are inputing numbers.')
else:print(answer)
print("please input two numbers:")while True:try:first=int(input("The first number: "))except ValueError:passelse:while True:try:second=int(input("The second number: "))except ValueError:passelse:breakanswer=first+secondprint(answer)
def read_and_print(filename):try:with open(filename) as f_obj:contents=f_obj.read()except FileNotFoundError:print(filename + " is not found")else:print(contents.strip())
read_and_print('cats.txt')

可以使用方法count() 来确定特定的单词或短语在字符串中出现了多少次
.>>> line = “Row, row, row your boat”
.>>> line.count(‘row’)
2
.>>> line.lower().count(‘row’)
3
请注意,通过使用lower() 将字符串转换为小写,可捕捉要查找的单词出现的所有次数,而不管其大小写格式如何。

10.4 存储数据

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

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

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

import jsonnumbers=[2,3,5,11]❶filename='numbers.json'
❷with open(filename,'w') as f_obj:
❸    json.dump(numbers, f_obj)

我们先导入模块json ,再创建一个数字列表。在❶处,我们指定了要将该数字列表存储到其中的文件的名称。通常使用文件扩展名.json来指出文件存储的数据为JSON格式。接下来,我们以写入模式打开这个文件,让json 能够将数据写入其中(见❷)。在❸处,我们使用函数json.dump() 将数字列表存储到文件numbers.json中。

import json❶filename='numbers.json'
❷with open(filename) as f_obj:
❸    numbers=json.load(f_obj)print(numbers)

[2, 3, 5, 11]
在❶处,我们确保读取的是前面写入的文件。这次我们以读取方式打开这个文件,因为Python只需读取这个文件(见❷)。在❸处,我们使用函数json.load() 加载存储在numbers.json中的信息,并将其存储到变量numbers 中。最后,我们打印恢复的数字列表,看看它是否与number_writer.py中创建的数字列表相同

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

存储用户名字:

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

读取用户并发出问候:

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

我们需要将这两个程序合并到一个程序中。这个程序运行时,我们将尝试从文件username.json中获取用户名,因此我们首先编写一个尝试恢复用户名的try 代码块。如果这个文件不存在,我们就在except 代码块中提示用户输入用户名,并将其存储在username.json中,以便程序再次运行时能够获取它:

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

10.4.3 重构

你经常会遇到这样的情况:代码能够正确地运行,但可做进一步的改进——将代码划分为一系列完成具体工作的函数。这样的过程被称为重构 。重构让代码更清晰、更易于理解、更容易扩展。
要重构上一个程序,可将其大部分逻辑放到一个或多个函数中。上一个程序的重点是问候用户,因此我们将其所有代码都放到一个名为greet_user() 的函数中:

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

接下来重构greet_user()

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

练习题10-11~10-13

import jsonfilename='favorite_number'
number=input("your favorite number:")
with open(filename,'w') as f_obj:json.dump(number,f_obj)try:with open(filename) as f_obj:fav_number=json.load(f_obj)
except FileNotFoundError:pass
else:print("I know your favorite number! It's " + fav_number)
import jsonfilename='favorite_number'try:with open(filename) as f_obj:fav_number=json.load(f_obj)
except FileNotFoundError:number=input("your favorite number:")with open(filename,'w') as f_obj:json.dump(number,f_obj)print(number + "! I got it.")
else:print("I know your favorite number! It's " + fav_number)
import jsondef get_stored_username():filename='username.json' try:with open(filename) as f_obj:username=json.load(f_obj)            except FileNotFoundError:return Noneelse:return usernamedef get_new_username():username=input("What is your name?")filename='username.json' with open(filename,'w') as f_obj:json.dump(username,f_obj)return usernamedef greet_user():username=get_stored_username()if username:answer=input("Is the name right?(Y/N)")if answer == 'Y':print("Welcome back, " + username + "!")else:username=get_new_username()print("We'll remember you when you come back, " + username + "!")else:username=get_new_username()print("We'll remember you when you come back, " + username + "!")greet_user()

Chapter 11 测试代码

name_function.py

def get_fromatted_name(first,last):"""Generate a neatly formatted full name."""full_name=first + ' ' +lastreturn full_name.title()

11.1.1 单元测试和测试用例

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

11.1.2可通过的测试

要为函数编写测试用例,可先导入模块unittest 以及要测试的函数,再创建一个继承unittest.TestCase 的类,并编写一系列方法对函数行为的不同方面进行测试。
test_name_function.py

import unittest
from name_function import get_formatted_name❶class NamesTestCase(unittest.TestCase):"""测试name_function.py"""def test_first_last_name(self):"""能够正确地处理像Janis Joplin这样的姓名吗?"""
❷        formatted_name = get_formatted_name('janis','joplin')
❸        self.asseserEqual(formatted_name,'Janis Joplin')unittest.main()

Ran 1 test in 0.001s

OK
首先,我们导入了模块unittest 和要测试的函数get_formatted_name() 。在❶处,我们创建了一个名为NamesTestCase 的类,用于包含一系列针对get_formatted_name() 的单元测试。你可随便给这个类命名,但最好让它看起来与要测试的函数相关,并包含字样Test。这个类必须继承unittest.TestCase 类,这样Python才知道如何运行你编写的测试。
NamesTestCase 只包含一个方法,用于测试get_formatted_name() 的一个方面。我们将这个方法命名为test_first_last_name() ,因为我们要核实的是只有名和姓的姓名能否被正确地格式化。我们运行testname_function.py时,所有以test 打头的方法都将自动运行。在这个方法中,我们调用了要测试的函数,并存储了要测试的返回值。在这个示例中,我们使用实参’janis’ 和’joplin’ 调用get_formatted_name() ,并将结果存储到变量formatted_name 中(见❷)。
在❸处,我们使用了unittest 类最有用的功能之一:一个断言 方法。断言方法用来核实得到的结果是否与期望的结果一致。在这里,我们知道get_formatted_name() 应
返回这样的姓名,即名和姓的首字母为大写,且它们之间有一个空格,因此我们期望formatted_name 的值为Janis Joplin 。为检查是否确实如此,我们调用unittest
的方法assertEqual() ,并向它传递formatted_name 和’Janis Joplin’ 。代码行self.assertEqual(formatted_name, ‘Janis Joplin’) 的意思是
说:“将formatted_name 的值同字符串’Janis Joplin’ 进行比较,如果它们相等,就万事大吉,如果它们不相等,跟我说一声!”
代码行unittest.main() 让Python运行这个文件中的测试。

11.1.4 添加新测试

import unittest
from name_function import get_formatted_nameclass NamesTestCase(unittest.TestCase):"""测试name_function.py"""def test_first_last_name(self):"""能够正确地处理像Janis Joplin这样的姓名吗?"""formatted_name = get_formatted_name('janis','joplin')self.assertEqual(formatted_name,'Janis Joplin')def test_first_last_middle_name(self):"""能够正确地处理像Wolfgang Amadeus Mozart这样的姓名吗?"""formatted_name = get_formatted_name('Wolfgang','Amadeus','Mozart')self.assertEqual(formatted_name,'Wolfgang Amadeus Mozart')unittest.main()

练习题11-1

import unittest
from city_fuction import city_countryclass CityTestCase(unittest.TestCase):"""test city_function"""def test_city_country(self):city_and_country=city_country('santiago','chile')self.assertEqual(city_and_country,'Santiago, Chile')unittest.main()

11.2 测试类

unittest Module中的断言方法

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

11.2.2 一个要测试的类

类的测试与函数的测试相似——你所做的大部分工作都是测试类中方法的行为,但存在一些不同之处,下面来编写一个类进行测试。来看一个帮助管理匿名调查的类:
survey.py

class AnonymousSurvey():def __init__(self,question):"""存储一个问题,并为存储答案左准备"""self.question=questionself.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)
class AnonymousSurvey():def __init__(self,question):"""存储一个问题,并为存储答案左准备"""self.question=questionself.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 类可用于进行简单的匿名调查。假设我们将它放在了模块survey 中,并想进行改进:让每位用户都可输入多个答案;编写一个方法,它只列出不同的答案,并指出每个答案出现了多少次;再编写一个类,用于管理非匿名调查。
进行上述修改存在风险,可能会影响AnonymousSurvey 类的当前行为。例如,允许每位用户输入多个答案时,可能不小心修改了处理单个答案的方式。要确认在开发这个模块时没有破坏既有行为,可以编写针对这个类的测试。

11.2.3 测试AnonymousSurvey类

import unittest
from survey import AnonymousSurvey❶class TestAnonymousSurvey(unittest.TestCase):❷    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)unittest.main()

我们首先导入了模块unittest 以及要测试的类AnonymousSurvey 。我们将测试用例命名为TestAnonymousSurvey ,它也继承了unittest.TestCase (见❶)。第一个测试方法验证调查问题的单个答案被存储后,会包含在调查结果列表中。对于这个方法,一个不错的描述性名称是test_store_single_response() (见❷)。如果这个测试未通过,我们就能通过输出中的方法名得知,在存储单个调查答案方面存在问题。
要测试类的行为,需要创建其实例。在❸处,我们使用问题"What language did you first learn to speak?" 创建了一个名为my_survey 的实例,然后使用方法store_response() 存储了单个答案English 。接下来,我们检查English 是否包含在列表my_survey.responses 中,以核实这个答案是否被妥善地存储了(见❹)。

import unittest
from survey import AnonymousSurveyclass TestAnonymousSurvey(unittest.TestCase):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_responses(self):"""测试三个答案是否妥善存储"""question="What language did you first learn to speak?"my_survey=AnonymousSurvey(question)responses=['English','Spanish','Mandarin']for response in responses:my_survey.store_response(response)for response in responses:self.assertIn(response, my_survey.responses)unittest.main()

11.2.4 方法setUp()

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

import unittest
from survey import AnonymousSurveyclass TestAnonymousSurvey(unittest.TestCase):def setUp(self):"""创建一个调查对象和一组答案,供使用的方法使用"""question="What language did you first learn 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_responses(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() 做了两件事情:创建一个调查对象(见❶);创建一个答案列表(见❷)。存储这两样东西的变量名包含前缀self (即存储在属性中),因此可在这个类的任何地方使用。这让两个测试方法都更简单,因为它们都不用创建调查对象和答案。方法test_store_three_response() 核实self.responses 中的第一个答案——self.responses[0] ——被妥善地存储,而方法test_store_three_response() 核实self.responses 中的全部三个答案都被妥善地存储。
再次运行test_survey.py时,这两个测试也都通过了。如果要扩展AnonymousSurvey ,使其允许每位用户输入多个答案,这些测试将很有用。修改代码以接受多个答案后,可运行这些测试,确认存储单个答案或一系列答案的行为未受影响。
测试自己编写的类时,方法setUp() 让测试方法编写起来更容易:可在setUp() 方法中创建一系列实例并设置它们的属性,再在测试方法中直接使用这些实例。相比于在每个测试方法中都创建实例并设置其属性,这要容易得多。
注意  运行测试用例时,每完成一个单元测试,Python都打印一个字符:测试通过时打印一个句点;测试引发错误时打印一个E ;测试导致断言失败时打印一个F 。这就是你运行测试用例时,在输出的第一行中看到的句点和字符数量各不相同的原因。如果测试用例包含很多单元测试,需要运行很长时间,就可通过观察这些结果来获悉有多少个测试通过了。
练习题11-3
employee.py

class Employee():def __init__(self,first_name,last_name,salary):self.first_name=first_nameself.last_name=last_nameself.salary=salarydef give_raise(self, increase=5000):self.salary += increaseemploy=Employee('zhang','qian',1000)
employ.give_raise(8000)
print(employ.salary)
a=[100,20]
print(a[0]==100)
import unittest
from employee import Employeeclass TestEmployee(unittest.TestCase):"""test employee.py"""def setUp(self):self.increase=8000  self.my_employee=Employee('zhang','qian',1000)self.salaries=[6000,9000]def test_give_default_raise(self):self.my_employee.give_raise()self.assertEqual(self.salaries[0],self.my_employee.salary)def test_give_custom_raise(self):self.my_employee.give_raise(self.increase)self.assertEqual(self.salaries[1], self.my_employee.salary)unittest.main()

Day 5 of Learning Python相关推荐

  1. learning python学习小记(一)

    python是一种高效的语言.尽管其运行效率比传统的语言差,但是由于其开发的高效性和简单性让程序员在开发过程中节省很多时间,所以python非常受欢迎.程序员的时间是非常有限的,提高程序员开发的效率非 ...

  2. Python学习入门基础教程(learning Python)--5.6 Python读文件操作高级

    前文5.2节和5.4节分别就Python下读文件操作做了基础性讲述和提升性介绍,但是仍有些问题,比如在5.4节里涉及到一个多次读文件的问题,实际上我们还没有完全阐述完毕,下面这个图片的问题在哪呢? 问 ...

  3. lynda.com教程之Learning Python and Django_手把手零基础搭建Django项目

    www.lynda.com作为全球一流的在线培训网站,一直以高质量的视频课程著称.其课程内容也是紧跟行业潮流,本人最近学了里面的一门课程Learning Python and Django, 分享一下 ...

  4. Learning Python 学习Python Lynda课程中文字幕

    Learning Python 中文字幕 学习Python 中文字幕Learning Python Python–流行且易读性强的面向对象语言 - 既强大又易于学习 无论您是编程新手还是有经验的开发人 ...

  5. 【Day 5 of Learning Python 】列表

    5.1 什么是列表 列表是装载任何元素的容器.元素之间可以无关系. 在Python中,我们用方括号 [  ]  来表示列表,用 ,  分割列表中不同的元素. 例如: # 定义一个列表,名为name,并 ...

  6. Python学习入门基础教程(learning Python)--6.3 Python的list切片高级

    上节"6.2 Python的list访问索引和切片"主要学习了Python下的List的访问技术:索引和切片的基础知识,这节将就List的索引index和切片Slice知识点做进一 ...

  7. Python学习入门基础教程(learning Python)--2.3.5Python返回多个值问题

    本节主要学习Python的函数是如何同时返回多个值的问题. 在学习Python的时候惊奇的发现,Python的函数可以同时返回多个值,很有意思. [python]view plaincopy #def ...

  8. Python学习入门基础教程(learning Python)--1.4 Python数据处理基础 .

    本节主要讨论数据操作及运算符等基础知识,熟悉C语言相关知识的读者请跳过此节. 在高级语言编程过程中,有了数据以后通常要对数据进行相应的数据处理,加.减.乘.除等基本运算,不难理解. 在Python里 ...

  9. Python学习入门基础教程(learning Python)--3.3.3 Python逻辑关系表达式

    在if分支判断语句里的条件判断语句不一定就是一个表达式,可以是多个(布尔)表达式的组合关系运算,这里如何使用更多的关系表达式构建出一个比较复杂的条件判断呢?这里需要再了解一下逻辑运算的基础知识.逻辑关 ...

  10. Python学习入门基础教程(learning Python)--3.3.1 Python下的布尔表达式

    简单的说就是if要判断condition是真是假,Python和C语言一样非0即真,所以如果if的condition是布尔表达式我们可以用True或者非0数(不可是浮点数)表示真,用False或者0表 ...

最新文章

  1. linux释放内存脚本
  2. ESP32 flash容量配置
  3. 小黑小波比.sql语句截取字符串的子串
  4. 对Python匿名函数和@property小小理解,希望对你学习也有帮助
  5. YTU 2903: A--A Repeating Characters
  6. 客户关系管理系统-帮管客CRM客户管理系统 v3.0.1
  7. 【零基础学Java】—成员变量和局部变量(九)
  8. 浅谈管理系统操作日志设计(附操作日志类)
  9. 这就是为什么IT人没有女朋友的原因!!
  10. 从模板引擎到模板引擎-使用aspx页面作为模板引擎的一种实现
  11. LINUX安装依赖库的解决办法
  12. 阿里云云计算:4 阿里云产品架构
  13. visual studio运行时库MT、MTd、MD、MDd
  14. 使用js实现简单的动画效果
  15. 如何用VNC远程管理Linux桌面
  16. 猿创征文|计算机专业硕博研究生提高效率的10款科研工具
  17. 第11章 Docker 中 Gitlab 的安装与配置
  18. 人工智能数学基础---不定积分3:分部积分法
  19. 货币金融学——金融体系监管
  20. flatten层的作用

热门文章

  1. 一篇看懂CVPR 2017五大研究前沿 | 腾讯AI Lab深度解析
  2. 终极Android.mk模板,遍历头文件和源文件目录
  3. php解析html dom节点树
  4. C++编写的QQ木马源码
  5. 190831 CSP-S 2019 模拟
  6. android 子module混淆_Android 代码混淆 混淆方案
  7. 第20章 使用Spring进行事务管理(三)
  8. 私募单周减仓创纪录 超八成股票仓位降至65%
  9. 配置简易oracle客户端,ORACLE 简易客户端配置
  10. ClearQuest灵犀一指系列(一):无状态记录字段的引用