============================ 8.25 创建缓å˜å®žä¾‹ ============================ ---------- 问题 ---------- 在创建一个类的对象时,如果之å‰ä½¿ç”¨åŒæ ·å‚数创建过这个对象, ä½ æƒ³è¿”å›žå®ƒçš„ç¼“å˜å¼•用。 ---------- 解决方案 ---------- è¿™ç§é€šå¸¸æ˜¯å› ä¸ºä½ å¸Œæœ›ç›¸åŒå‚数创建的对象是å•例的。 在很多库ä¸éƒ½æœ‰å®žé™…的例å,比如 ``logging`` 模å—,使用相åŒçš„å称创建的 ``logger`` å®žä¾‹æ°¸è¿œåªæœ‰ä¸€ä¸ªã€‚例如: .. code-block:: python >>> import logging >>> a = logging.getLogger('foo') >>> b = logging.getLogger('bar') >>> a is b False >>> c = logging.getLogger('foo') >>> a is c True >>> ä¸ºäº†è¾¾åˆ°è¿™æ ·çš„æ•ˆæžœï¼Œä½ éœ€è¦ä½¿ç”¨ä¸€ä¸ªå’Œç±»æœ¬èº«åˆ†å¼€çš„工厂函数,例如: .. code-block:: python # 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 ç„¶åŽåšä¸€ä¸ªæµ‹è¯•ï¼Œä½ ä¼šå‘现跟之å‰é‚£ä¸ªæ—¥å¿—对象的创建行为是一致的: .. code-block:: python >>> a = get_spam('foo') >>> b = get_spam('bar') >>> a is b False >>> c = get_spam('foo') >>> a is c True >>> ---------- 讨论 ---------- 编写一个工厂函数æ¥ä¿®æ”¹æ™®é€šçš„实例创建行为通常是一个比较简å•的方法。 ä½†æ˜¯æˆ‘ä»¬è¿˜èƒ½å¦æ‰¾åˆ°æ›´ä¼˜é›…的解决方案呢? ä¾‹å¦‚ï¼Œä½ å¯èƒ½ä¼šè€ƒè™‘釿–°å®šä¹‰ç±»çš„ ``__new__()`` 方法,就åƒä¸‹é¢è¿™æ ·ï¼š .. code-block:: python # 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 åˆçœ‹èµ·æ¥å¥½åƒå¯ä»¥è¾¾åˆ°é¢„期效果,但是问题是 ``__init__()`` æ¯æ¬¡éƒ½ä¼šè¢«è°ƒç”¨ï¼Œä¸ç®¡è¿™ä¸ªå®žä¾‹æ˜¯å¦è¢«ç¼“å˜äº†ã€‚例如: .. code-block:: python >>> s = Spam('Dave') Initializing Spam >>> t = Spam('Dave') Initializing Spam >>> s is t True >>> è¿™ä¸ªæˆ–è®¸ä¸æ˜¯ä½ 想è¦çš„æ•ˆæžœï¼Œå› æ¤è¿™ç§æ–¹æ³•å¹¶ä¸å¯å–。 ä¸Šé¢æˆ‘们使用到了弱引用计数,对于垃圾回收æ¥è®²æ˜¯å¾ˆæœ‰å¸®åŠ©çš„ï¼Œå…³äºŽè¿™ä¸ªæˆ‘ä»¬åœ¨8.23å°èЂ已ç»è®²è¿‡äº†ã€‚ å½“æˆ‘ä»¬ä¿æŒå®žä¾‹ç¼“å˜æ—¶ï¼Œä½ å¯èƒ½åªæƒ³åœ¨ç¨‹åºä¸ä½¿ç”¨åˆ°å®ƒä»¬æ—¶æ‰ä¿å˜ã€‚ 一个 ``WeakValueDictionary`` 实例åªä¼šä¿å˜é‚£äº›åœ¨å…¶å®ƒåœ°æ–¹è¿˜åœ¨è¢«ä½¿ç”¨çš„实例。 å¦åˆ™çš„è¯ï¼Œåªè¦å®žä¾‹ä¸å†è¢«ä½¿ç”¨äº†ï¼Œå®ƒå°±ä»Žå—å…¸ä¸è¢«ç§»é™¤äº†ã€‚观察下下é¢çš„æµ‹è¯•结果: .. code-block:: python >>> 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) [] >>> 对于大部分程åºè€Œå·²ï¼Œè¿™é‡Œä»£ç å·²ç»å¤Ÿç”¨äº†ã€‚ä¸è¿‡è¿˜æ˜¯æœ‰ä¸€äº›æ›´é«˜çº§çš„实现值得了解下。 首先是这里使用到了一个全局å˜é‡ï¼Œå¹¶ä¸”工厂函数跟类放在一å—。我们å¯ä»¥é€šè¿‡å°†ç¼“å˜ä»£ç 放到一个å•独的缓å˜ç®¡ç†å™¨ä¸ï¼š .. code-block:: python 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) è¿™æ ·çš„è¯ä»£ç æ›´æ¸…æ™°ï¼Œå¹¶ä¸”ä¹Ÿæ›´çµæ´»ï¼Œæˆ‘们å¯ä»¥å¢žåŠ æ›´å¤šçš„ç¼“å˜ç®¡ç†æœºåˆ¶ï¼Œåªéœ€è¦æ›¿ä»£managerå³å¯ã€‚ è¿˜æœ‰ä¸€ç‚¹å°±æ˜¯ï¼Œæˆ‘ä»¬æš´éœ²äº†ç±»çš„å®žä¾‹åŒ–ç»™ç”¨æˆ·ï¼Œç”¨æˆ·å¾ˆå®¹æ˜“åŽ»ç›´æŽ¥å®žä¾‹åŒ–è¿™ä¸ªç±»ï¼Œè€Œä¸æ˜¯ä½¿ç”¨å·¥åŽ‚æ–¹æ³•ï¼Œå¦‚ï¼š .. code-block:: python >>> a = Spam('foo') >>> b = Spam('foo') >>> a is b False >>> æœ‰å‡ ç§æ–¹å¼å¯ä»¥é˜²æ¢ç”¨æˆ·è¿™æ ·åšï¼Œç¬¬ä¸€ä¸ªæ˜¯å°†ç±»çš„åå—修改为以下划线(_)开头,æç¤ºç”¨æˆ·åˆ«ç›´æŽ¥è°ƒç”¨å®ƒã€‚ 第二ç§å°±æ˜¯è®©è¿™ä¸ªç±»çš„ ``__init__()`` 方法抛出一个异常,让它ä¸èƒ½è¢«åˆå§‹åŒ–: .. code-block:: python 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()`` æ¥åˆ›å»ºå®žä¾‹ï¼Œè€Œä¸æ˜¯ç›´æŽ¥è°ƒç”¨ ``Spam()`` æž„é€ å‡½æ•°ï¼š .. code-block:: python # ------------------------最åŽçš„ä¿®æ£æ–¹æ¡ˆ------------------------ 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 最åŽè¿™æ ·çš„æ–¹æ¡ˆå°±å·²ç»è¶³å¤Ÿå¥½äº†ã€‚ 缓å˜å’Œå…¶ä»–æž„é€ æ¨¡å¼è¿˜å¯ä»¥ä½¿ç”¨9.13å°èŠ‚ä¸çš„元类实现的更优雅一点(使用了更高级的技术)。