最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

singleton - Python subclass constructor calls metaclass with *args and **kwargs - Stack Overflow

programmeradmin0浏览0评论

I am trying to instantiate a singleton with constructor argument. However, passing the input arguments to the metaclass hits an error.

from typing_extensions import List, TypedDict, Optional, Any

class Singleton(type): # Inherit from "type" in order to gain access to method __call__
    _var: str = None
    _myObject: MyType = None
    def __init__(self, *args, **kwargs):
        print(f"Singleton::__init__")
        print(f"Singleton::__init__ len(kwargs): {len(kwargs)}, len(args): {len(args)}, args[0]: {args[0]}")
        print(**kwargs)
        for i in args:
            print(f"i: {i}")
        if "var" in kwargs and kwargs.get("var"):
            self._var = kwargs.get('var')
        elif len(args):
            self._var = args[0]
        else:
            self._var = "text-embedding-005"
        print(f"Singleton::__init__self._var: {self._var}")
        self.__instance = None # Create a variable to store the object reference
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            # if the object has not already been created
            self._myObject = MyType(self._var)
            print(f"Singleton::__call__ self._var: {self._var}")
            self.__instance = super().__call__(*args, **kwargs)
            return self.__instance
        else:
            # if object (Spam) reference already exists; return it
            return self.__instance
            
class MySingleton(metaclass=Singleton):
    """
    Class constructor
    """
    def __init__(self, *args, **kwargs):
        print(f"MySingleton::__init__")
        super().__init__(*args, **kwargs)
        print(f"MySingleton::__init__ {self._var}")

myobject = MySingleton("hello")
myobject1 = MySingleton("world")

Error:

Singleton::__init__
Singleton::__init__ len(kwargs): 0, len(args): 3, args[0]: MySingleton

i: MySingleton
i: ()
i: {'__module__': '__main__', '__qualname__': 'MySingleton', '__doc__': '\n    Class constructor\n', '__init__': <function MySingleton.__init__ at 0x7de2ca4fd800>, '__classcell__': <cell at 0x7de2ca5399f0: Singleton object at 0x12d21760>}
Singleton::__init__self._model: MySingleton
Singleton::__call__ _model: MySingleton
MySingleton::__init__
Traceback (most recent call last):
  File "singleton.py", line 43, in <module>
    vector_store = MySingleton("hello")
                   ^^^^^^^^^^^^^^^^^^^^
  File "singleton.py", line 26, in __call__
    self.__instance = super().__call__(*args, **kwargs)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "singleton.py", line 38, in __init__
    super().__init__(*args, **kwargs)
TypeError: object.__init__() takes exactly one argument (the instance to initialize)

However, even if this doesn't hit any error, I doubt the logic would work. The console output shows that both the metaclass __init__ and __call__ are called first before the MySingleton constructor. The intput parameter is used to instantiate the ultimate singleton self._myObject in __call__.

I am trying to instantiate a singleton with constructor argument. However, passing the input arguments to the metaclass hits an error.

from typing_extensions import List, TypedDict, Optional, Any

class Singleton(type): # Inherit from "type" in order to gain access to method __call__
    _var: str = None
    _myObject: MyType = None
    def __init__(self, *args, **kwargs):
        print(f"Singleton::__init__")
        print(f"Singleton::__init__ len(kwargs): {len(kwargs)}, len(args): {len(args)}, args[0]: {args[0]}")
        print(**kwargs)
        for i in args:
            print(f"i: {i}")
        if "var" in kwargs and kwargs.get("var"):
            self._var = kwargs.get('var')
        elif len(args):
            self._var = args[0]
        else:
            self._var = "text-embedding-005"
        print(f"Singleton::__init__self._var: {self._var}")
        self.__instance = None # Create a variable to store the object reference
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            # if the object has not already been created
            self._myObject = MyType(self._var)
            print(f"Singleton::__call__ self._var: {self._var}")
            self.__instance = super().__call__(*args, **kwargs)
            return self.__instance
        else:
            # if object (Spam) reference already exists; return it
            return self.__instance
            
class MySingleton(metaclass=Singleton):
    """
    Class constructor
    """
    def __init__(self, *args, **kwargs):
        print(f"MySingleton::__init__")
        super().__init__(*args, **kwargs)
        print(f"MySingleton::__init__ {self._var}")

myobject = MySingleton("hello")
myobject1 = MySingleton("world")

Error:

Singleton::__init__
Singleton::__init__ len(kwargs): 0, len(args): 3, args[0]: MySingleton

i: MySingleton
i: ()
i: {'__module__': '__main__', '__qualname__': 'MySingleton', '__doc__': '\n    Class constructor\n', '__init__': <function MySingleton.__init__ at 0x7de2ca4fd800>, '__classcell__': <cell at 0x7de2ca5399f0: Singleton object at 0x12d21760>}
Singleton::__init__self._model: MySingleton
Singleton::__call__ _model: MySingleton
MySingleton::__init__
Traceback (most recent call last):
  File "singleton.py", line 43, in <module>
    vector_store = MySingleton("hello")
                   ^^^^^^^^^^^^^^^^^^^^
  File "singleton.py", line 26, in __call__
    self.__instance = super().__call__(*args, **kwargs)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "singleton.py", line 38, in __init__
    super().__init__(*args, **kwargs)
TypeError: object.__init__() takes exactly one argument (the instance to initialize)

However, even if this doesn't hit any error, I doubt the logic would work. The console output shows that both the metaclass __init__ and __call__ are called first before the MySingleton constructor. The intput parameter is used to instantiate the ultimate singleton self._myObject in __call__.

Share Improve this question asked Mar 13 at 10:19 khtehkhteh 4,06810 gold badges59 silver badges104 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 1

For much that this is interesting, I should advise you against such mechanisms: Python is not constrained in that all objects must be classes, and the "global variable" mechanism is actually module name-spaced. That means you can just create an ordinary class, create your single instance of it as an importable name, and always import that instance instead of importing the class:


class _MySingletonClass:
    def __init__(...):
        ...

# import this instead:

MySingleton = _MySingletonClass()

# you can even delete the class to avoid
# accidental instantiation:

del _MySingletonClass

And what if your users intend to instantiate it after importing? (Although no one tries to instantiate None or True) but you can just add a __call__ method to the class which will allow for a fake instantiation:

class _MySingletonClass:
    def __init__(...):
        ...

    def __call__(self):
        return self

WIth that said, even if one is to resort to actually using a metaclass to create a singleton class, there is no need to fiddle with __init__ in the metaclass - the __call__ method in the metaclass is what runs for each instance:


class MetaSingleton():
    _registry = {}

    def __call__(cls, *args, **kwargs):
        registry = type(cls)._registry
        if cls not in registry:
              registry[cls] = super().__call__(*args, **kwargs)
        return registry[cls]

As you can see, it can be one order of magnitude simpler than what you are doing. Even the usage of Python name mangling mechanisms, with __ prefix can complicate things up in unexpected ways - try to avoid it unless really needed - that is not for creating arbitrary "hidden" attributes, it is something intended to avoid name clashes in complex inheritance hierarchies.

You could even add a mechanism to error out if one try to re-instantiate the class with different arguments than on the first call:


class MetaSingleton():
    _registry = {}

    def __call__(cls, *args, **kwargs):
        registry = type(cls)._registry
        if cls not in registry:
              registry[cls] = (super().__call__(*args, **kwargs), args, kwargs)
        elif registry(cls)[1] != args or registry(cls)[2] != kwargs:
              raise TypeError(f"Class already initialized with different arguments!")
        return registry[cls][0]

(but seriously - just create an instance and use it, as in my first example. Using metaclasses for this is way overkill)

Solution is to put all the logic in subclass:

from typing_extensions import List, TypedDict, Optional, Any

class Singleton(type): # Inherit from "type" in order to gain access to method __call__
    def __init__(self, *args, **kwargs):
        print(f"Singleton::__init__")
        print(f"Singleton::__init__ len(kwargs): {len(kwargs)}, len(args): {len(args)}, args[0]: {args[0]}")
        self.__instance = None # Create a variable to store the object reference
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            # if the object has not already been created
            self.__instance = super().__call__(*args, **kwargs)
            return self.__instance
        else:
            # if object (Spam) reference already exists; return it
            return self.__instance
            
class MySingleton(metaclass=Singleton):
    """
    Class constructor
    """
    _var: str = None
    def __init__(self, var="Hello"):
        print(f"MySingleton::__init__")
        self._var = var
        self._myObject = MyType(self._var)
        print(f"MySingleton::__init__ {self._var}")

myobject = MySingleton("hello")
myobject1 = MySingleton("world")
assert myobject is not None
assert myobject1 is not None
assert myobject == myobject1
发布评论

评论列表(0)

  1. 暂无评论