New submission from Joël Larose <joel.lar...@gmail.com>:

Hi,

I'm trying to implement a metaclass for the singleton pattern, with the intent 
of creating type-appropriate sentinels.  After trying several approaches, I've 
come up with what I thought would be an elegant solution.  

However, I've run into a bit of a snag.  Whenever I "call" the class to get the 
instance, the machinery behind the scenes always calls __init__.  To bypass 
this, I tried overriding type.__call__ in my metaclass.  Contrary to all the 
documentation I've read, metaclass.__call__ is not being used.  The call 
sequence goes straight to class.__new__ and class.__init__.

=====================================================
M = TypeVar("M")

class SingletonMeta(type):
    """Metaclass for single value classes."""
    def __call__(cls: Type[M], *args: Any, **kwargs: Any) -> M:

        ### Never see this line of output
        print(f"{cls.__name__}.__call__({args=}, {kwargs=}")

        it: Optional[M] = cast(Optional[M], cls.__dict__.get("__it__"))
        if it is not None:
            return it

        try:
            it = cls.__new__(*args, **kwargs)
            it.__init__(*args, **kwargs)
        except TypeError:
            it = cls.__new__()
            it.__init__()

        # cls.__it__ = it
        return it

    def __new__(mcs, name: str, bases: th.Bases, namespace: th.DictStrAny,
                **kwargs: Any) -> SingletonMeta:
        print(f"{mcs.__name__}.__new__({name=}, {bases=}, {namespace=}, 
{kwargs=}")
        new_cls: SingletonMeta = cast(SingletonMeta, type(name, bases, 
namespace))
        print(f"{new_cls=}")
        print(f"{new_cls.__call__}")

        ### Both of these lines ignore the __call__ defined in this metaclass
        ### They produce TypeError if the class doesn't define __new__ or 
__init__ accepting arguments
        # new_cls.__it__ = new_cls(new_cls, **kwargs)
        # new_cls.__it__ = new_cls.__call__(new_cls, **kwargs)

        return new_cls


Here's the output I get after defining the metaclass and try to use it:
>>> class S(metaclass=SingletonMeta):
...    pass
SingletonMeta.__new__(name='S', bases=(), namespace={'__module__': '__main__', 
'__qualname__': 'S'}, kwargs={}
new_cls=<class '__main__.S'>
<method-wrapper '__call__' of type object at 0x000002C1283BF1D0>
>>> S()
<__main__.S object at 0x000002C128AE5940>
>>> S()
<__main__.S object at 0x000002C128AE56A0>


If SingletonMeta.__call__ was being used, I would see the output from that 
call, and consecutive calls to S() would yield the same object (with the same 
address).  As you can see, that is not the case.


Environment: 
Python 3.9.0 (tags/v3.9.0:9cf6752, Oct  5 2020, 15:34:40) [MSC v.1927 64 bit 
(AMD64)] on win32

Is this a bug?  Or am I misunderstanding how/when __call__ gets called?

----------
components: Interpreter Core
messages: 389947
nosy: joel.larose
priority: normal
severity: normal
status: open
title: __call__ not being called on metaclass
type: behavior
versions: Python 3.9

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue43685>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to