9.13 使用元类控制实例的创建

    Python程序员都知道,如果你定义了一个类,就能像函数一样的调用它来创建实例,例如:

    如果你想自定义这个步骤,你可以定义一个元类并自己实现 方法。

    为了演示,假设你不想任何人创建这个类的实例:

    1. class NoInstances(type):
    2. def __call__(self, *args, **kwargs):
    3. raise TypeError("Can't instantiate directly")
    4.  
    5. # Example
    6. class Spam(metaclass=NoInstances):
    7. @staticmethod
    8. def grok(x):
    9. print('Spam.grok')

    这样的话,用户只能调用这个类的静态方法,而不能使用通常的方法来创建它的实例。例如:

    1. class Singleton(type):
    2. def __init__(self, *args, **kwargs):
    3. self.__instance = None
    4. super().__init__(*args, **kwargs)
    5.  
    6. def __call__(self, *args, **kwargs):
    7. if self.__instance is None:
    8. self.__instance = super().__call__(*args, **kwargs)
    9. else:
    10. return self.__instance
    11.  
    12. # Example
    13. class Spam(metaclass=Singleton):
    14. def __init__(self):
    15. print('Creating Spam')

    那么Spam类就只能创建唯一的实例了,演示如下:

    最后,假设你想创建8.25小节中那样的缓存实例。下面我们可以通过元类来实现:

    1. import weakref
    2.  
    3. class Cached(type):
    4. def __init__(self, *args, **kwargs):
    5. super().__init__(*args, **kwargs)
    6. self.__cache = weakref.WeakValueDictionary()
    7.  
    8. def __call__(self, *args):
    9. if args in self.__cache:
    10. return self.__cache[args]
    11. else:
    12. obj = super().__call__(*args)
    13. self.__cache[args] = obj
    14.  
    15. # Example
    16. class Spam(metaclass=Cached):
    17. def __init__(self, name):
    18. print('Creating Spam({!r})'.format(name))
    19. self.name = name

    然后我也来测试一下:

    利用元类实现多种实例创建模式通常要比不使用元类的方式优雅得多。

    1. class _Spam:
    2. def __init__(self):
    3. print('Creating Spam')
    4.  
    5. _spam_instance = None
    6.  
    7. def Spam():
    8. global _spam_instance
    9.  
    10. if _spam_instance is not None:
    11. return _spam_instance
    12. else:
    13. _spam_instance = _Spam()
    14. return _spam_instance

    尽管使用元类可能会涉及到比较高级点的技术,但是它的代码看起来会更加简洁舒服,而且也更加直观。

    更多关于创建缓存实例、弱引用等内容,请参考8.25小节。

    原文: