W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
在創(chuàng)建一個類的對象時,如果之前使用同樣參數(shù)創(chuàng)建過這個對象, 你想返回它的緩存引用。
這種通常是因為你希望相同參數(shù)創(chuàng)建的對象時單例的。在很多庫中都有實(shí)際的例子,比如 logging
模塊,使用相同的名稱創(chuàng)建的 logger
實(shí)例永遠(yuǎn)只有一個。例如:
>>> import logging
>>> a = logging.getLogger('foo')
>>> b = logging.getLogger('bar')
>>> a is b
False
>>> c = logging.getLogger('foo')
>>> a is c
True
>>>
為了達(dá)到這樣的效果,你需要使用一個和類本身分開的工廠函數(shù),例如:
# The class in question
class Spam:
def __init__(self, name):
self.name = name
# Caching support
import weakref
_spam_cache = weakref.WeakValueDictionary()
def get_spam(name):
if name not in _spam_cache:
s = Spam(name)
_spam_cache[name] = s
else:
s = _spam_cache[name]
return s
然后做一個測試,你會發(fā)現(xiàn)跟之前那個日志對象的創(chuàng)建行為是一致的:
>>> a = get_spam('foo')
>>> b = get_spam('bar')
>>> a is b
False
>>> c = get_spam('foo')
>>> a is c
True
>>>
編寫一個工廠函數(shù)來修改普通的實(shí)例創(chuàng)建行為通常是一個比較簡單的方法。但是我們還能否找到更優(yōu)雅的解決方案呢?
例如,你可能會考慮重新定義類的 __new__()
方法,就像下面這樣:
# Note: This code doesn't quite work
import weakref
class Spam:
_spam_cache = weakref.WeakValueDictionary()
def __new__(cls, name):
if name in cls._spam_cache:
return cls._spam_cache[name]
else:
self = super().__new__(cls)
cls._spam_cache[name] = self
return self
def __init__(self, name):
print('Initializing Spam')
self.name = name
初看起來好像可以達(dá)到預(yù)期效果,但是問題是 __init__()
每次都會被調(diào)用,不管這個實(shí)例是否被緩存了。例如:
>>> s = Spam('Dave')
Initializing Spam
>>> t = Spam('Dave')
Initializing Spam
>>> s is t
True
>>>
這個或許不是你想要的效果,因此這種方法并不可取。
上面我們使用到了弱引用計數(shù),對于垃圾回收來講是很有幫助的,關(guān)于這個我們在8.23小節(jié)已經(jīng)講過了。當(dāng)我們保持實(shí)例緩存時,你可能只想在程序中使用到它們時才保存。一個 WeakValueDictionary
實(shí)例只會保存那些在其它地方還在被使用的實(shí)例。否則的話,只要實(shí)例不再被使用了,它就從字典中被移除了。觀察下下面的測試結(jié)果:
>>> a = get_spam('foo')
>>> b = get_spam('bar')
>>> c = get_spam('foo')
>>> list(_spam_cache)
['foo', 'bar']
>>> del a
>>> del c
>>> list(_spam_cache)
['bar']
>>> del b
>>> list(_spam_cache)
[]
>>>
對于大部分程序而已,這里代碼已經(jīng)夠用了。不過還是有一些更高級的實(shí)現(xiàn)值得了解下。
首先是這里使用到了一個全局變量,并且工廠函數(shù)跟類放在一塊。我們可以通過將緩存代碼放到一個單獨(dú)的緩存管理器中:
import weakref
class CachedSpamManager:
def __init__(self):
self._cache = weakref.WeakValueDictionary()
def get_spam(self, name):
if name not in self._cache:
s = Spam(name)
self._cache[name] = s
else:
s = self._cache[name]
return s
def clear(self):
self._cache.clear()
class Spam:
manager = CachedSpamManager()
def __init__(self, name):
self.name = name
def get_spam(name):
return Spam.manager.get_spam(name)
這樣的話代碼更清晰,并且也更靈活,我們可以增加更多的緩存管理機(jī)制,只需要替代manager即可。
還有一點(diǎn)就是,我們暴露了類的實(shí)例化給用戶,用戶很容易去直接實(shí)例化這個類,而不是使用工廠方法,如:
>>> a = Spam('foo')
>>> b = Spam('foo')
>>> a is b
False
>>>
有幾種方式可以防止用戶這樣做,第一個是將類的名字修改為以下劃線(_)開頭,提示用戶別直接調(diào)用它。第二種就是讓這個類的 __init__()
方法拋出一個異常,讓它不能被初始化:
class Spam:
def __init__(self, *args, **kwargs):
raise RuntimeError("Can't instantiate directly")
# Alternate constructor
@classmethod
def _new(cls, name):
self = cls.__new__(cls)
self.name = name
然后修改緩存管理器代碼,使用 Spam._new()
來創(chuàng)建實(shí)例,而不是直接調(diào)用 Spam()
構(gòu)造函數(shù):
# ------------------------最后的修正方案------------------------
class CachedSpamManager2:
def __init__(self):
self._cache = weakref.WeakValueDictionary()
def get_spam(self, name):
if name not in self._cache:
temp = Spam3._new(name) # Modified creation
self._cache[name] = temp
else:
temp = self._cache[name]
return temp
def clear(self):
self._cache.clear()
class Spam3:
def __init__(self, *args, **kwargs):
raise RuntimeError("Can't instantiate directly")
# Alternate constructor
@classmethod
def _new(cls, name):
self = cls.__new__(cls)
self.name = name
return self
最后這樣的方案就已經(jīng)足夠好了。緩存和其他構(gòu)造模式還可以使用9.13小節(jié)中的元類實(shí)現(xiàn)的更優(yōu)雅一點(diǎn)(使用了更高級的技術(shù))。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: