Last active
August 17, 2023 01:59
-
-
Save LiutongZhou/802423872e02e5cafa146a492e668b6b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import functools | |
from typing import Any, Mapping | |
def singleton(cls): | |
'''a class decorator that wraps class definition so that only one class instance can exist''' | |
existing_instances = dict() | |
@functools.wraps(cls) | |
def singleton_class(*args, **kwargs): | |
if cls not in existing_instances: | |
existing_instances[cls] = cls(*args,**kwargs) | |
return existing_instances[cls] | |
return singleton_class | |
class SingletonClass(type): | |
"""A single instance of a class derived from this metaclass can exist at all times | |
An instance will be uniquely identified by its call parameters on instantiation | |
When you define a class with class MyClass(metaclass=SingletonClass), python will call | |
type.__call__(class_name, bases, namespace) in which __prepare__, __new__ and __init__ will be called sequentially | |
""" | |
@classmethod | |
def __prepare__(metacls, class_name: str, bases: tuple[type, ...], **kwds: Any) -> Mapping[str, object]: | |
"""Prepare the namespace of the class""" | |
return super().__prepare__(class_name, bases, **kwds) | |
def __new__(metacls, class_name: str, bases: tuple[type, ...], namespace: dict[str, Any]) -> Any: | |
"""Construct the class derived from this metaclass when you define a class""" | |
namespace.setdefault("_cache", dict()) # Add cache to the class attributes | |
class_ = super().__new__(metacls, class_name, bases, namespace) | |
return class_ | |
def __init__(cls, class_name: str, bases: tuple[type, ...], namespace: dict[str, Any]) -> None: | |
"""Initialize the class cls constructed from __new__""" | |
super().__init__(class_name, bases, namespace) | |
# cls._cache = {} # can also add _cache here | |
def __call__(cls, *args: Any, **kwargs: Any) -> Any: | |
"""Instantiate an instance of a derived class. instance = cls(*args, **kwargs)""" | |
# This method calls __prepare__, __new__, and __init__ methods of the child class | |
_cache: dict = cls._cache | |
key = (str(args), str(kwargs)) | |
if key not in _cache: | |
# instantiate the instance of cls | |
_cache[key] = super().__call__(*args, **kwargs) | |
return _cache[key] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment