python编程从入门到实践 #01 基础知识(续)

第9章 类

面向对象编程是最有效的软件编写方法之一。在面向对象编程中,你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,你定义一大类对象都有的通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。
根据类来创建对象被称为实例化

9.1 创建和使用类

使用类几乎可以模拟任何东西。

9.1.1 创建Dog类

image.png

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

1.方法_ init_()

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

2.在python2.7中创建类
class ClassName(object):

9.1.2 根据类创建实例

可将类视为有关如何创建实例的说明。Dog类是一系列说明,让python知道如何创建表示特定小狗的实例。

# -*- coding:gbk -*- 
class Dog():
    """一次模拟小狗的简单尝试"""
    def __init__(self,name,age):
        """初始化属性name和age"""
        self.name = name
        self.age = age
    
    def sit(self):
        """模拟小狗被命令时蹲下"""
        print(self.name.title() + " is now siitting.")
    
    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.")

python使用实参‘willie’和6调用Dog类的方法_ init_(),方法_ init_()创建一个表示特定小狗的实例,并使用我们提供的值来设置属性name和age.方法_ init_()并未显式地包含return语句,但python自动返回一个表示这条小狗的实例。将实例存储在变量my_dog中。

命名约定:我们通常可以认为首字母大写的名称(如Dog)指的是类,而小写的名称(如my_dog)指的是根据类创建的实例。

1.访问属性

要访问实例的属性,可使用句点表示法
访问my_dog的属性name的值.python先找到实例my_dog,再查找与这个实例相关联的属性name。在Dog类中引用这个属性时,使用的是self.name

my_dog.name
2.调用方法

根据Dog类创建实例后,就可以使用句点表示法来调用Dog类中定义的任何方法。

class Dog():
    --snip--

my_dog = Dog('willie',6)
my_dog.sit()
my_dog.roll_over()

要调用方法,可指定实例的名称和要调用的方法,并用句点分隔它们。

3.创建多个实例

class Dog():
    --snip--

my_dog = Dog('willie',6)
your_dog = Dog('lucy',3)

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

print("\nYour dog's name is " + your_dog.name.title() + ".")
print("Your dog is " + str(your_dog.age) + " years old.")
your_dog.sit()

课后习题9-1

# -*- coding:gbk -*- 
class Restaurant():
    """餐厅"""
    def __init__(self,restaurant_name,cuisine_type):
        """初始化属性"""
        self.restaurant_name = restaurant_name
        self.cuisine_type = cuisine_type
    
    def describe_restaurant(self):
        print("The name of the restaurant is " + self.restaurant_name + ".")
        print("The cuisine_type is " + self.cuisine_type + ".")
    
    def open_restaurant(self):
        print("The restaurant is oppen.")

restaurant = Restaurant('kfc','fast food')
print(restaurant.restaurant_name)
print(restaurant.cuisine_type)
restaurant.describe_restaurant()
restaurant.open_restaurant()

课后习题9-3

# -*- coding:gbk -*- 
class User():
    """用户"""
    def __init__(self,first_name,last_name,location):
        """初始化属性"""
        self.first_name = first_name
        self.last_name = last_name
        self.location = location
    
    def describe_user(self):
        print("First_name: " + self.first_name)
        print("Last_name: " + self.last_name)
        print("Location: " + self.location)
    
    def greet_user(self):
        full_name = self.first_name + " " + self.last_name
        print("Hello, " + full_name.title() + "." )
    
user = User('zhao','sheng','chongqing')
user.describe_user()
user.greet_user()

9.2 使用类和实例

你可以使用来模拟现实世界中的很多情景。类编写好后,你的大部分时间都将花在使用根据类创建的实例上。你需要执行的一个重要任务是修改实例的属性。你可以直接修改实例的属性,也可以编写方法以特定的方式进行修改。

9.2.1 Car类

# -*- coding:gbk -*- 
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)
print(my_new_car.get_descriptive_name())

9.2.2 给属性指定默认值

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

