Skip to content

Instantly share code, notes, and snippets.

@LiutongZhou
Last active August 17, 2023 01:59
Show Gist options
  • Save LiutongZhou/802423872e02e5cafa146a492e668b6b to your computer and use it in GitHub Desktop.
Save LiutongZhou/802423872e02e5cafa146a492e668b6b to your computer and use it in GitHub Desktop.
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