9.13 使用元类控制实例的创建¶
问题¶
ä½ æƒ³é€šè¿‡æ”¹å�˜å®žä¾‹åˆ›å»ºæ–¹å¼�æ�¥å®žçްå�•例ã€�ç¼“å˜æˆ–其他类似的特性。
解决方案¶
Python程åº�员都知é�“ï¼Œå¦‚æžœä½ å®šä¹‰äº†ä¸€ä¸ªç±»ï¼Œå°±èƒ½åƒ�å‡½æ•°ä¸€æ ·çš„è°ƒç”¨å®ƒæ�¥åˆ›å»ºå®žä¾‹ï¼Œä¾‹å¦‚:
class Spam:
def __init__(self, name):
self.name = name
a = Spam('Guido')
b = Spam('Diana')
å¦‚æžœä½ æƒ³è‡ªå®šä¹‰è¿™ä¸ªæ¥éª¤ï¼Œä½ å�¯ä»¥å®šä¹‰ä¸€ä¸ªå…ƒç±»å¹¶è‡ªå·±å®žçް __call__() 方法。
为了演示,å�‡è®¾ä½ ä¸�想任何人创建这个类的实例:
class NoInstances(type):
def __call__(self, *args, **kwargs):
raise TypeError("Can't instantiate directly")
# Example
class Spam(metaclass=NoInstances):
@staticmethod
def grok(x):
print('Spam.grok')
è¿™æ ·çš„è¯�,用户å�ªèƒ½è°ƒç”¨è¿™ä¸ªç±»çš„é�™æ€�方法,而ä¸�能使用通常的方法æ�¥åˆ›å»ºå®ƒçš„实例。例如:
>>> Spam.grok(42)
Spam.grok
>>> s = Spam()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "example1.py", line 7, in __call__
raise TypeError("Can't instantiate directly")
TypeError: Can't instantiate directly
>>>
现在,å�‡å¦‚ä½ æƒ³å®žçŽ°å�•例模å¼�(å�ªèƒ½åˆ›å»ºå”¯ä¸€å®žä¾‹çš„类),实现起æ�¥ä¹Ÿå¾ˆç®€å�•:
class Singleton(type):
def __init__(self, *args, **kwargs):
self.__instance = None
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
self.__instance = super().__call__(*args, **kwargs)
return self.__instance
else:
return self.__instance
# Example
class Spam(metaclass=Singleton):
def __init__(self):
print('Creating Spam')
那么Spam类就�能创建唯一的实例了,演示如下:
>>> a = Spam()
Creating Spam
>>> b = Spam()
>>> a is b
True
>>> c = Spam()
>>> a is c
True
>>>
最å�Žï¼Œå�‡è®¾ä½ 想创建8.25å°�节ä¸é‚£æ ·çš„缓å˜å®žä¾‹ã€‚下é�¢æˆ‘们å�¯ä»¥é€šè¿‡å…ƒç±»æ�¥å®žçŽ°ï¼š
import weakref
class Cached(type):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__cache = weakref.WeakValueDictionary()
def __call__(self, *args):
if args in self.__cache:
return self.__cache[args]
else:
obj = super().__call__(*args)
self.__cache[args] = obj
return obj
# Example
class Spam(metaclass=Cached):
def __init__(self, name):
print('Creating Spam({!r})'.format(name))
self.name = name
然�我也�测试一下:
>>> a = Spam('Guido')
Creating Spam('Guido')
>>> b = Spam('Diana')
Creating Spam('Diana')
>>> c = Spam('Guido') # Cached
>>> a is b
False
>>> a is c # Cached value returned
True
>>>
讨论¶
利用元类实现多�实例创建模�通常�比�使用元类的方�优雅得多。
å�‡è®¾ä½ ä¸�ä½¿ç”¨å…ƒç±»ï¼Œä½ å�¯èƒ½éœ€è¦�将类éš�è—�在æŸ�些工厂函数å�Žé�¢ã€‚ 比如为了实现一个å�•ä¾‹ï¼Œä½ ä½ å�¯èƒ½ä¼šåƒ�下é�¢è¿™æ ·å†™ï¼š
class _Spam:
def __init__(self):
print('Creating Spam')
_spam_instance = None
def Spam():
global _spam_instance
if _spam_instance is not None:
return _spam_instance
else:
_spam_instance = _Spam()
return _spam_instance
尽管使用元类å�¯èƒ½ä¼šæ¶‰å�Šåˆ°æ¯”较高级点的技术,但是它的代ç �看起æ�¥ä¼šæ›´åŠ ç®€æ´�舒æœ�ï¼Œè€Œä¸”ä¹Ÿæ›´åŠ ç›´è§‚ã€‚
更多关于创建缓å˜å®žä¾‹ã€�弱引用ç‰å†…容,请å�‚考8.25å°�节。