# -*- coding: utf-8 -*-
from functools import wraps
import threading
from typing import Any, Dict, Type, TypeVar

T = TypeVar('T')


class SingletonMeta(type):
    _instances = {}
    _lock = threading.Lock()

    def __call__(cls, *args, **kwargs):
        with cls._lock:
            if cls not in cls._instances:
                cls._instances[cls] = super().__call__(*args, **kwargs)
            return cls._instances[cls]


def singleton(cls):
    instances: Dict[Type[T], T] = {}
    lock = threading.Lock()

    @wraps(cls)
    def get_instance(*args: Any, **kwargs: Any) -> T:
        nonlocal instances
        nonlocal lock
        with lock:
            if cls not in instances:
                instances[cls] = cls(*args, **kwargs)
            return instances[cls]

    return get_instance
