Reputation: 73
I would like to have in the code underneath that when i type instance_of_A = A(
, that the name of the supposed arguments is init_argumentA
and not *meta_args, **meta_kwargs
. But unfortunatally, the arguments of the __call__
method of the metaclass are shown.
class Meta(type):
def __call__(cls,*meta_args,**meta_kwargs):
# Something here
return super().__call__(*meta_args, **meta_kwargs)
class A(metaclass = Meta):
def __init__(self,init_argumentA):
# something here
class B(metaclass = Meta):
def __init__(self,init_argumentB):
# something here
I have searched for a solution and found the question How to dynamically change signatures of method in subclass? and Signature-changing decorator: properly documenting additional argument. But none, seem to be completely what I want. The first link uses inspect to change the amount of variables given to a function, but i can't seem to let it work for my case and I think there has to be a more obvious solution. The second one isn't completely what I want, but something in that way might be a good alternative.
Edit: I am working in Spyder. I want this because I have thousands of classes of the Meta type and each class have different arguments, which is impossible to remember, so the idea is that the user can remember it when seeing the correct arguments show up.
Upvotes: 6
Views: 960
Reputation: 31
Although this question is from 6 years ago, I'd like to point out that this much simpler implementation also works:
from functools import wraps
class Meta(type):
@wraps(type.__call__)
def __call__(cls, *meta_args, **meta_kwargs):
# Something here
return super().__call__(*meta_args, **meta_kwargs)
class A(metaclass=Meta):
def __init__(self, x):
"""Documentation for 'A.__init__'"""
pass
Upvotes: 0
Reputation: 11293
I found that the answer of @johnbaltis was 99% there but not quite what was needed to ensure the signatures were in place.
If we use __init__
rather than __call__
as below we get the desired behaviour
import inspect
class Meta(type):
def __init__(cls, clsname, bases, attrs):
# Restore the signature
sig = inspect.signature(cls.__init__)
parameters = tuple(sig.parameters.values())
cls.__signature__ = sig.replace(parameters=parameters[1:])
return super().__init__(clsname, bases, attrs)
def __call__(cls, *args, **kwargs):
super().__call__(*args, **kwargs)
print(f'Instanciated: {cls.__name__}')
class A(metaclass=Meta):
def __init__(self, x: int, y: str):
pass
which will correctly give:
In [12]: A?
Init signature: A(x: int, y: str)
Docstring: <no docstring>
Type: Meta
Subclasses:
In [13]: A(0, 'y')
Instanciated: A
Upvotes: 1
Reputation: 808
Not sure if this helps the author but in my case I needed to change inspect.signature(Klass)
to inspect.signature(Klass.__init__)
to get signature of class __init__
instead of metaclass __call__
.
Upvotes: 0
Reputation: 1588
Using the code you provided, you can change the Meta
class
class Meta(type):
def __call__(cls, *meta_args, **meta_kwargs):
# Something here
return super().__call__(*meta_args, **meta_kwargs)
class A(metaclass=Meta):
def __init__(self, x):
pass
to
import inspect
class Meta(type):
def __call__(cls, *meta_args, **meta_kwargs):
# Something here
# Restore the signature of __init__
sig = inspect.signature(cls.__init__)
parameters = tuple(sig.parameters.values())
cls.__signature__ = sig.replace(parameters=parameters[1:])
return super().__call__(*meta_args, **meta_kwargs)
Now IPython or some IDE will show you the correct signature.
Upvotes: 2
Reputation: 110516
Ok - even though the reason for you to want that seems to be equivocated, as any "honest" Python inspecting tool should show the __init__
signature, what is needed for what you ask is that for each class you generate a dynamic metaclass, for which the __call__
method has the same signature of the class's own __init__
method.
For faking the __init__
signature on __call__
we can simply use functools.wraps
. (but you might want to check the answers at
https://stackoverflow.com/a/33112180/108205 )
And for dynamically creating an extra metaclass, that can be done on the __metaclass__.__new__
itself, with just some care to avoud infinite recursion on the __new__
method - threads.Lock can help with that in a more consistent way than a simple global flag.
from functools import wraps
creation_locks = {}
class M(type):
def __new__(metacls, name, bases, namespace):
lock = creation_locks.setdefault(name, Lock())
if lock.locked():
return super().__new__(metacls, name, bases, namespace)
with lock:
def __call__(cls, *args, **kwargs):
return super().__call__(*args, **kwargs)
new_metacls = type(metacls.__name__ + "_sigfix", (metacls,), {"__call__": __call__})
cls = new_metacls(name, bases, namespace)
wraps(cls.__init__)(__call__)
del creation_locks[name]
return cls
I initially thought of using a named parameter to the metaclass __new__
argument to control recursion, but then it would be passed to the created class' __init_subclass__
method (which will result in an error) - so the Lock use.
Upvotes: 0