I'm building a framework and want to allow users to easily influence the construction of an object (e.g. singletons)
However no matter what I've tried I always get recursion. I wonder if there's a way to achieve this without altering create_instance. I'm sure I can probably achieve this by passing all the necessary stuff from the metaclass to the function, but the key is keeping it extremely simple for end users.
def create_instance(cls, *args, **kwargs):
print("CUSTOM LOGIC TO CREATE INSTANCE")
return cls(*args, **kwargs)
class PersonMetaclass(type):
def __call__(cls, *args, **kwargs):
# Delegate creation elsewhere
return create_instance(cls, *args, **kwargs)
class Person(metaclass=PersonMetaclass):
def __init__(self, *args, **kwargs):
print("Person instance created")
person = Person()
Output:
CUSTOM LOGIC TO CREATE INSTANCE
CUSTOM LOGIC TO CREATE INSTANCE
CUSTOM LOGIC TO CREATE INSTANCE
CUSTOM LOGIC TO CREATE INSTANCE
..
E RecursionError: maximum recursion depth exceeded while calling a Python object
!!! Recursion detected (same locals & position)
The problem here is that when creating a class' instance, what Python does is exactly call the metaclass'
__call__method.type's call is responsible for calling the class__new__and__init__to create the new instance.And in your code, the metaclass
__call__calls a function that will try to build an instance by simply calling the class again: that will call the same__call__method, and there is your recursion.I am usually very verbose against using metaclasses for creating singletons - it usually just a matter of creating an instance of the desired class to act as singleton, and expose that in the documentation. Or just modifying the class'
__new__method - no metaclasses needed. (But changing the__new__alone might cause the__init__method, if any, to be called more than once).Anyway, a simple thing there, if you don't want to change anything in
create_instance, is to have some control in the metaclass'__call__method to detect when it is been re-entered, and runsuper().__call__(), instead of callingcreate_instance. If you use aContextVarfor that, it will work across threads and asynchronous code. (Multiprocessed code, would, naturally have one instance per process)