当使用面向对象编程范式时,我们使用对象隐喻来指导程序的组织。关于如何表示和操作数据的大多数逻辑都在类声明中表示。在本节中,我们看到类和对象本身可以仅使用函数和字典来表示。以这种方式实现对象系统的目的是为了说明使用对象隐喻并不需要特殊的编程语言。程序可以是面向对象的,即使在没有内置对象系统的编程语言中也是如此。
为了实现对象,我们将放弃点表示法(它确实需要内置语言支持),但创建与内置对象系统的元素行为基本相同的分派字典。我们已经了解了如何通过分派字典实现消息传递行为。为了完整地实现对象系统,我们在实例、类和基类之间发送消息,所有这些都是包含属性的字典。
我们不会实现整个Python对象系统,它包含了我们在本文中没有涉及到的特性(例如,元类和静态方法)。我们将把重点放在没有多重继承和没有内省行为(例如返回实例的类)的用户定义类上。我们的实现并不意味着要遵循Python类型系统的精确规范。相反,它旨在实现支持对象隐喻的核心功能。
2.6.1 实例
我们从实例开始。 实例具有命名属性,例如帐户余额,可以设置和检索这些属性。 我们使用一个分派字典来实现一个实例,该字典响应“get”和“set”属性值的消息。 属性本身存储在一个称为attributes的本地字典中。
正如我们在本章前面已经看到的,字典本身是抽象数据类型。 我们用列表实现了字典,用对实现了列表,用函数实现了对。 当我们根据字典来实现对象系统时,请记住,我们也可以只使用函数来实现对象。
为了开始我们的实现,我们假设我们有一个类实现,它可以查找不属于实例的任何名称。 我们将一个类作为参数cls传递给make_instance。
Copy >>> 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 instance
该实例是一个分派字典,响应get和set消息。set消息对应于Python对象系统中的属性分配:所有被分配的属性都直接存储在对象的本地属性字典中。在get中,如果name没有出现在本地属性字典中,则在类中查找它。如果cls返回的值是一个函数,则必须绑定到实例。
值绑定方法。 make_instance中的get_value函数使用get在其类中找到一个命名属性,然后调用bind_method。绑定方法只应用于函数值,它通过将实例作为第一个参数插入函数值来创建绑定方法值:
Copy >>> 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消息:
Copy >>> 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函数不会查询它的类,而是查询它的baseclass。类不需要方法绑定。
初始化。 makeclass中的新函数调用init_instance,它首先创建一个新实例,然后调用一个名为__init__的方法。
Copy >>> 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
最后这个函数完成了我们的对象系统。 我们现在有实例,它们在局部设置,但在get上返回到它们的类。 实例在其类中查找名称后,它将自己绑定到函数值以创建方法。 最后,类可以创建新的实例,并在实例创建后立即应用其__init__构造函数。
在这个对象系统中,用户应该调用的唯一函数是make_class。 所有其他功能都是通过消息传递来启用的。 类似地,Python的对象系统是通过class语句调用的,它的所有其他功能都是通过点表达式和对类的调用来启用的。
2.6.3 使用实现的对象
现在我们返回来使用上一节中的银行账户示例。使用我们实现的对象系统,我们将创建Account类、CheckingAccount子类和每个类的实例。
Account类是通过make_account_class函数创建的,该函数的结构类似于Python中的类语句,但最后调用make_class。
Copy >>> 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的最后一次调用返回一个包含字符串键的字典,该字符串键包含当前本地框架中的名称-值绑定。
Account类最终通过赋值来实例化。
Copy >>> Account = make_account_class()
然后,通过新消息创建account实例,该实例要求为新创建的帐户提供一个名称。
Copy >>> kirk_account = Account['new']('Kirk')
然后,获取传递给kirk_account的消息,检索属性和方法。方法可以被调用来更新帐户的余额。
Copy >>> kirk_account['get']('holder')
'Kirk'
>>> kirk_account['get']('interest')
0.02
>>> kirk_account['get']('deposit')(20)
20
>>> kirk_account['get']('withdraw')(5)
15
与Python对象系统一样,设置实例的属性不会改变其类的相应属性。
Copy >>> kirk_account['set']('interest', 0.04)
>>> Account['get']('interest')
0.02
继承。 我们可以通过重载类属性的子集来创建CheckingAccount子类。在本例中,我们更改取款方法以收取费用,并降低利率。
Copy >>> 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的内置对象系统中一样。我们可以像前面一样创建子类本身和一个实例。
Copy >>> CheckingAccount = make_checking_account_class()
>>> jack_acct = CheckingAccount['new']('Spock')
存款的行为与构造函数的行为相同。提现从专门的提现方式中收取1美元的费用,利息从支票账户中有新的较低的值。
Copy >>> jack_acct['get']('interest')
0.01
>>> jack_acct['get']('deposit')(20)
20
>>> jack_acct['get']('withdraw')(5)
14
我们构建在字典之上的对象系统在实现上与Python中的内置对象系统非常相似。在Python中,任何用户定义类的实例都有一个特殊的属性__dict__,它将该对象的本地实例属性存储在字典中,就像我们的属性字典一样。Python之所以不同,是因为它区分了某些与内置函数交互的特殊方法,以确保这些函数对于许多不同类型的参数都能正确地表现。操作不同类型的函数是下一节的主题。