面向对象编程 是最有效的软件编写方法之一。在面向对象编程中,你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。在本章中,你将学习处理文件,让程序能够快速地分析大 量的数据;你将学习错误处理,避免程序在面对意外情形时崩溃。还有如何使用Python模块unittest 中的工具来测试代码。
类
创建和使用类
class Cat():
def __init__(self, name, color):
self.name = name
self.color = color
def eat(self):
print('cat ' + self.name + ' color ' + self.color + ', now eat')
def run(self):
print('cat ' + self.name + ' color ' + self.color + ', now run')
my_cat = Cat('Spring', 'white')
print(my_cat.name)
print(my_cat.color)
my_cat.eat()
my_cat.run()
上面创建了类 Cat ,并实例化了 my_cat,然后调用了类的方法 eat() 和 run()。输出结果:
类中的函数称为方法。init() 是函数的构造方法,每档创建新实例时 Python 都会自动运行它。注意构造方法名字必须是这个,是规定好的。
上面的例子中__init__(self, name, color) 有三个形参,第一个形参 self 必不可少,还必须位于其他形参的前面。其他的形参可以根据需要调整。self 是一个指向实例本身的引用,让实例能够访问类中的属性和方法。
还可以通过实例直接访问属性:my_cat.name。但在其他语言中并不建议这样做。
在Python 2.7中创建类
在Python 2.7中创建类时,需要做细微的修改——在括号内包含单词object:
class ClassName(object):
类的属性
给属性设置默认值
类中的每个属性都必须有初始值,哪怕这个值是0或空字符串。在有些情况下,如设置默认值时,在方法__init__() 内指定这种初始值是可行的;如果你对某个属性这样做了,就无需包含为它提供初始值的形参。
重新定义 Cat ,在构造方法中给属性 age 设置默认值。
class Cat():
def __init__(self, name, color):
self.name = name
self.color = color
self.age = 3
def eat(self):
print('cat ' + self.name + ' color ' + self.color + ', now eat')
def run(self):
print('cat ' + self.name + ' color ' + self.color + ', now run')
def print_age(self):
print('cat`s age is ' + str(self.age))
修改属性的值
可以以三种不同的方式修改属性的值:直接通过实例进行修改,通过方法进行设置。
直接修改属性的值
要修改属性的值,最简单的方式是通过实例直接访问它。
class Cat():
def __init__(self, name, color):
self.name = name
self.color = color
self.age = 3
def eat(self):
print('cat ' + self.name + ' color ' + self.color + ', now eat')
def run(self):
print('cat ' + self.name + ' color ' + self.color + ', now run')
def print_age(self):
print('cat`s age is ' + str(self.age))
my_cat = Cat('Spring', 'white')
my_cat.print_age()
my_cat.age = 4
my_cat.print_age()
上例直接通过 my_cat.age = 4 修改了 age 属性的值。
通过方法修改属性的值
再来更新代码,加入 update_age() 方法来修改 age 的属性。
class Cat():
def __init__(self, name, color):
self.name = name
self.color = color
self.age = 3
def eat(self):
print('cat ' + self.name + ' color ' + self.color + ', now eat')
def run(self):
print('cat ' + self.name + ' color ' + self.color + ', now run')
def print_age(self):
print('cat`s age is ' + str(self.age))
def update_age(self, age):
self.age = age
my_cat = Cat('Spring', 'white')
my_cat.print_age()
my_cat.update_age(10)
my_cat.print_age()
继承
编写类时,并非总是要从空白开始。如果你要编写的类是另一个现成类的特殊版本,可使用继继承承 。一个类继继承承 另一个类时,它将自动获得另一个类的所有属性和方法;原有的 类称为父父类类 ,而新类称为子子类类 。子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法。
class Animal():
def __init__(self, name, age):
self.name = name
self.age = age
def run(self):
print('Animal ' + self.name + ' run')
class Cat(Animal):
def __init__(self, name, age):
super().__init__(name, age)
cat = Cat('Tony', 2)
cat.run()
运行程序,输出:
先定义了类 Animal,又定义了 Cat 继承自 Animal。 Animal称为父类, Cat 称为子类。通过输出可以验证,子类继承了父类的方法。
在子类的构造方法中要先实现父类的构造方法:super().init(name, age)。
还可以给子类定义自己的方法,或者重写父类的方法。
class Animal():
def __init__(self, name, age):
self.name = name
self.age = age
def run(self):
print('Animal ' + self.name + ' run')
class Cat(Animal):
def __init__(self, name, age):
super().__init__(name, age)
def play(self):
print('Cat ' + self.name + ' play')
def run(self):
print('Cat ' + self.name + ' run')
cat = Cat('Tony', 2)
cat.run()
cat.play()
我们来修改下程序,Animal 类不变,Cat 类还是继承了 Animal ,但定义了自己的方法 play() 并重写了父类方法 run() 。运行程序,得到输出:
Python2.7 中的继承
在Python 2.7中,继承语法稍有不同,ElectricCar类的定义类似于下面这样:
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。为帮助Python将父类和子类关联起来,这些实参必不可少。另外,在Python 2.7中使用继承时,务必在定义父类时在括号内指定object。
导入类
当一个文件过长时,可以将其中一部分代码抽离出去,然后导入到主文件中。
导入方式有多种:
导入单个类
假如 car.py 里定义了类 Car
from car import Car
从一个模块中导入多个类
假如 car.py 包含了三个类 Car , Battery 和 ElectricCar 。
只导入一个类:
from car import ElectricCar
导入多个类,中间用逗号隔开:
from car import Car, ElectricCar
导入整个模块
还可以导入整个模块,再使用句点表示法访问需要的类。这种导入方法很简单,代码也易于阅读。由于创建类实例的代码都包含模块名,因此不会与当前文件使用的任何名称发生冲突。
import car
my_car = car.Car()
导入模块中的所有类
要导入模块中的每个类,可使用下面的语法:
from module_name import *
不推荐使用这种导入方式,其原因有二。
首先,如果只要看一下文件开头的import语句,就 能清楚地知道程序使用了哪些类,将大有裨益;但这种导入方式没有明确地指出你使用了模块中 的哪些类。这种导入方式还可能引发名称方面的困惑。如果你不小心导入了一个与程序文件中其 他东西同名的类,将引发难以诊断的错误。这里之所以介绍这种导入方式,是因为虽然不推荐使 用这种方式,但你可能会在别人编写的代码中见到它。
需要从一个模块中导入很多类时,最好导入整个模块,并使用module_name.class_name语法 来访问类。这样做时,虽然文件开头并没有列出用到的所有类,但你清楚地知道在程序的哪些地 方使用了导入的模块;你还避免了导入模块中的每个类可能引发的名称冲突。
文件和异常
从文件中读取数据
要使用文本文件中的信息,首先需要将信息读取到内存中。为此,你可以一次性读取文件的全部内容,也可以以每次一行的方式逐步读取。
读取整个文件
with open('test.txt') as file_obj:
contents = file_obj.read()
print(contents)
open() 用于打开一个文件,参数为文件的路径。
关键字 with 在不再需要访问文件后将其关闭。有了 with 你只管打开文件,并在需要时使用它,Python自会在合适的时候自动将其关闭。
相比于原始文件,该输出唯一不同的地方是末尾多了一个空行。为何会多出这个空行呢?因为 read() 到达文件末尾时返回一个空字符串,而将这个空字符串显示出来时就是一个空行。要删除多出来的空行,可在print语句中使用 rstrip()。
文件路径可以是相对路径,也可以是绝对路径。
逐行读取
with open('test.txt') as file_obj:
for line in file_obj:
print(line.rstrip())
要以每次一行的方式检查文件,可对文件对象使用for循环。
写入文件
with open('test.txt', 'w') as file_obj:
file_obj.write("I love python")
在这个示例中,调用open()时提供了两个实参,第一个实参也是要打开的文件的名称;第二个实参(‘w’)告诉Python,我们要以写入模式打开这个文件。
可选模式:
r :只读。
w : 只写。如果文件不存在则创建,如果文件存在则先清空,再写入。
a :附加模式,写入的内容追加到原始文件后面。如果文件不存在则创建。
r+ :可读可写。
如果你省略了模式实参,Python将以默认的只读模式打开文件。
异常
Python使用被称为异常 的特殊对象来管理程序执行期间发生的错误。每当发生让Python不知所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常的代码,程序将继 续运行;如果你未对异常进行处理,程序将停止,并显示一个traceback,其中包含有关异常的报告。
异常是使用try-except 代码块处理的。try-except 代码块让Python执行指定的操作,同时告诉Python发生异常时怎么办。使用了try-except 代码块时,即便出现异常, 程序也将继续运行:显示你编写的友好的错误消息,而不是令用户迷惑的traceback。
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")
else 代码块
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")
else:
print("no exception")
如果 try 中的代码运行成功,没有出现异常,则执行 else 代码块中的代码。
用 json 存储数据
Python 中使用 json.dump() 和 json.load() 来存储和读取 json 文件。
import json
userInfo = {'username': 'jack', 'age': 18}
with open('test.txt', 'w') as obj:
json.dump(userInfo, obj)
with open('test.txt') as obj:
content = json.load(obj)
print(content)
上例中用 json.dump() 把数据存入到了 test.txt 中,又用 json.load() 把数据从文件中取出并打印。
注意使用前先导入 json 模块。
单元测试
在本章中,你将学习如何使用Python模块unittest 中的工具来测试代码。先定义一个拼接名字的函数 name_function.py
def get_formatted_name(first, last):
full_name = first + ' ' + last
return full_name.title()
再写测试类来测试这个函数
import unittest
from name_function import get_formatted_name
class NamesTestCase(unittest.TestCase):
def test_name_function(self):
full_name = get_formatted_name('david', 'alex')
self.assertEqual(full_name, 'David Alex')
unittest.main()
测试类要继承 unittest.TestCase ,通过 self.assertEqual 断言是否得到的结果和预期相等。
各种断言方法
Python在unittest.TestCase 类中提供了很多断言方法。前面说过,断言方法检查你认为应该满足的条件是否确实满足。如果该条件确实满足,你对程序行为的假设就得到了 确认,你就可以确信其中没有错误。如果你认为应该满足的条件实际上并不满足,Python将引发异常。
方法 | 用途 |
---|---|
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中 |
setUp() 方法
如果你在TestCase类中包含了方法 setUp() ,Python将先运行它,再运行各个以test_打头的方法。
通常用于做一些初始化操作。这样,在你编写 的每个测试方法中都可使用在方法setUp() 中创建的对象了。
总结
在本章中,你学习了:如何编写类;如何使用属性在类中存储信息,以及如何编写方法,以让类具备所需的行为,如何编写方法__init__() ,以便根据类创建包含所需属性的 实例。
使用文件,这让你能够保存你在程序中所做的工作,以及你让用户做的工作。你还将学习异常 。最后,如何使用模块unittest 中的工具来为函数和类编写测试。
欢迎关注公众号【程序猿编码】,添加本人微信号(17865354792),回复:领取学习资料。或者回复:进入技术交流群。网盘资料有如下: