2.6 實現類和對象
譯者:飛龍
在使用面向對象編程范式時,我們使用對象隱喻來指導程序的組織。數據表示和操作的大部分邏輯都表達在類的定義中。在這一節中,我們會看到,類和對象本身可以使用函數和字典來表示。以這種方式實現對象系統的目的是展示使用對象隱喻并不需要特殊的編程語言。即使編程語言沒有面向對象系統,程序照樣可以面向對象。
為了實現對象,我們需要拋棄點運算符(它需要語言的內建支持),并創建分發字典,它的行為和內建對象系統的元素差不多。我們已經看到如何通過分發字典實現消息傳遞行為。為了完整實現對象系統,我們需要在實例、類和基類之間發送消息,它們全部都是含有屬性的字典。
我們不會實現整個 Python 對象系統,它包含這篇文章沒有涉及到的特性(比如元類和靜態方法)。我們會專注于用戶定義的類,不帶有多重繼承和內省行為(比如返回實例的類)。我們的實現并不遵循 Python 類型系統的明確規定。反之,它為實現對象隱喻的核心功能而設計。
2.6.1 實例
我們從實例開始。實例擁有具名屬性,例如賬戶余額,它可以被設置或獲取。我們使用分發字典來實現實例,它會響應“get”和“set”屬性值消息。屬性本身保存在叫做attributes
的局部字典中。
就像我們在這一章的前面看到的那樣,字典本身是抽象數據類型。我們使用列表來實現字典,我們使用偶對來實現列表,并且我們使用函數來實現偶對。就像我們以字典實現對象系統那樣,要注意我們能夠僅僅使用函數來實現對象。
為了開始我們的實現,我們假設我們擁有一個類實現,它可以查找任何不是實例部分的名稱。我們將類作為參數cls
傳遞給make_instance
。
>>> 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
instance
是分發字典,它響應消息get
和set
。set
消息對應 Python 對象系統的屬性賦值:所有賦值的屬性都直接儲存在對象的局部屬性字典中。在get
中,如果name
在局部attributes
字典中不存在,那么它會在類中尋找。如果cls
返回的value
為函數,它必須綁定到實例上。
綁定方法值。make_instance
中的get_value
使用get
尋找類中的具名屬性,之后調用bind_method
。方法的綁定只在函數值上調用,并且它會通過將實例插入為第一個參數,從函數值創建綁定方法的值。
>>> 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
會回溯到它們的類中。實例在它的類中查找名稱之后,它會將自己綁定到函數值上來創建方法。最后類可以創建新的(new
)實例,并且在實例創建之后立即調用它們的__init__
構造器。
在對象系統中,用戶僅僅可以調用create_class
,所有其他功能通過消息傳遞來使用。與之相似,Python 的對象系統由class
語句來調用,它的所有其他功能都通過點表達式和對類的調用來使用。
2.6.3 使用所實現的對象
我們現在回到上一節銀行賬戶的例子。使用我們實現的對象系統,我們就可以創建Account
類,CheckingAccount
子類和它們的實例。
Account
類通過create_account_class
函數創建,它擁有類似于 Python class
語句的結構,但是以make_class
的調用結尾。
>>> def make_account_class():
"""Return the Account class, which has deposit and withdraw methods."""
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({'__init__': __init__,
'deposit': deposit,
'withdraw': withdraw,
'interest': 0.02})
在這個函數中,屬性名稱在最后設置。不像 Python 的class
語句,它強制內部函數和屬性名稱之間的一致性。這里我們必須手動指定屬性名稱和值的對應關系。
Account
類最終由賦值來實例化。
>>> Account = make_account_class()
之后,賬戶實例通過new
消息來創建,它需要名稱來處理新創建的賬戶。
>>> jim_acct = Account['new']('Jim')
之后,get
消息傳遞給jim_acct
,來獲取屬性和方法。方法可以調用來更新賬戶余額。
>>> jim_acct['get']('holder')
'Jim'
>>> jim_acct['get']('interest')
0.02
>>> jim_acct['get']('deposit')(20)
20
>>> jim_acct['get']('withdraw')(5)
15
就像使用 Python 對象系統那樣,設置實例的屬性并不會修改類的對應屬性:
>>> jim_acct['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."""
def withdraw(self, amount):
return Account['get']('withdraw')(self, amount + 1)
return make_class({'withdraw': withdraw, 'interest': 0.01}, Account)
在這個實現中,我們在子類的withdraw
中調用了基類Account
的withdraw
函數,就像在 Python 內建對象系統那樣。我們可以創建子類本身和它的實例,就像之前那樣:
>>> CheckingAccount = make_checking_account_class()
>>> jack_acct = CheckingAccount['new']('Jack')
它們的行為相似,構造函數也一樣。每筆取款都會在特殊的withdraw
函數中收費 $1,并且interest
也擁有新的較低值。
>>> jack_acct['get']('interest')
0.01
>>> jack_acct['get']('deposit')(20)
20
>>> jack_acct['get']('withdraw')(5)
14
我們的構建在字典上的對象系統十分類似于 Python 內建對象系統的實現。Python 中,任何用戶定義類的實例,都有個特殊的__dict__
屬性,將對象的局部實例屬性儲存在字典中,就像我們的attributes
字典那樣。Python 的區別在于,它區分特定的特殊方法,這些方法和內建函數交互來確保那些函數能正常處理許多不同類型的參數。操作不同類型參數的函數是下一節的主題。