PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0

Wednesday, October 26, 2022

[FIXED] How to get default arguments from concrete __init__ into a metaclass' __call__?

 October 26, 2022     metaclass, oop, python     No comments   

Issue

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?


Solution

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.)



Answered By - jsbueno
Answer Checked By - Mildred Charles (PHPFixing Admin)
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Newer Post Older Post Home

0 Comments:

Post a Comment

Note: Only a member of this blog may post a comment.

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
Comments
Atom
Comments

Copyright © PHPFixing