Please consider
class Meta(type):
def __call__(cls, *args, **kwargs):
print(cls)
print(f"args = {args}")
print(f"kwargs = {kwargs}")
super().__call__(*args, **kwargs)
class Actual(metaclass=Meta):
def __init__(self, value=1):
print(f"value=P{value}")
a1 = Actual()
a2 = Actual(value=2)
outputs
<class '__main__.Actual'>
args = ()
kwargs = {}
value=P1
<class '__main__.Actual'>
args = ()
kwargs = {'value': 2}
value=P2
Please notice kwargs = {}
instead of kwargs = {'value': 1}
as would be expected from the default __init__
argument in Actual
.
How can I get the value in case a default was used, in the __call__
method?
CodePudding user response:
The metaclass'__call__
will do that: call the class init, where the default value is stored: so it is only natural that this defaultvalue is not made avaliable upstream to it.
However, as it knows which __init__
method it will call, it can just introspection, through the tools offered in the inspect
module to retrieve any desired defaults the about-to-be-called __init__
method has.
With this code you end up with the values that will be actually used in the __init__
method in the intermediate "bound_arguments" object - all arguments can be seem in a flat dictinary in this object's .arguments
attribute.
import inspect
class Meta(type):
def __call__(cls, *args, **kwargs):
# instead of calling `super.__call__` that would do `__new__` and `__init__`
# in a single pass, we need
print(cls)
sig = inspect.signature(cls.__init__)
bound_args = sig.bind(None, *args, **kwargs)
bound_args.apply_defaults()
print(f"args = {bound_args.args}")
print(f"kwargs = {bound_args.kwargs}")
print(f"named arguments = {bound_args.arguments}")
return super().__call__(*args, **kwargs)
class Actual(metaclass=Meta):
def __init__(self, value=1):
print(f"value=P{value}")
a1 = Actual()
a2 = Actual(value=2)
Output:
<class '__main__.Actual'>
args = (None, 1)
kwargs = {}
named arguments = {'self': None, 'value': 1}
value=P1
<class '__main__.Actual'>
args = (None, 2)
kwargs = {}
named arguments = {'self': None, 'value': 2}
value=P2
(Note that we do call super.__call__
with the original "args" and "kwargs"and not with the values in the intermediate object. In order to create the instance of BoundArguments used, this code passes "None" in place of "self": this just creates the appropriate argument and never run through the actual function.)