# -*- coding:gbk -*- 
class Car():
    """一次模拟汽车的简单尝试"""
    def __init__(self,make,model,year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0
    
    def get_descriptive_name(self):
        """返回整洁的描述性信息"""
        long_name = str(self.year) + ' ' + self.make + ' ' + self.model
        return long_name.title()

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

9.2.3 修改属性的值

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

1.直接修改属性的值
class Car():
    --snip--

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

更新属性的方法

class Car():
    --snip--

def update_odometer(self,mileage):
    """将里程表读数设置为指定的值"""
    self.odometer_reading = mileage

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

禁止把里程表读数回调

class Car():
    --snip--

def update_odometer(self,mileage):
    """
    将里程表读数设置为指定的值
    禁止将里程表读数往回调
    """
          if mileage >= self.odometer_reading:
              self.odometer_reading = mileage
          else:
              print("You can't roll back an odometer!")
3.通过方法对属性的值进行递增
class Car():
    --snip--
    def update_odometer(self,mileage):
        --snip--
    def increment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading += miles

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

9.3 继承

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

9.3.1 子类的方法_ init_()

创建子类的实例时,python首先需要的任务是给父类的所有属性赋值。为此,子类的方法_ init_()需要父类施以援手。
- END - 2019/3/31

# -*- coding:gbk -*- 
class Car():
    """一次模拟汽车的简单尝试"""
    def __init__(self,make,model,year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0
    
    def get_descriptive_name(self):
        long_name = 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):
        if mileage >= odometer_reading:
            self.odometer_reading = mileage
        else:
            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())

创建子类时,父类必须包含在当前文件中,且位于子类前面。
定义子类时,必须在括号内指定父类的名称。
super()的一个特殊函数,帮助python将父类和子类关联起来,让python调用ElectricCar的父类的方法_ init_(),让ElectricCar实例包含父类的所有属性。父类也成为超类(superclass),名称super因此而得名。

9.3.2 python2.7中的继承

class Car(object):
    def __init__(self,make,model,year):
        --snip--

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

函数super()需要两个实参:子类名和对象self。

9.3.3 给子类定义属性和方法

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

# -*- coding:gbk -*- 
class Car():
--sinp--

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 = ElectricCar('tesla','model s',2016)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battery()

9.3.4 重写父类的方法

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

class ElectricCar(Car):
    --snip--

    def fill_gas_tank(self):
        """电动汽车没有油箱"""
        print("This car doesn't need a gas tank!")

使用继承时,可让子类保留从父类那里继承而来的精华,并剔除不需要的糟粕。

9.3.5 将实例用作属性

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


image.png

②处的方法_ init_()除self外,还有另一个形参battery_size,这个形参时可选的:如果没有给它提供值,电瓶容量将被设置为70.
④处代码让python创建一个新的Battery实例(由于没有指定尺寸,因此为默认值70),并将该实例存储在属性self.battery中。

my_tesla.battery.describe_battery()

这行代码让python在实例my_tesla中查找属性battery,并对存储在该属性中的Battery实例调用方法describe_battery()。

# -*- coding:gbk -*- 
class Car():
    """一次模拟汽车的简单尝试"""
    def __init__(self,make,model,year):
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0
    
    def get_descriptive_name(self):
        long_name = 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):
        if mileage >= odometer_reading:
            self.odometer_reading = mileage
        else:
            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.")
    def get_range(self):
        """打印一条消息,指出电瓶的续航里程"""
        if self.battery_size ==70:
            range = 240
        elif self.battery_size ==85:
            range = 270
        
    message = "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()

9.3.6 模拟实物

逻辑层面 语法层面

9.4 导入类

将类存储在模块中,然后在主程序中导入所需的模块。

9.4.1 导入单个类

# -*- coding:gbk -*- 
"""一个可用于表示汽车的类"""

class Car():
    """一次模拟汽车的简单尝试"""
    def __init__(self,make,model,year):
        """初始化描述汽车的属性"""
        self.make = make
        self.model = model
        self.year = year
        self.odometer_reading = 0
    
    def get_descriptive_name(self):
        """返回整洁的描述性名称"""
        long_name = 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):
        """
        将里程表读数设置为指定的值
        拒绝将里程表往回拨
        """
        if mileage >= odometer_reading:
            self.odometer_reading = mileage
        else:
            print("You can't roll back an odometer!")
        
    def increment_odometer(self,miles):
        """将里程表读数增加指定的量"""
        self.odometer_reading += miles

