niCk cAMel
niCk cAMel

Reputation: 919

Add an automated function call to each method

Is it possible to create a "constructor".. or rather "Initializer" to each function, instead of having to manually write it at the top of each function in class?

So, each time a function in a class is called, the other assigned function (unknown to caller) is always called first (called pre_check in below example).

An example using super(), but I then have to manually copy it inside each function.

class Helper():
    def pre_check(self):
        print("Helper fcn")


class Parent(Helper):
    def __init__(self):
        print("Initializer")

    def foo(self):
        super().pre_check()  # <---- new code
        # ... existing code here ...

    def bar(self):
        super().pre_check()  # <---- new code
        # ... existing code here ...

    def many_more_functions(self):
        super().pre_check()  # <---- new code
        # ... existing code here ...

m = Parent()

m.foo()
m.bar()

Note how __init__ in Parent is not supposed to run pre_check.

Upvotes: 1

Views: 129

Answers (3)

Serge Ballesta
Serge Ballesta

Reputation: 149185

You can use a decorator for the class that will in turn decorate all public methods defined in the class:

def addhelper(helpmethod):
    def deco(cls):
        def decomethod(method):
            def inner(self, *args, **kwargs):
                helpmethod(self)
                return method(self, *args, **kwargs)
            # copy signature, doc and names from the original method
            inner.__signature__ = inspect.signature(method)
            inner.__doc__ = method.__doc__
            inner.__name__ = method.__name__
            inner.__qualname__ = method.__qualname__
            return inner
        # search all methods declared in cls with a name not starting with _
        for name, meth in inspect.getmembers(
            cls,lambda x: inspect.isfunction(x)
            and not x.__name__.startswith('_')
            and x.__qualname__.startswith(cls.__name__)):
            # replace each method with its decoration
            setattr(cls, name, decomethod(meth))
        return cls
    return deco

class Helper():
    def pre_check(self):
        print("Helper fcn")

@addhelper(Helper.pre_check)
class Parent(Helper):
    def __init__(self):
        print("Initializer")

    def foo(self):
#        super().pre_check()  # <----
        print('in foo')

    def bar(self):
#        super().pre_check()  # <----
        print('in bar')

    def many_more_functions(self):
#        super().pre_check()  # <----
        print('in many_more_functions')

We can now use it:

>>> p = Parent()
Initializer
>>> p.foo()
Helper fcn
in foo
>>> p.bar()
Helper fcn
in bar
>>> p.many_more_functions()
Helper fcn
in many_more_functions

Upvotes: 2

MisterMiyagi
MisterMiyagi

Reputation: 52169

Use __init_subclass__ to change subclasses as they are created. You can wrap the methods of subclasses:

class Helper():
    def __init_subclass__(cls):
        for field, value in cls.__dict__.items():
            # add additional checks as desired, e.g. exclude __special_methods__
            if inspect.isfunction(value) and not getattr(value, 'checked', False):
                setattr(cls, field, cls._check(value))  # wrap method

    @classmethod
    def _check(cls, fcn):
        """Create a wrapper to inspect the arguments passed to methods"""
        @functools.wraps(fcn)
        def checked_fcn(*args, **kwargs):
            print(fcn, "got", args, kwargs)
            return fcn(*args, **kwargs)
        return checked_fcn


class Parent(Helper):
    def __init__(self):
        print("Initializer")

    def foo(self):
        print("Foo")

Note that this will wrap all methods, including special methods such as __init__:

>>> Parent().foo()
<function Parent.__init__ at 0x1029b2378> got (<__main__.Parent object at 0x102c09080>,) {}
Initializer
<function Parent.foo at 0x1029b2158> got (<__main__.Parent object at 0x102c09080>,) {}
Foo

You can extend the check in __init_subclass__ with arbitrary rules to filter out functions. For example, field[:2] == field[-2:] == "__" excludes special methods.

Upvotes: 1

Amartya Gaur
Amartya Gaur

Reputation: 705

You can use metaclass and define a decorator for each method in the instance of that metaclass

Code :

def decorate(f):
    def do_something(self, a):

        if (f(self, a) > 18) :
            return ("Eligible to vote")
        else :
            return ("Not eligible to vote")

    return do_something


class Meta(type):
    def __new__(cls, name, bases, namespace, **kwds):
        namespace = {k: v if k.startswith('__') else decorate(v) for k, v in namespace.items()}
        return type.__new__(cls, name, bases, namespace)


class MetaInstance(metaclass=Meta):

    def foo1(self, val):
        return val + 15

    def foo2(self, val):
        return val + 9

obj1 = MetaInstance()
print(obj1.foo1(5))
print(obj1.foo2(2))

Upvotes: 1

Related Questions