[issue43685] __call__ not being called on metaclass

Joël Larose report at bugs.python.org
Wed Mar 31 22:37:08 EDT 2021


New submission from Joël Larose <joel.larose at 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 <report at bugs.python.org>
<https://bugs.python.org/issue43685>
_______________________________________


More information about the Python-bugs-list mailing list