5-3-一个更实际的例子
编写两个类:
- Person——创建并处理关于人员的信息的一个类
- Manager——一个定制的 Person,修改了继承的行为。
1. 步骤1:创建实例
在 Python 中,模块名使用小写字母开头,类名使用一个大写字母开头:
# person.py 文件
class Person:
1.1 编写构造函数
赋给实例属性第一个值的通常方法是,在 __init__
构造函数方法中将它们赋给 self,构造函数方法包含了每次创建一个实例的时候 Python 会自动运行的代码:
# 添加记录字段初始化
class Person:
def __init__(self, name, job, pay): # 构造函数取三个参数
self.name = name
self.job = job
self.pay = pay
job 参数在 __init__
函数的作用域里是一个本地变量,但是 self.job 是实例的一个属性,它暗示了方法调用的内容。
1.2 在进行中测试
常见的做法是,Python 程序员使用交互式提示来进行简单的一次性测试,但是通过在包含测试对象的文件的底部编写代码来进行更多的大量测试:
# 添加增量自测试代码
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
bob = Person('Bob Smith') # 测试类
sue = Person('Sue Jones', job='dev', pay=100000)
print(bob.name, bob.pay) # 获取附加的属性
print(sue.name, sue.pay)
Bob Smith 0
Sue Jones 100000
1.3 以两种方式使用代码
文件底部的测试代码是有用的,但是当文件作为脚本运行的时候,或者当它作为一个模块导入的时候,顶层的 print 都会运行,这不是一种很好的软件关系。
# 允许文件被导入和运行/测试
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
if __name__ == '__main__':
# 自测试代码
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='dev', pay=100000)
print(bob.name, bob.pay)
print(sue.name, sue.pay)
Bob Smith 0
Sue Jones 100000
把文件作为顶层脚本运行的时候,测试它,将它作为类库导入的时候,则不会这么做。
2. 步骤2:添加行为方法
尽管类添加了结构的一个额外的层级,它们最终还是通过嵌入和处理列表及字符串这样的基本核心数据类型来完成其大部分工作。
# 处理嵌入的内置类型:字符串,可变对象
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
if __name__ == '__main__':
# 自测试代码
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='dev', pay=100000)
print(bob.name, bob.pay)
print(sue.name, sue.pay)
print(bob.name.split()[-1])
sue.pay *= 1.10
print('%.2f' % sue.pay)
Bob Smith 0
Sue Jones 100000
Smith
110000.00
在类之外的硬编码操作可能会导致未来的维护问题。
2.1 编写方法
这里借用一种叫做封装的软件设计概念,封装的思想就是把操作逻辑包装到界面之后,这样,每种操作在我们程序里只编码一次。通过这种方式,如果将来需要修改,只需要修改一个版本。
把操作放入方法中,使得这些操作可以应用于类的任何实例,而不是仅能用于把它们硬编码来处理的那些对象。
# 添加方法来封装操作
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
if __name__ == '__main__':
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='dev', pay=100000)
print(bob.name, bob.pay)
print(sue.name, sue.pay)
print(bob.lastName(), sue.lastName())
sue.giveRaise(.10)
print(sue.pay)
Bob Smith 0
Sue Jones 100000
Smith Jones
110000
Python 通过自动把实例传递给第一个参数从而告诉一个方法应该处理哪个实例,通常这个参数叫做 self。bob.lastName() 调用中,bob 是隐藏的主体,传递给了 self。
3. 步骤3:运算符重载
当前,测试还是不能像所需要的那样方便——要跟踪对象,必须手动地接受和打印单个的属性。如果一次显示一个实例真的能给我们一些有用的信息,那还是不错的。但是,实例对象的默认显示格式并不是很好,它显示对象的类名及其在内存中的地址。
3.1 提供打印显示
通过运算符重载可以很容易做得更好,使用 __repr__
方法,__str__
的双胞胎方法。
从技术上讲,__str__
是 print 和 str 的首选,__repr__
被用作这些角色和所有其他上下文中的后备。虽然这两种方法可以用于在不同的上下文中实现不同的显示,但是仅通过编写 __repr__
就足以在所有情况下——打印、嵌套显示和交互回显中提供显示。这仍然允许客户端提供 __str__
的替代显示,但仅限于有限的上下文。
# 添加 __repr__ 重载方法用于打印对象
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
def __repr__(self):
return '[Person: %s, %s]' % (self.name, self.pay)
if __name__ == '__main__':
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='dev', pay=100000)
print(bob)
print(sue)
print(bob.lastName(), sue.lastName())
sue.giveRaise(.10)
print(sue)
[Person: Bob Smith, 0]
[Person: Sue Jones, 100000]
Smith Jones
[Person: Sue Jones, 110000]
__repr__
提供对象的一种代码低层级显示,有时候,类提供了一个 __str__
以便实现用户友好的显式,同时也提供一个 __repr__
以便让开发者看到额外的细节。
4. 步骤4:通过子类定制行为
4.1 扩展方法:不好的方式
复制和粘贴 Person 中的 giveRaise 代码。
class Manager(Person):
def giveRaise(self, percent, bonus=.10):
self.pay = int(self.pay * (1 + percent + bonus))
4.2 扩展方法:好的方式
使用扩展的参数来直接调用其最初的版本:
class Manager(Person):
def giveRaise(self, percent, bonus=.10):
Person.giveRaise(self, percent + bonus)
类方法总是可以在一个实例中调用,或者通过类来调用。通过类直接调用有效地扰乱了继承,并且把调用沿着类树向上传递以运行一个特定的版本。
# 在子类中添加定制行为
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
def __repr__(self):
return '[Person: %s, %s]' % (self.name, self.pay)
class Manager(Person):
def giveRaise(self, percent, bonus=.10):
Person.giveRaise(self, percent + bonus)
if __name__ == '__main__':
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='dev', pay=100000)
print(bob)
print(sue)
print(bob.lastName(), sue.lastName())
sue.giveRaise(.10)
print(sue)
tom = Manager('Tom Jones', 'mgr', 50000)
tom.giveRaise(.10)
print(tom.lastName())
print(tom)
[Person: Bob Smith, 0]
[Person: Sue Jones, 100000]
Smith Jones
[Person: Sue Jones, 110000]
Jones
[Person: Tom Jones, 60000]
5. 步骤5:定制构造函数
重新定义 Manager 中的 __init__
方法,从而提供 mgr 字符串,通过类名的调用来运行 Person 中最初的 __init__
,以便它仍然会初始化对象的状态信息属性。
# 在子类中添加定制构造函数
class Person:
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
def __repr__(self):
return '[Person: %s, %s]' % (self.name, self.pay)
class Manager(Person):
def __init__(self, name, pay):
Person.__init__(self, name, 'mgr', pay)
def giveRaise(self, percent, bonus=.10):
Person.giveRaise(self, percent + bonus)
if __name__ == '__main__':
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='dev', pay=100000)
print(bob)
print(sue)
print(bob.lastName(), sue.lastName())
sue.giveRaise(.10)
print(sue)
tom = Manager('Tom Jones', 50000)
tom.giveRaise(.10)
print(tom.lastName())
print(tom)
[Person: Bob Smith, 0]
[Person: Sue Jones, 100000]
Smith Jones
[Person: Sue Jones, 110000]
Jones
[Person: Tom Jones, 60000]
6. 步骤6:使用内省工具
现在仍有两个问题:
- 当打印 tom 时,Manager 会把它标记为 Person。
- 当前的显式格式只是显式了包含在
__repr__
中的属性,而没有考虑未来的目标。如果更改了在__init__
中分配给对象的属性集合,那么还必须也要更新__repr__
以显示新的名字。
6.1 特殊类属性
可以使用 Python 的内省工具来解决这两个问题,它们是特殊的属性和函数,允许我们访问对象实现的一些内部机制。有两个 hook 可以帮助解决问题:
- 内置的
instance.__class__
属性提供了一个从实例到创建它的类的链接。类反过来有一个__name__
,还有一个__bases__
序列,提供了超类的访问。 - 内置的
object.__dict__
属性提供了一个字典,以便每个属性都附加到一个命名控件对象。
from person import Person
bob = Person('Bob Smith')
print(bob)
[Person: Bob Smith, 0]
bob.__class__ # 显示 bob 的类和名称
person.Person
bob.__class__.__name__
'Person'
list(bob.__dict__.keys())
['name', 'job', 'pay']
6.2 一种通用显示工具
# classtools.py 文件
"Assorted class utilities and tools"
class AttrDisplay:
"""
Provides an inheritable display overload method that shows
instances with their class names and a name=value pair for
each attribute stored on the instance itself (but not attrs
inherited from its classes). Can be mixed into any class,
and will work on any instance.
"""
def gatherAttrs(self):
attrs = []
for key in sorted(self.__dict__):
attrs.append('%s=%s' % (key, getattr(self, key)))
return ', '.join(attrs)
def __repr__(self):
return '[%s: %s]' % (self.__class__.__name__, self.gatherAttrs())
if __name__ == '__main__':
class TopTest(AttrDisplay):
count = 0
def __init__(self):
self.attr1 = TopTest.count
self.attr2 = TopTest.count+1
TopTest.count += 2
class SubTest(TopTest):
pass
X, Y = TopTest(), SubTest()
print(X) # 显示所有实例属性
print(Y)
[TopTest: attr1=0, attr2=1]
[SubTest: attr1=2, attr2=3]
这里定义的 __repr__
显示了实例的类,及其所有的属性名和值,按照属性名排序。
6.3 类的最终形式
from classtools import AttrDisplay
class Person(AttrDisplay):
"""
Create and process person records
"""
def __init__(self, name, job=None, pay=0):
self.name = name
self.job = job
self.pay = pay
def lastName(self):
return self.name.split()[-1]
def giveRaise(self, percent):
self.pay = int(self.pay * (1 + percent))
class Manager(Person):
"""
A customized Person with special requirements
"""
def __init__(self, name, pay):
Person.__init__(self, name, 'mgr', pay)
def giveRaise(self, percent, bonus=.10):
Person.giveRaise(self, percent + bonus)
if __name__ == '__main__':
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='dev', pay=100000)
print(bob)
print(sue)
print(bob.lastName(), sue.lastName())
sue.giveRaise(.10)
print(sue)
tom = Manager('Tom Jones', 50000)
tom.giveRaise(.10)
print(tom.lastName())
print(tom)
[Person: job=None, name=Bob Smith, pay=0]
[Person: job=dev, name=Sue Jones, pay=100000]
Smith Jones
[Person: job=dev, name=Sue Jones, pay=110000]
Jones
[Manager: job=mgr, name=Tom Jones, pay=60000]
7. 步骤7:把对象存储到数据库中
尽管类按照计划工作,但它们创建的对象还不是真正的数据库记录,如果关闭 Python,实例也将消失。Python 的一项对象持久化功能能让对象在创建它们的程序退出之后依然存在。
7.1 Pickle 和 Shelve
对象持久化通过三个标准的库模块来实现:
- **pickle。**任意 Python 对象和字节字符串之间的序列化
- **dbm。**实现一个可通过键访问的文件系统,以存储字符串
- **shelve。**使用另两个模块按照键把 Python 对象存储到一个文件中
shelve 使用 pickle 把一个对象转换为其 pickle 化的字符串,并将其存储在一个 dbm 文件中的键之下;随后载入的时候,shelve 通过键获取 pickle 化的字符串,并用 pickle 在内存中重新创建最初的对象。
7.2 在 shelve 数据库中存储对象
编写 makedb.py 文件:
# makedb.py 文件
from person import Person, Manager
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='dev', pay=100000)
tom = Manager('Tom Jones', 50000)
import shelve
db = shelve.open('persondb') # 对象保存的文件名
for obj in (bob, sue, tom): # 使用对象名称属性作为键值
db[obj.name] = obj # 通过键在 shelve 上保存对象
db.close() # 关闭
7.3 交互地探索 shelve
此时,当前目录下会有多个文件,名字都以“persondb”开头。
# 目录展示模块:确认文件是否存在
import glob
glob.glob('person*')
['person.py', 'persondb.bak', 'persondb.dat', 'persondb.dir']
print(open('persondb.dir').read())
'Bob Smith', (0, 80)
'Sue Jones', (512, 92)
'Tom Jones', (1024, 91)
print(open('persondb.dat', 'rb').read())
b'\x80\x03cperson\nPerson\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\t\x00\x00\x00Bob Smithq\x04X\x03\x00\x00\x00jobq\x05NX\x03\x00\x00\x00payq\x06K\x00ub.\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x03cperson\nPerson\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\t\x00\x00\x00Sue Jonesq\x04X\x03\x00\x00\x00jobq\x05X\x03\x00\x00\x00devq\x06X\x03\x00\x00\x00payq\x07J\xa0\x86\x01\x00ub.\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x03cperson\nManager\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\t\x00\x00\x00Tom Jonesq\x04X\x03\x00\x00\x00jobq\x05X\x03\x00\x00\x00mgrq\x06X\x03\x00\x00\x00payq\x07MP\xc3ub.'
import shelve
db = shelve.open('persondb') # 重新打开 shelve
len(db) # 保存 3 记录
3
list(db.keys())
['Bob Smith', 'Sue Jones', 'Tom Jones']
bob = db['Bob Smith']
print(bob)
[Person: job=None, name=Bob Smith, pay=0]
bob.lastName()
'Smith'
for key in db:
print(key, '=>', db[key])
Bob Smith => [Person: job=None, name=Bob Smith, pay=0]
Sue Jones => [Person: job=dev, name=Sue Jones, pay=100000]
Tom Jones => [Manager: job=mgr, name=Tom Jones, pay=50000]
为了载入或使用存储的对象,不一定必须导入 Person 或 Manager 类。Python 对一个类实例进行 pickle 操作,它记录了其 self 实例属性,以及实例所创建的类的名字和类的位置。当随后从 shelve 中获取 bob 并对其 unpickle 的时候,Python 将自动地重新导入该类并且将 bob 连接到它。只有在创建新实例的时候,才必须导入自己的类。
7.4 更新 shelve 中的对象
在每次运行的时候更新一个实例。
# updatedb.py 文件
import shelve
db = shelve.open('persondb')
for key in sorted(db):
print(key, '\t=>', db[key])
sue = db['Sue Jones']
sue.giveRaise(.10)
db['Sue Jones'] = sue
db.close()
! python updatedb.py
Bob Smith => [Person: job=None, name=Bob Smith, pay=0]
Sue Jones => [Person: job=dev, name=Sue Jones, pay=110000]
Tom Jones => [Manager: job=mgr, name=Tom Jones, pay=50000]
! python updatedb.py
Bob Smith => [Person: job=None, name=Bob Smith, pay=0]
Sue Jones => [Person: job=dev, name=Sue Jones, pay=121000]
Tom Jones => [Manager: job=mgr, name=Tom Jones, pay=50000]
! python updatedb.py
Bob Smith => [Person: job=None, name=Bob Smith, pay=0]
Sue Jones => [Person: job=dev, name=Sue Jones, pay=133100]
Tom Jones => [Manager: job=mgr, name=Tom Jones, pay=50000]
! python updatedb.py
Bob Smith => [Person: job=None, name=Bob Smith, pay=0]
Sue Jones => [Person: job=dev, name=Sue Jones, pay=146410]
Tom Jones => [Manager: job=mgr, name=Tom Jones, pay=50000]