包含一个模块级文档字符串,对该模块的内容做了简要的秒速。应该为自己创建的每个模块都编写文档字符串。

from car9 import Car

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

import语句让python打开模块car9,并导入其中的Car类。
专注于主程序的高级逻辑

9.4.2 在一个模块中存储多个类

虽然同一个模块中的类之间应存在某种关联性,但可根据需要在一个模块中存储任意数量的类。


image.png

9.4.3 从一个模块中导入多个类

可根据需要在程序文件中导入任意数量的类。


image.png

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

9.4.4 导入整个模块

image.png

使用语法module_name.class_name访问需要的类

9.4.5 导入模块中的所有类

要导入模块中的每个类,可使用语法:

from module_name import *

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

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

image.png

image.png

image.png

9.4.7 自定义工作流程

9.5 python标准库

python标准库是一组模块,安装的python都包含它。
字典让你能够将信息关联起来,但它们不记录你添加键-值对的顺序。要创建字典并记录其中的键-值对添加顺序,可使用模块collections中的OrderedDict类。

from collections import OrderedDict

favorite_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() + ".")

9.6 类编码风格

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

第10章 文件和异常

10.1 从文件中读取数据

要读取文本文件中的信息,首先需要将信息读取到内存中。可一次性读取文件的全部内容,也可以每次一行的方式逐步读取。

10.1.1 读取整个文件

pi_digits.txt

3.1415926525
  8979323846
  2643383279

file_reader.py

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

函数open()接受一个参数:要打开的文件的名称,并返回一个表示文件的对象。(python在当前执行的文件所在的目录中查找指定的文件。)python将这个对象存储在后面使用的变量中。
关键字with在不再需要访问文件后将其关闭。
方法read()读取这个文件的全部内容。
结果多出空行,因为read()到达文件末尾时返回一个空字符串,而将这个空字符串显示出来时就说一个空行。要删除末尾的空行,可在print语句中使用rstrip()。

print(contents.rstrip())

10.1.2 文件路径

将文件名传递给函数open()时,python将在当前执行的文件(即.py程序文件)所在的目录中查找文件。
要让python打开不与程序文件位于同一个目录中的文件,需要提供文件路径,它让python到系统的特定位置去查找。
由于文件夹txt_files位于文件夹python_work中,因此可使用相对文件路径来打开这个文件夹中的文件。
在windows系统中,在文件路径中使用反斜杠(\)而不是斜杠(/)。

with open('txt_files\filename.txt') as file_object:

绝对文件路径通常比相对文件路径更长,因此将其存储在一个变量中,再将该变量传递给open()会有所帮助。

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

10.1.3 逐行读取

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

filename = 'pi_digits.txt'

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

将要读取的文件的名称存储在filename中,变量filename表示的并非实际文件——只是一个让python知道到哪里去查找文件的字符串。

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

方法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.rstrip()

print(pi_string)
print(len(pi_string))
image.png
pi_string += line.strip()

image.png

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

10.1.6 包含一百万位的大型文件

image.png

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

将生日表示为一个由数字组成的字符串,再检查这个字符串是否包含在pi_string中。

filename = 'pi_million_digits'

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 million digits of pi!")
else:
    print("Your birthday does not appear in the first million digits of pi!")

10.2 写入文件

保存数据的最简单的方式之一是将其写入到文件中。通过将输出写入文件,即便关闭包含程序输出的终端窗口,这些输出依然存在:你可以在程序结束运行后查看这些输出,可与别人分享输出文件,还可编写程序来将这些输出读取到内存中并进行处理。

10.2.1 写入空文件

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

filename = 'programming.txt'

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

调用open()时提供了两个实参:1)要打开的文件的名称;2)实参‘w’,告诉python,我们要以写入模式打开这个文件。打开文件时,可指定读取模式(‘r’)、写入模式('w')、附加模式('a')或让你能够读取和写入文件的模式('r+')。如果省略了模式实参,python将以默认的只读模式打开文件。
如果要写入的文件不存在,函数open()将自动创建它。以写入(‘w’)模式打开文件时要小心,因为如果指定的文件已经存在,python将在返回文件对象前清空该文件。
这个程序没有终端输出,但如果打开文件programming.txt将看到内容。
注意: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")

