2.6 实现类和对象
在面向对象编程范式(object-oriented programming paradigm)下工作时,我们用对象隐喻来指导程序的组织。如何表示和操作数据,大部分逻辑都在类声明中表达。本节会看到,类和对象本身只用函数和字典就能表示。以这种方式实现对象系统,是为了说明:使用对象隐喻并不需要一门特殊的编程语言。即使编程语言没有内置对象系统,程序一样可以是面向对象的。
要实现对象,我们会放弃点表示法(它确实需要语言内置支持),转而创建分派字典,让它们的行为与内置对象系统的元素非常相似。前面已经见过如何用分派字典实现消息传递。要完整实现一个对象系统,我们会在实例、类和基类之间发送消息,而它们都是包含属性的字典。
我们不会实现完整的 Python 对象系统——它包含本书没有涉及的功能(比如元类(meta-class)和静态方法)。我们只关注用户定义的类,不涉及多重继承,也不涉及内省行为(比如返回实例所属的类)。我们的实现并不打算遵循 Python 类型系统的精确规范,而是要实现支撑对象隐喻的核心功能。
2.6.1 实例
先从实例开始。实例有具名属性,比如一个账户的余额,可以设置,也可以读取。我们用一个分派字典实现实例,它能响应「get」和「set」属性值的消息。属性本身存放在一个名为 attributes 的局部字典里。
本章前面已经讲过,字典本身就是抽象数据类型。我们用列表实现字典,用「对」实现列表,再用函数实现「对」。既然用字典实现对象系统,那就别忘了,我们同样可以只靠函数来实现对象。
开始实现之前,先假定已经有一个类的实现,它能查找任何不属于实例的名称。我们把一个类传给 make_instance,作为形参 cls。
>>> def make_instance(cls):
"""返回一个新的对象实例,它是一个分派字典。"""
def get_value(name):
if name in attributes:
return attributes[name]
else:
value = cls['get'](name)
return bind_method(value, instance)
def set_value(name, value):
attributes[name] = value
attributes = {}
instance = {'get': get_value, 'set': set_value}
return instanceinstance 是一个分派字典,能响应 get 和 set 消息。set 消息对应 Python 对象系统里的属性赋值:所有被赋值的属性都直接存进对象本地的属性字典。处理 get 消息时,如果 name 不在本地的 attributes 字典里,就到类里查找。如果类返回的值是一个函数,就必须把它绑定到实例上。
绑定方法值。 make_instance 里的 get_value 函数用 get 消息在类中查找具名属性,然后调用 bind_method。方法绑定只对函数值生效:它把 instance 作为第一个参数插入函数值,从而由函数值创建出绑定方法值:
>>> def bind_method(value, instance):
"""如果 value 可调用,就返回绑定方法,否则返回 value。"""
if callable(value):
def method(*args):
return value(instance, *args)
return method
else:
return value按照这个定义,方法被调用时,第一个形参 self 会绑定到 instance 的值。
2.6.2 类
不论是在 Python 的对象系统里,还是在我们自己实现的系统中,类本身也是一个对象。为简单起见,我们说类本身没有类。(在 Python 中,类确实有类;几乎所有类都共享同一个类,叫做 type。)一个类能响应 get 和 set 消息,也能响应 new 消息:
>>> def make_class(attributes, base_class=None):
"""返回一个新的类,它是一个分派字典。"""
def get_value(name):
if name in attributes:
return attributes[name]
elif base_class is not None:
return base_class['get'](name)
def set_value(name, value):
attributes[name] = value
def new(*args):
return init_instance(cls, *args)
cls = {'get': get_value, 'set': set_value, 'new': new}
return cls和实例不同,类的 get 函数在找不到属性时不会去查它的类,而是查它的基类(base_class)。类不需要绑定方法。
初始化。 make_class 里的 new 函数会调用 init_instance,后者先创建一个新实例,再调用一个名为 __init__ 的方法。
>>> def init_instance(cls, *args):
"""返回一个类型为 cls、并用 args 初始化的新对象。"""
instance = make_instance(cls)
init = cls['get']('__init__')
if init:
init(instance, *args)
return instance最后一个函数补全了整个对象系统。现在有了实例:处理 set 时在本地设置,处理 get 时则回退到类中。实例在类里查到一个名称后,会把自己绑定到函数值上,从而创建方法。最后,类可以创建新实例,并在实例创建后立即调用它们的 __init__ 构造函数。
在这个对象系统中,用户唯一需要调用的函数是 make_class,其他功能都靠消息传递实现。同样,Python 的对象系统通过 class 语句启用,其他功能都靠点表达式和对类的调用实现。
2.6.3 使用已实现的对象
现在回到上一节的银行账户例子。用我们自己实现的对象系统,创建 Account 类、CheckingAccount 子类,并分别创建各自的实例。
Account 类由 make_account_class 函数创建,它的结构与 Python 中的 class 语句相似,只是最后会调用 make_class。
>>> def make_account_class():
"""返回 Account 类,它有 deposit 和 withdraw 方法。"""
interest = 0.02
def __init__(self, account_holder):
self['set']('holder', account_holder)
self['set']('balance', 0)
def deposit(self, amount):
"""把账户余额增加 amount,并返回新余额。"""
new_balance = self['get']('balance') + amount
self['set']('balance', new_balance)
return self['get']('balance')
def withdraw(self, amount):
"""把账户余额减少 amount,并返回新余额。"""
balance = self['get']('balance')
if amount > balance:
return 'Insufficient funds'
self['set']('balance', balance - amount)
return self['get']('balance')
return make_class(locals())最后对 locals 的调用返回一个以字符串为键的字典,其中包含当前局部帧里的名称-值绑定。
Account 类最终通过赋值实例化。
>>> Account = make_account_class()然后通过 new 消息创建一个账户实例,创建时需要给新账户一个名称。
>>> kirk_account = Account['new']('kirk')之后向 kirk_account 发送 get 消息,就能取出属性和方法。调用方法即可更新账户余额。
>>> kirk_account['get']('holder')
'kirk'
>>> kirk_account['get']('interest')
0.02
>>> kirk_account['get']('deposit')(20)
20
>>> kirk_account['get']('withdraw')(5)
15和 Python 对象系统一样,设置实例的属性,不会改变它所属类中对应的属性。
>>> kirk_account['set']('interest', 0.04)
>>> Account['get']('interest')
0.02继承。 重载类的一部分属性,就能创建子类 CheckingAccount。这里我们改写 withdraw 方法来收取费用,同时降低利率。
>>> def make_checking_account_class():
"""返回 CheckingAccount 类,它在取款时收取 1 美元手续费。"""
interest = 0.01
withdraw_fee = 1
def withdraw(self, amount):
fee = self['get']('withdraw_fee')
return Account['get']('withdraw')(self, amount + fee)
return make_class(locals(), Account)这个实现里,我们从子类的 withdraw 函数调用基类 Account 的 withdraw 函数,就像在 Python 内置对象系统里那样。和之前一样,我们可以创建子类本身,再创建一个实例。
>>> CheckingAccount = make_checking_account_class()
>>> jack_acct = CheckingAccount['new']('Spock')存款行为完全一样,构造函数也一样。取款由专门的 withdraw 方法收取 1 美元的费用,interest 则取 CheckingAccount 里新的较低值。
>>> jack_acct['get']('interest')
0.01
>>> jack_acct['get']('deposit')(20)
20
>>> jack_acct['get']('withdraw')(5)
14我们基于字典构建的对象系统,在实现上和 Python 内置的对象系统非常相似。在 Python 中,任何用户定义类的实例都有一个特殊属性 __dict__,它把该对象的局部实例属性存进一个字典,很像我们的 attributes 字典。Python 的不同之处在于,它区分了一些特殊方法,这些方法与内置函数交互,确保这些函数对许多不同类型的参数都能正确运行。操作不同类型的函数,是下一节的主题。