2.6 实现类和对象
在面向对象编程范式中,我们用对象的隐喻来组织程序。多数数据表示与操作逻辑都用类声明表达。本节将展示类和对象也能用函数与字典来表示,说明对象隐喻不依赖特定语言:即便没有内置对象系统的语言,程序也可以是面向对象的。
为实现对象,我们不用点表示(需语言支持),而是创建行为类似内置对象系统的调度字典。之前已见过用调度字典实现消息传递。要完整实现对象系统,我们在实例、类、基类之间发送消息,它们都是携带属性的字典。
我们不会实现完整的 Python 对象系统(如元类、静态方法等未涉及特性)。重点放在无多重继承且无内省行为(如返回实例的类)的用户定义类上。实现目标是支持对象隐喻的核心功能,而非完全符合 Python 类型系统规范。
2.6.1 实例
从实例开始。实例有可设置和获取的具名属性,例如账户实例 account 的 balance。我们用调度字典实现实例,响应设置与获取("get"、"set")属性值的消息,属性存储在名为 attributes 的本地字典中。
如前所示,字典本身也是抽象数据类型。我们用函数实现数据对,再用数据对实现列表,再用列表实现字典。用字典实现对象系统时,要记得仅用函数也能实现对象。
开始实现前,假定已有一个类的实现,能查找实例没有的名称。类作为参数传给 make_instance 的形参 cls。
>>> def make_instance(cls):
"""Return a new object instance, which is a dispatch dictionary."""
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 通过 get_value 在类中找到属性,再调用 bind_method。只有属性值是函数才绑定:创建绑定方法时,会把 instance 作为第一个参数插入函数值。
>>> def bind_method(value, instance):
"""Return a bound method if value is callable, or value otherwise."""
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):
"""Return a new class, which is a dispatch dictionary."""
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):
"""Return a new object with type cls, initialized with 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():
"""Return the Account class, which has deposit and withdraw methods."""
interest = 0.02
def __init__(self, account_holder):
self['set']('holder', account_holder)
self['set']('balance', 0)
def deposit(self, amount):
"""Increase the account balance by amount and return the new balance."""
new_balance = self['get']('balance') + amount
self['set']('balance', new_balance)
return self['get']('balance')
def withdraw(self, amount):
"""Decrease the account balance by amount and return the new balance."""
balance = self['get']('balance')
if amount > balance:
return 'Insufficient funds'
self['set']('balance', balance - amount)
return self['get']('balance')
return make_class(locals())最后对 locals 的调用返回一个以字符串为 key 的字典,其中包含了当前局部帧的名称 - 值的绑定。
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():
"""Return the CheckingAccount class, which imposes a $1 withdrawal fee."""
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__,它将该对象的本地实例属性存储在一个字典中,就像我们的 attribute 字典一样。但 Python 与我们的系统不同之处在于,它区分了一些特殊方法,这些方法与内置函数交互,以确保这些函数对许多不同类型的参数都能正确运行。接下来的一节将详细讨论操作不同类型的函数的问题。