10.2.3 附加到文件

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

filename = 'programming.txt'

with open(filename,'a') as file_object:
    file_object.write("I also love finding meaning in large datasets.\n")
    file_object.write("I love creating apps that can run in browser.\n")

10.3 异常

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

10.3.1 处理ZeroDivisionError异常

image.png

10.3.2 使用try-except代码块

try:
    print(5/0)
except ZeroDivisionError:
    print("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':
        break
    second_number = input("Second number: ")
    if second_number == 'q':
        break
    answer = int(first_number) / int(second_number)
    print(answer)

这个程序没有采取任何处理错误的措施,因此让它执行除数为0时的除法运算,它将崩溃。


image.png

如果用户怀有恶意,他将通过traceback获悉你不希望他知道的信息。例如,他将知道你的程序的名称,还将看到部分不能正确运行的代码。有时候,训练有素的攻击者可根据这些信息判断出可对你的代码发起什么样的攻击。

10.3.4 else代码块

通过将可能引发错误的代码放在try-except代码块中,可提高赠程序抵御错误的能力。依赖于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':
        break
    second_number = input("Second number: ")
    try:
        answer = int(first_number) / int(second_number)
    except ZeroDivisionError:
        print("You can't divide by 0!")
    else:
        print(answer)

try-except-else代码块的工作原理:python尝试执行try代码块中的代码;只有可能引发异常的代码才需要放在try语句中。有时候,有一些仅在try代码块成功执行时才需要运行的代码应放在else代码块中。except代码块告诉python,如果它尝试运行try代码块中的代码时引发了指定的异常,该怎么办。
通过预测可能发生错误的代码,可编写健壮的程序,他们即便面临无效数据或缺少资源,也能继续运行,从而能够抵御无意的用户错误和恶意的攻击。

10.3.5 处理FileNotFoundError异常

使用文件时,一种常见的问题是找不到文件:你要查找的文件可能在其他地方、文件名可能不正确或者这个文件根本就不存在。

filename = 'alice.txt'

with open(filename) as f_ojt:
    contents = f_ojt.read()
image.png
filename = 'alice.txt'

try:
    with open(filename) as f_ojt:
        contens = f_ojt.read()
except FileNotFoundError:
    msg = "Sorry, the file " + filename + " does not exist."
    print(msg)

10.3.6 分析文本

方法split(),它根据一个字符串创建一个单词列表。
方法split()以空格为分隔符将字符串分拆成多个部分,并将这些部分都存储到一个列表中,结果是一个包含字符串中所有单词的列表。


image.png
# -*- coding:gbk -*-
filename = 'programming.txt'

try:
    with open(filename) as f_ojt:
        contents = f_ojt.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.")

10.3.7 使用多个文件

# -*- coding:gbk -*-
def count_words(filename):
    """计算一个文件大致包含多少个单词"""
    try:
        with open(filename) as f_ojt:
            contents = f_ojt.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.")

filename = 'programming.txt'
count_words(filename)

多个

def count_words(filename):
--snip--

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

10.3.8 失败时一声不吭

python中有一个pass语句,可在代码块中使用它来让python什么都不要做。

# -*- coding:gbk -*-
def count_words(filename):
"""计算一个文件大致包含多少个单词"""
    try:
        --snip--
    except FileNotFoundError:
        pass
    else:
    --snip--
filenames = ['alice.txt','siddhartha.txt','moby_dick.txt','little_women.txt']
for filename in filenames:
    count_words(filename)

10.3.9 决定报告哪些错误

python的错误处理结构让你能过细致地控制与用户分享错误信息的程度,要分享多少信息由你决定。
编写得很好且经过详尽测试的代码不容易出现内部错误,如语法或逻辑错误,但只要程序依赖于外部因素,如用户输入、存在指定的文件、有网络链接,就由可能出现异常。凭借经验判断该在程序的什么地方包含异常处理块,以及出现错误时该向用户提供多少相关的信息。

10.4 存储数据

使用模块json来存储数据。
模块json让你能够将简单的python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据。你还可以使用json在python程序之间分享数据。更重要的是,JSON数据格式并非python专业的,这让你能够将以JSON格式存储的数据与使用其他编程语言的人分享。
注意:JSON(JavaScriptObjectNotation)格式最初是为JavaScript开发的,但随后成了一种常见格式,被包括python在内的众多语言采用。

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

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

import json

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

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

导入模块json
通常使用文件拓展名.json来指出文件存储的数据为JSON格式。
这个程序没有输出,但可打开文件numbers.json,看内容。

import json

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

print(numbers)

使用函数json.load()加载存储在numbers.json中的信息,并将其存储在变量numbers中。
这是一种在程序之间共享数据的简单方式。

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

对于用户生成的数据,使用json保存它们大有裨益,因为如果不以某种方式进行存储,等程序停止运行时用户的信息将丢失。
remember_me.py

import json

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

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

greet_user.py

import json

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

合并程序

# -*- coding:gbk -*-
import json

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

10.4.3 重构

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

# -*- coding:gbk -*-
import json

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

greet_user()

考虑到现在使用了函数,我们删除了注释,转而使用一个文档字符串来指出程序是做什么的。

# -*- coding:gbk -*-
import json
def get_stored_username():
    """如果存储了用户名,就获取它"""
    filename = 'username.json'
    try:
        with open(filename) as f_obj:
            username = json.load(f_obj)
    except FileNotFoundError:
        return None
    else:
        return username
    
def 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()
image.png

第11章 测试代码

11.1 测试函数

name_function.py

# -*- coding:gbk -*-
def get_formatted_name(first,last):
    """生成整洁的姓名"""
    full_name = first + ' ' + last
    return full_name.title()

names.py

from name_function import get_formatted_name
print("Enter 'q' ant any time to quit.")
while True:
    first = input("\nPlease give me a first name: ")
    if first == 'q':
        break
    last = input("Please give me a last name: ")
    if last == 'q':
        break

    formatted_name = get_formatted_name(first,last)
    print("\tNeatly formatted name: " + formatted_name + ".")

11.1.1 单元测试和测试用例

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

11.1.2 可通过的测试

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

# -*- coding:gbk -*-
import unittest
from name_function import get_formatted_name

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

    def test_firt_last_name(self):
        """能够正确地处理像Janis Joplin这样的姓名吗?"""
        formatted_name = get_formatted_name('janis','joplin')
        self.assertEqual(formatted_name,'Janis Joplin')
    
unittest.main()

创建一个名为NamesTestCase的类,用于包含一系列针对get_formatted_name()的单元测试。可随便给这个类命名,但最好让它看起来与要测试的函数有关,并包含字样Test。这个类必须继承unittest.TestCase类,这样python才知道如何运行你编写的测试。
断言方法用来核实得到的结果是否与期望的结果一致。
方法self.assertEqual()

image.png

11.1.3 不能通过的测试

image.png

image.png

11.1.4 测试未通过时怎么办

如果你检查的条件没错,测试通过了意味着函数的行为是对的,而测试未通过意味着你编写的新代码有错。因此,测试未通过时,不要修改测试,而应修复导致测试不能通过的代码:检查刚对函数所做的修改,找出导致函数行为不符合预期的修改。


image.png

11.1.5 添加新测试

# -*- coding:gbk -*-
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.assertEqual(formatted_name,'Janis Joplin')
    
    def test_first_last_middle_name(self):
        """能够正确地处理像Wolfgang Amadeus Mozart这样的姓名吗?"""
        formatted_name = get_formatted_name(
          'wolfgang','mozart','amadeus')
        self.assertEqual(formatted_name,'Wolfgang Amadeus Mozart')
    
unittest.main()
image.png

11.2 测试类

11.2.1 各种断言方法

python在unittest.TestCase类中提供了很多断言方法。断言方法检查你认为应该满足的条件是否确实满足。
只能在继承unittest.TestCase的类中使用这些方法。


image.png

11.2.2 一个要测试的类

类的测试和函数的测试相似——你所做的大部分工作都是测试类中方法的行为。
- END - 2019/3/31 22:14

111.2.3 测试AnonymousSurvey类

11.2.4 方法setUp()

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容